Production deploy
api.nodetool.ai runs on the Docker host 46.224.131.110. Caddy routes traffic
to app-1 and app-2 with cookie affinity. Both replicas use the same Supabase
database, auth project, and storage buckets. Deployment configuration and the
rolling release script live in the adjacent
nodetool-deploy repository,
checked out at /home/claude/nodetool-deploy on that host.
Release chain
A push to main starts the Docker image build and User Journeys independently.
The Deploy to Docker workflow waits for both to succeed for that exact commit
and rejects a commit superseded on main. It retains the historical filename
fly-deploy.yml, but does not invoke Fly or
use its credentials. Manual dispatch follows the same gates and uses the
dispatch commit, never a mutable image tag.
The Deploy server job connects to the host over SSH and sends only the full
commit SHA. A host-side forced command validates the SHA, rechecks the build
gates and current main, and invokes the rolling script with
ghcr.io/nodetool-ai/nodetool:main-<shortsha>. The host resolves this to an
immutable digest. GitHub serializes release runs without cancelling a rollout
in progress. The host script also takes a deployment lock.
web-deploy.yml follows Deploy to Docker
and checks that its Deploy server job succeeded before releasing the same
commit to Cloudflare Pages. A skipped or failed server release cannot promote
the frontend. Cloudflare Pages credentials remain in web-production.
GitHub connection settings
Repository variables:
DOCKER_DEPLOY_HOST: the Docker host IP or hostname.DOCKER_DEPLOY_USER: the SSH account that owns the deployment checkout.
Repository secrets:
DOCKER_DEPLOY_SSH_KEY: a dedicated Ed25519 private key.DOCKER_DEPLOY_KNOWN_HOSTS: the host’s verified SSH public key entry.
The matching public key in authorized_keys uses restrict and forces
/home/claude/nodetool-deploy/bootstrap/github-ssh-deploy.sh. It permits only a
full release SHA or the read-only check probe, not shell commands, forwarding,
or a terminal. The SSH client requires the pinned host key. Database credentials
remain in the host’s mode-0600 .env and are not copied into Actions.
Rolling release
The script pulls the image and runs its database migrations once before changing either replica. It then verifies the peer is healthy, signals SIGUSR2 to drain the selected replica, waits for its turns and jobs to reach zero, and recreates it. Health must pass before the peer is drained. The saved image digest changes only after both replacements succeed. A failed migration or replacement stops the release. Migrations must remain compatible with the previous image during the handover.
Inspect the running deployment on the host:
cd /home/claude/nodetool-deploy
docker compose ps
node bootstrap/verify.mjs --public
docker compose logs --tail 100 app-1 app-2
For a manual rollback, review schema compatibility and run the rolling script
on the host with a previously deployed immutable image digest. Rollbacks are
not accepted by the restricted GitHub key, which only releases current main.
The deployment setup guide covers DNS, trigger-dispatch handover, pool sizing, and affinity checks. The retained Fly guide covers legacy rollback operations, not the active GitHub release destination.