SugarDocs

Deploy Sugar#

Run one Sugar process on a host you control, under a service manager, serving an immutable commit-named release directory on loopback behind an HTTPS proxy. The repository ships the deployment as two fail-closed scripts: npm run deploy builds and promotes a release, and npm run rollback returns to the previous one.

Before you start#

  • A Linux host with git, curl, tar, npm, Node.js 20.9 or newer and GNU coreutils. The cutover uses mv -T, so GNU mv is required.
  • A dedicated unprivileged sugar account that owns the release tree.
  • An SSH key for the deploy account that is not passphrase-protected, or is already loaded into an agent. The scripts run ssh with BatchMode=yes.
  • Passwordless sudo for the deploy account, limited to systemctl on the two units below and to sudo -u sugar. Every call uses sudo -n, so a missing rule fails instead of prompting.
  • A public HTTPS proxy that terminates TLS, forwards to loopback port 3211, preserves streaming responses and overwrites a client-address header.
  • The commit you intend to ship, pushed and reachable from origin.

Prepare the host once#

The deploy script deploys; it does not provision. Create this layout first:

/opt/sugar/
├── repo/                       # bare mirror the operator pushes into
├── releases/<full-commit-sha>/ # one immutable, already-built release each
├── current  -> releases/<sha>  # what the service unit serves
└── previous -> releases/<sha>  # the rollback target
/etc/sugar/sugar.env            # environment file, root:sugar, mode 0640
/var/lib/sugar/                 # SQLite database and WAL, outside every release
sudo useradd --system --create-home --shell /usr/sbin/nologin sugar
sudo install -d -o sugar -g sugar /opt/sugar /opt/sugar/releases /var/lib/sugar
sudo install -d -o root -g sugar -m 0750 /etc/sugar
sudo -u sugar git init --bare -b main /opt/sugar/repo

Configure two systemd units:

  • sugar.service runs as sugar with WorkingDirectory=/opt/sugar/current, EnvironmentFile=/etc/sugar/sugar.env, and listens on 127.0.0.1:3211. The working directory is resolved at start, so a release change takes effect only on restart.
  • sugar-backup.service is a Type=oneshot unit whose ExecStart is an absolute path outside /opt/sugar/releases. It backs up the database with SQLite's online backup API and exits non-zero on any failure. The deploy reads its exit status and trusts it.

Set at least NODE_ENV, APP_URL, SUGAR_DATA_DIR=/var/lib/sugar and SUGAR_TRUSTED_CLIENT_IP_HEADER in /etc/sugar/sugar.env. See configuration.html for the full set.

Warning SUGAR_DATA_DIR must never point inside a release directory. Releases are pruned; the database must not be.

Deploy a release#

Point the scripts at your host, then read the plan before the deploy:

export SUGAR_DEPLOY_HOST=deploy@your-host
export SUGAR_DEPLOY_KEY=~/.ssh/your-deploy-key
export SUGAR_DEPLOY_ROOT=/opt/sugar

git push
npm run deploy:dry-run
npm run deploy

--host, --key and --root override the same three values for one run. Add --keep <n> to change release retention, --no-prune to keep every release, and a full commit sha as the first argument to deploy a commit other than HEAD. --dry-run opens no connection and changes nothing.

The deploy runs in this order and stops at the first step it cannot confirm:

  1. Local preflight — resolve the sha, require a clean working tree, fetch origin, require the sha to be reachable from a remote-tracking ref. --allow-dirty overrides the clean-tree refusal only; the committed sha is still the only thing that ships.
  2. Host preflight — prove the layout, the account, the tools, GNU mv -T, the sudo rules and both units exist.
  3. Publish — push the commit into /opt/sugar/repo over the deploy key, then confirm the mirror can produce the object.
  4. Release — extract the commit into /opt/sugar/releases/<sha>. A directory already marked complete is reused; interrupted build debris is rebuilt.
  5. Buildnpm ci && npm run build as sugar, then require .next/sugar-route-surface.json.
  6. Backup — run sugar-backup.service and require a success whose exit timestamp is newer than the start of this step.
  7. Cutover — record the outgoing release in previous and repoint current, each as one atomic rename.
  8. Restart — restart sugar.service and require it active.
  9. Verify — poll both health endpoints until both pass in the same round.
  10. Rollback — on a verification failure only: promote previous, restart and re-verify.
  11. Prune — after a verified deploy only: keep the newest five releases plus whatever current and previous point at.

Re-running the same deploy is safe. A completed release is not rebuilt, and deploying the release that is already live leaves the rollback target alone.

Important Never remove scripts/ from a release. Next.js resolves the route-surface adapter from it on every command, next start included.

Verify#

Both http://127.0.0.1:3211/api/health on the host and the public https://<your-origin>/api/health must return HTTP 200 with:

Field Required value Meaning
ready true A compute lane is ready, AUTH_SECRET is set, a configured receipt signer is valid, model and routing-policy configuration is valid, and no critical route is missing.
routes.missing [] Every critical route this build should register is servable.
routes.source "build" The route manifest could be proved to describe this build. "unavailable" means the check could not run.

breakers.open may be non-zero and the deploy still passes: a tripped lane is a degraded provider, not a bad build.

Then send one streaming request through the public proxy and confirm disconnect and timeout behaviour. To exercise the whole path — bootstrap, provider setup, key issue, buffered and streamed completion, usage and quota — against a fake upstream, run npm run smoke. Set SMOKE_PORT and SMOKE_UPSTREAM_PORT above 3050 when the host is already serving Sugar.

Roll back#

npm run rollback

Rollback is automatic when verification fails. Run it by hand when a release passed its health checks but is misbehaving in a way health cannot see. It refuses when previous is missing, dangling, identical to current or is not a built release; it repoints previous at the release being retired, so rolling forward again is one command; and it requires the same three health conditions from both endpoints.

Rollback deliberately does not run sugar-backup.service. The backup gate belongs to the forward path, where new code is about to touch the database.

If it does not work#

Symptom Check Recovery
The deploy stops at backup journalctl -u sugar-backup.service Nothing changed and the old release is still serving. Fix the backup destination, credentials or file permissions, then deploy again. Do not bypass the gate.
The backup reports success with a stale exit time Whether the unit is RemainAfterExit=yes and its start became a no-op Repair the unit so each start actually runs a backup.
routes.source is "unavailable" Whether npm run build wrote .next/sugar-route-surface.json, and whether scripts/ survived into the release Rebuild the release from the complete tree. The deploy refuses to promote a build whose route surface cannot be proved.
Loopback health passes and public health fails DNS, TLS and the proxy Fix the public path. The deploy treats this as a failed deploy and rolls back.
The rollback itself fails systemctl status sugar.service, journalctl -u sugar.service -n 200, ls -l /opt/sugar Treat it as an outage and repair by hand. Do not retry the deploy.
Sign-in throttles behave as if every visitor shares one address Whether SUGAR_TRUSTED_CLIENT_IP_HEADER names the header the proxy overwrites Set it and restart. Sugar warns at start-up in production when it is unset.

Related: configuration.html, operations.html, troubleshooting.html.