3. Deploy the control plane
The control plane is a web application: a console, an API, and PostgreSQL. Production runs it on Kubernetes; nothing requires that.
Components
Section titled “Components”| Component | What it is | Scaling |
|---|---|---|
api | The HTTP API | Stateless; replicable |
web | The console | Stateless; replicable |
migrate | A job that applies schema migrations | Runs once per deploy, before the API rolls out |
| PostgreSQL | Intent, allocations, job queue, audit trail | The only stateful component |
Order matters
Section titled “Order matters”Migrations run before the API rolls out. In the other order, new API pods spend a few seconds querying a schema that does not have their columns yet and return errors to real users.
The supplied deployment script enforces this: build and push, apply infrastructure manifests, run the migration job to completion, then roll out the API and console.
Environment
Section titled “Environment”| Variable | Required | Purpose |
|---|---|---|
ZONE9_DATABASE_URL | yes | PostgreSQL connection string |
ZONE9_JWT_SECRET | yes | Signs session tokens |
ZONE9_AGENT_LATEST | yes | The agent version the panel expects; drives the “update available” badge |
ZONE9_DELETE_BUFFER_SECONDS | yes | The two-phase deletion window. 600 is a sensible default |
ZONE9_CLOUDFLARE_API_TOKEN | optional | DNS hosting and ACME challenges |
ZONE9_CLOUDFLARE_ACCOUNT_ID | optional | When the token can reach several accounts |
ZONE9_NAMESILO_KEY | optional | Domain registration |
ZONE9_ACME_EMAIL | optional | Contact address for the ACME account; expiry notices only |
ZONE9_ACME_DIRECTORY | optional | Defaults to Let’s Encrypt production. Point it at staging while testing |
ZONE9_CONSOLE_WS_URL | yes | Where browsers connect for console sessions |
ZONE9_PLATFORM_ADMINS | first run | Bootstrap platform operator identities |
Provider credentials are optional on purpose: without a DNS token the panel starts normally and only the DNS features are unavailable, with the reason shown on screen. A missing optional integration must not take the whole panel down.
Ingress
Section titled “Ingress”| Path | Service |
|---|---|
/ | console |
/api/v1 | API |
/console/ws | console WebSocket relay |
Terminate TLS at your ingress. The console relay needs WebSocket upgrades to pass through.
Database
Section titled “Database”Any PostgreSQL will do. Production uses an operator-managed cluster, but a managed database service or a single well-backed-up instance is fine.
Back it up. It holds intent, allocations and the audit trail. Losing it does not stop your virtual machines — they keep running, because nothing depends on the panel — but you lose the record of who owns what, and rebuilding that from tags is work you do not want to do under pressure.
Schema migrations
Section titled “Schema migrations”Migrations are numbered SQL files applied in order, tracked in a table, and forward only: an applied file is never edited; a new numbered file is added instead. Each migration and its bookkeeping row commit in one transaction, so a half-applied migration is not a state the database can reach.
Verify
Section titled “Verify”kubectl -n zone9 get podskubectl -n zone9 logs job/migrate --tail=20curl -sI https://<your-host> | head -1Then sign in. With ZONE9_PLATFORM_ADMINS set, your identity has platform operator
rights and can create organisations and address pools.
Upgrading
Section titled “Upgrading”Same script. Image tags include the timestamp and commit, so you can see what is running and roll back to a previous tag without rebuilding.
Order for a release that changes both the panel and the agent: publish the agent release first, let regions pick it up, then deploy the panel. An old agent may not understand a new field; the reverse is designed to be safe.