Every VPS deployment ends up as the same ritual: SSH in, git pull, npm install, pm2 restart. Automating it properly means either exposing SSH credentials to CI or paying for a platform that wraps a container runtime you don’t need.

floo runs an HTTP agent on the server. You configure projects and their steps in a YAML file. You issue tokens — one per project, scoped, shown once, stored hashed. From your machine or a CI pipeline, you run floo myapp and the deploy logs stream back in real time via SSE.

projects:
  avelor.es:
    cwd: /var/www/html/avelor.es
    steps:
      - git fetch origin master
      - git reset --hard origin/master
      - npm ci
      - pm2 restart avelor --update-env
floo avelor.es
 deploying avelor.es  4 steps
[1/4] git fetch origin master ✓
[2/4] git reset --hard origin/master ✓
...

Architecture decisions

Tokens hashed at rest. SHA-256, shown once at issuance. The server stores no raw token — a dump of the token file is useless to an attacker.

Per-project token scoping. A token for avelor.es returns 403 against any other project name. A compromised token can’t enumerate projects or deploy anything else.

SSE for log streaming. No WebSocket, no polling. The response is a text/event-stream that stays open while steps execute. floo the client reads it and prints each line as it arrives.

systemd as the process supervisor. floo agent install writes and enables a systemd unit. No wrapper scripts, no custom daemon logic, no PID file juggling. systemctl restart floo is the ops interface.

What I ruled out

  • Webhook-only approaches — no streaming, no live feedback during a deploy.
  • Docker-based solutions — the sites running on this VPS are PHP + Node apps. A container runtime is overhead that doesn’t pay for itself.
  • Managed platforms — Render, Railway, Fly — each has constraints on build environment or charges for what’s effectively a small Node process and a git pull.

Implementation

~700 lines of Node.js across six modules. The agent is a plain http.createServer. Steps run as sh -c <cmd> subprocesses with stdout/stderr piped back line by line. The client is a fetch loop over the SSE response. Config is YAML via js-yaml, tokens stored as JSON.

Release pipeline builds platform binaries via Node SEA (Single Executable Applications) and publishes to npm and a Homebrew tap on each tag.

npm install -g @avelor/floo

v0.2.1. Repo: github.com/avelor-es/floo