What neo init Actually Does (and Why That Restraint Is the Point)

neo init doesn't run an agent or a control plane — but it touches more than Docker and Caddy: swap, a managed SSH key, a bare-IP welcome page. Here's the complete, honest list, and how to undo every part of it.

8 min read

The question worth asking before you run it

neo init root@your-server is the first command against a new server, and it's reasonable to want to know what it's about to do before pointing it at a real machine. Before anything touches the server: if the name you're initializing already exists in your local ~/.neo/config.json, Neo asks whether to overwrite that entry, and stops there if you say no. Past that point, here's everything it does, in order:

  1. Connects over SSH and checks the OS is supported (Ubuntu 24.04+, Debian, Fedora 39+, or RHEL-family 9+)
  2. Detects RAM, CPU, and the server's IP — from the host argument if it's already an IP, otherwise with curl -sf https://ifconfig.me run on the server itself
  3. Updates system packages — apt-get on Debian/Ubuntu, dnf on Fedora and RHEL-family distros
  4. Adds swap if the server has none, sized to RAM: 2 GB or less gets 2 GB of swap, 2–8 GB gets roughly 1× RAM rounded to the nearest GB, and anything over 8 GB skips it as unnecessary. Adding swap means writing to two system files — a line appended to /etc/fstab so the swapfile survives a reboot, and vm.swappiness=10 appended to /etc/sysctl.conf
  5. Installs Docker if it isn't already there, via the official get.docker.com script, and — if you're connecting as a non-root user — adds that user to the docker group. If Docker's already installed with containers running, Neo lists them and asks before stopping anything; answering no cancels init entirely
  6. Creates one Docker network, named neo
  7. Starts one container: neo-caddy, for reverse proxying and automatic HTTPS
  8. Adds a catch-all Caddy welcome page, so visiting the server's bare IP shows something instead of a connection error — this is exactly what neo stealth removes later, if you'd rather the server not respond to anything but its configured domains
  9. Writes /etc/neo/state.json — a plain file recording what's deployed, in a freshly created /etc/neo/ directory locked to mode 700 — and saves the server to your local ~/.neo/config.json
  10. Deploys a managed SSH key: generates an ed25519 keypair at ~/.neo/neo_ed25519 on your machine, if one doesn't already exist, and appends the public half to ~/.ssh/authorized_keys on the server, so future connections don't need a password

That's the complete list — ten steps, not the seven you'd get from counting only the parts that sound impressive. No agent process, no daemon phoning home, no database, no dashboard software running on the server.

Why the restraint is deliberate, not a missing feature

Nothing to keep patched but Docker and Caddy themselves. A typical PaaS agent is itself a piece of server-side software with its own update cycle, its own vulnerabilities, its own failure modes. Neo has none of that surface — there's simply nothing extra running that could need a security patch.

It's inspectable without Neo's help. /etc/neo/state.json is a plain JSON file, not a database behind an API. cat /etc/neo/state.json on the server tells you exactly what Neo believes is deployed — no CLI required, no black box.

It's reversible. Stop using Neo entirely, and the server keeps working exactly as it did the moment before — it's still just Docker containers behind Caddy, both of which you could operate by hand if you ever needed to. There's no agent to uninstall, no control plane to migrate away from — see "How to undo it" below for exactly what that looks like in practice.

Every action after this point is one SSH command. neo deploy, neo status, neo logs — all of them are Neo connecting over the same SSH credential you already trust to manage the box, running a command, and disconnecting. There's no separate access model to reason about, no additional credential that could leak.

What it changes on your system

"Nothing else" was overclaiming. Here's every file, permission, and group membership init can change, and exactly when each one applies:

Path Change When
~/.ssh/authorized_keys (server) Neo's public key appended Every run
/etc/fstab (server) /swapfile none swap sw 0 0 appended Only if swap was added
/etc/sysctl.conf (server) vm.swappiness=10 appended Only if swap was added
/etc/neo/ (server) New directory, mode 700, holding state.json Every run
docker group (server) SSH user added as a member Only if the SSH user isn't root
~/.neo/neo_ed25519 (your machine) New ed25519 keypair generated Every run, unless one already exists
~/.neo/config.json (your machine) Server entry saved (name, host, port) Every run

What it doesn't touch

No edits to sshd_config. No new user accounts. No firewall rules configured by Neo — ports 80 and 443 are reachable because Caddy needs them, not because Neo ran ufw, firewalld, or raw iptables; none of those are called anywhere in the init path. Docker's own installer does manage iptables/nftables rules for container networking, the same way it would on any server where you installed Docker by hand — that's Docker's doing, not Neo's. No packages installed beyond Docker itself. No web server running on the host outside the neo-caddy container. No app code, no data.

If you want intrusion prevention on top of this, that's a separate, explicit opt-in — see neo firewall install — not something init assumes you want.

How to undo it

There's no neo uninstall. Reversing what init did means walking each change back by hand, in roughly the order init made them. Run these on the server, unless a comment says otherwise:

# Swap — only relevant if init added one
swapoff /swapfile
sed -i '/\/swapfile none swap sw 0 0/d' /etc/fstab
sed -i '/vm.swappiness=10/d' /etc/sysctl.conf
rm /swapfile

# Neo's SSH key isn't tagged with a comment in authorized_keys, so match it
# against your local copy before deleting the line
cat ~/.neo/neo_ed25519.pub                       # run locally — this is what to look for
ssh -t user@server 'nano ~/.ssh/authorized_keys' # delete the matching line, save

# The bare-IP welcome page — run locally, this is a neo command
neo stealth

# Neo's state
rm -rf /etc/neo

# The proxy and its network
docker stop neo-caddy && docker rm neo-caddy
docker network rm neo

None of this is automated, on purpose — undoing swap sizing or deleting a running container isn't something that should happen without you looking at it first. Same philosophy as the rest of init: nothing hidden, nothing you can't do yourself with plain commands.

After init

neo deploy

See Deploying Your First App with Neo for what happens from here — and if a server was already set up by someone else, you don't run init again: see Joining a Server Someone Else Set Up with neo attach.

Try it yourself — Neo is free, with every feature unlocked.

Get started free