Production runbook
Deploy and operate a controller safely.
Before enabling provisioning
- Create a dedicated service account and persistent state directory, such as
/var/lib/pve-agents. - Put secrets in a root-readable environment file, not the checkout. Set
DATABASE_PATHto the persistent directory. - Bind to loopback or a trusted LAN address and terminate TLS before exposing the controller.
- Set a 32+ character
CONTROLLER_AUTH_SECRETand aCONTROLLER_URLthat exactly matches the browser origin. - Create the GitHub OAuth app for operator sign-in and set the numeric
CONTROLLER_OPERATOR_GITHUB_ID. - Create a GitHub App with Contents: read/write and Pull requests: read/write, install it for the repositories it may access, and configure its key.
- Build a template, create the pool and API token, configure workspace networking, and add at least one agent in Settings → Agents.
- Keep
PROVISIONING_ENABLED=falseuntil you have tested the infrastructure.
Create an API key for scripts with pnpm apikey:create <name>. It is shown once and stored only as a
hash.
Deploy
Run the project's deployment script. Before starting a new release, run:
pnpm check && pnpm typecheck && pnpm test && pnpm build
pnpm db:migratedb:migrate is required before serving traffic: migrations otherwise wait for the first database
request. Verify GET /api/health after the service starts.
A minimal systemd unit is:
[Service]
User=pve-agents
WorkingDirectory=/opt/pve-agents
EnvironmentFile=/opt/pve-agents/.env
ExecStart=/usr/bin/pnpm start
Restart=on-failureSet NODE_EXTRA_CA_CERTS directly in the unit if needed; Node reads it before the application loads
the environment file.
Run workspaces
Use pnpm worker:tick for one worker pass, or pnpm worker:tick --watch 5 while validating a new
installation. Once verified, set WORKER_ENABLED=true; the server then runs the same loop at
WORKER_INTERVAL_SECONDS (default: 5).
Reaping is configured in Settings, not the environment. It will not remove blocked workspaces, workspaces with uncommitted or unpushed work, or ones that cannot be inspected.
Back up SQLite with the service stopped or using SQLite's online backup support. For file-level
backups, preserve the database with its -wal and -shm files.
Reverse proxy
SSE must not be buffered or compressed. For Caddy:
reverse_proxy 10.0.3.50:3000 {
flush_interval -1
}
@compressible not path /api/workspaces/*/agent
encode @compressible gzipUse the controller's configured address, then test at the public hostname. A stream that connects but never delivers updates is usually being buffered by the proxy.
Last updated on