Deployments
A deployment is a long-lived application instance ZopNight runs in your Kubernetes cluster. It lives inside an environment, is built from a connected git repository, and is rolled out through a Helm release that ZopNight manages on your behalf.
Mental model
- Deployment — a long-lived app instance in one environment (e.g.
checkoutinprod-us-east). - Revision — an immutable image + config snapshot. Each push, config change, or rollback creates a new revision.
- Rollback — revert to a previous revision; the rollback itself is recorded as a new revision so history is append-only.
Which service serves which call
Since the catalog/runtime split (Phase 3a–3c, May 2026), ownership of the deployment surface is split across two services: config owns the long-lived catalog row, and deployer owns the per-deploy runtime (builds, revisions, events, Helm rollout). Both services sit behind the gateway at zopnight.com/api — the split is invisible to API clients, but the table below explains which service handles what, in case you're tracing logs or wiring fine-grained IAM.
| Concern | Service | Surface |
|---|---|---|
| Catalog row (name, source, configs, status, current revision, canvas position) | config | /deployments, /deployments/{id}, /environments/{envID}/deployments |
| Configs (env vars, secrets, port, resources) | config | PATCH /deployments/{id}/configs |
| Revisions, rollback, analyze | deployer | /deployments/{id}/revisions, /rollback, /analyze |
| Event timeline + LoadBalancer status | deployer | /deployments/{id}/events, /loadbalancer |
| Status & current-revision writeback | deployer → config | gRPC UpdateDeploymentStatus, SetCurrentRevision |
| Teardown (helm uninstall + revision/event cleanup) | config → deployer | gRPC Teardown(orgID, deploymentID) |
Lifecycle
- Client calls
POST /deployments. Config writes the catalog row plus a revision-#1 placeholder, then enqueues a job into the deployer's MySQLjobsqueue via deployer gRPC. - The deployer's worker pool claims the job, clones the repo, runs stack detection, and dispatches a GitHub Actions build.
- On build completion, the deployer mints a per-deploy ephemeral kubeconfig from the cloud account credentials and applies a Helm release to the target namespace on the deployment's deployment space.
- Every stage boundary appends an event to
deployment_events(deployer). Status changes are written back to config via gRPC so the catalog row always reflects the live state. - Subsequent pushes (auto-deploy) or config changes create new revisions; the deployer reconciles them as rolling updates with zero downtime.
DELETE /deployments/{id}on config calls the deployer's inboundTeardowngRPC (idempotent — re-runs returnalready_clean=true) before soft-deleting the catalog row.
Create a Deployment
Two equivalent forms: a flat path for integrations and CI scripts, and an env-nested form for the canvas (which already has the environment in context). Both land on config.
/deploymentsCreate a deployment. Writes the catalog row and revision #1 placeholder, then enqueues a build job. (config)
curl -X POST https://zopnight.com/api/deployments \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "checkout",
"envId": "env_abc",
"sourceType": "git",
"repoUrl": "https://github.com/acme/checkout",
"branch": "main",
"integrationId": "int_github_001",
"autoDeploy": true,
"configs": {
"port": 8080,
"envVars": { "DB_HOST": "checkout-db.zopnight.internal", "LOG_LEVEL": "info" },
"secretRefs": ["secret://checkout-db-password"]
}
}'/environments/{envID}/deploymentsEnv-nested create. Same payload, used by the canvas. (config)
List & Get
/deploymentsList all deployments visible to the caller across every environment. (config)
/environments/{envID}/deploymentsList deployments in one environment. (config)
/deployments/{id}Get a deployment, hydrated with current revision, configs, and the env's inherited infra (space, namespace, registry). (config)
{
"data": {
"id": "dep_xyz",
"name": "checkout",
"orgId": "org_acme",
"envId": "env_abc",
"sourceType": "git",
"repoUrl": "https://github.com/acme/checkout",
"branch": "main",
"integrationId": "int_github_001",
"autoDeploy": true,
"spaceId": "spc_abc",
"namespace": "checkout-prod",
"registryUrl": "123456789012.dkr.ecr.us-east-1.amazonaws.com/checkout",
"currentRevisionId": "rev_005",
"status": "active",
"configs": "{\"port\":8080,\"envVars\":{\"LOG_LEVEL\":\"info\"}}",
"canvasPosition": { "x": 320, "y": 180 },
"lastDeployAt": "2026-04-29T11:30:00Z",
"createdAt": "2026-04-15T08:00:00Z",
"updatedAt": "2026-04-29T11:30:00Z"
}
}/deployments/{id}Tear down a deployment. Config calls the deployer's Teardown gRPC (helm uninstall + revision/event cleanup, idempotent) before soft-deleting the catalog row. (config)
Update Configs
Env-var, secret, port, replica, and resource changes go through PATCH /deployments/{id}/configs on config. The deployer picks the new shape up on the next revision and applies it as a rolling update — the catalog stores configs as opaque JSON; the deployer parses the typed shape (port, replicas, liveness, resources, expose, rootPath) at apply time.
/deployments/{id}/configsPatch deployment configs. (config)
curl -X PATCH https://zopnight.com/api/deployments/dep_xyz/configs \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"configs": {
"envVars": { "LOG_LEVEL": "debug" },
"port": 8080
}
}'Canvas Position
/deployments/{id}/canvas-positionPersist the (x, y) of a deployment card on the canvas. Body: { x, y }, both finite numbers. (config)
Analyze a Deployment
/deployments/{id}/analyzeRe-run deploy-readiness checks against the current source + configs. Returns warnings (missing health probe, missing resource requests, etc.) without creating a revision. (deployer)
Revisions
Every image or config change is captured as a revision. Revisions are immutable. Push-triggered revisions carry the GitHub delivery_id as their idempotency key (UUID v5 of the delivery), so webhook replays coalesce into a no-op via INSERT IGNORE — see Webhooks for the full dedup story.
/deployments/{id}/revisionsCreate a new revision (typically from CI after a build completes). (deployer)
curl -X POST https://zopnight.com/api/deployments/dep_xyz/revisions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"image": "registry.zopnight.com/acme/checkout:v1.4.3",
"buildID": "build_789",
"note": "Hotfix for retry loop"
}'/deployments/{id}/revisionsList revisions for a deployment. (deployer)
/deployments/{id}/revisions/{revID}Get a specific revision. (deployer)
Rollback
/deployments/{id}/rollbackRoll back to a prior revision. The deployer clones the target revision into a new revision and applies it. (deployer)
curl -X POST https://zopnight.com/api/deployments/dep_xyz/rollback \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "toRevisionID": "rev_004" }'Events Timeline
/deployments/{id}/eventsAppend-only chronological list of deploy stage events. Used by the canvas drawer's activity feed. (deployer)
{
"data": [
{ "id": "ev_1", "stage": "building", "status": "started", "createdAt": "2026-04-29T11:00:00Z" },
{ "id": "ev_2", "stage": "building", "status": "succeeded", "meta": { "imageRef": "..." }, "createdAt": "2026-04-29T11:04:30Z" },
{ "id": "ev_3", "stage": "deploying", "status": "started", "createdAt": "2026-04-29T11:04:35Z" },
{ "id": "ev_4", "stage": "rolling_out", "status": "succeeded", "meta": { "ready": 3, "desired": 3 }, "createdAt": "2026-04-29T11:05:10Z" },
{ "id": "ev_5", "stage": "active", "status": "succeeded", "meta": { "address": "a-1234.elb..." }, "createdAt": "2026-04-29T11:06:50Z" }
]
}LoadBalancer Status
For deployments that expose ports as http or tcp, ZopNight provisions a cloud LoadBalancer through the cluster's ingress class. The endpoint below is what the UI polls during the post-deploy "Waiting for public address…" state.
/deployments/{id}/loadbalancerGet LoadBalancer status (assigned address, listeners, TLS certificate). (deployer)
{
"data": {
"status": "ready",
"address": "a-12345.elb.us-east-1.amazonaws.com",
"ports": [
{ "port": 80, "protocol": "HTTP" },
{ "port": 443, "protocol": "HTTPS" }
],
"tls": { "issuer": "letsencrypt-prod", "expiresAt": "2026-07-29T00:00:00Z" }
}
}See Builds for how images are produced, Projects & Environments for how deployments are grouped, Deployment Spaces for the cluster they target, and Service Connections for wiring deployments together on the canvas.