Documentation

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. checkout in prod-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.

ConcernServiceSurface
Catalog row (name, source, configs, status, current revision, canvas position)config/deployments, /deployments/{id}, /environments/{envID}/deployments
Configs (env vars, secrets, port, resources)configPATCH /deployments/{id}/configs
Revisions, rollback, analyzedeployer/deployments/{id}/revisions, /rollback, /analyze
Event timeline + LoadBalancer statusdeployer/deployments/{id}/events, /loadbalancer
Status & current-revision writebackdeployer → configgRPC UpdateDeploymentStatus, SetCurrentRevision
Teardown (helm uninstall + revision/event cleanup)config → deployergRPC Teardown(orgID, deploymentID)

Lifecycle

  1. Client calls POST /deployments. Config writes the catalog row plus a revision-#1 placeholder, then enqueues a job into the deployer's MySQL jobs queue via deployer gRPC.
  2. The deployer's worker pool claims the job, clones the repo, runs stack detection, and dispatches a GitHub Actions build.
  3. 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.
  4. 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.
  5. Subsequent pushes (auto-deploy) or config changes create new revisions; the deployer reconciles them as rolling updates with zero downtime.
  6. DELETE /deployments/{id} on config calls the deployer's inbound Teardown gRPC (idempotent — re-runs return already_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.

POST
/deployments

Create a deployment. Writes the catalog row and revision #1 placeholder, then enqueues a build job. (config)

Requestbash
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"]
    }
  }'
POST
/environments/{envID}/deployments

Env-nested create. Same payload, used by the canvas. (config)

List & Get

GET
/deployments

List all deployments visible to the caller across every environment. (config)

GET
/environments/{envID}/deployments

List deployments in one environment. (config)

GET
/deployments/{id}

Get a deployment, hydrated with current revision, configs, and the env's inherited infra (space, namespace, registry). (config)

Responsejson
{
  "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"
  }
}
DELETE
/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.

PATCH
/deployments/{id}/configs

Patch deployment configs. (config)

Requestbash
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

PATCH
/deployments/{id}/canvas-position

Persist the (x, y) of a deployment card on the canvas. Body: { x, y }, both finite numbers. (config)

Analyze a Deployment

POST
/deployments/{id}/analyze

Re-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.

POST
/deployments/{id}/revisions

Create a new revision (typically from CI after a build completes). (deployer)

Requestbash
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"
  }'
GET
/deployments/{id}/revisions

List revisions for a deployment. (deployer)

GET
/deployments/{id}/revisions/{revID}

Get a specific revision. (deployer)

Rollback

POST
/deployments/{id}/rollback

Roll back to a prior revision. The deployer clones the target revision into a new revision and applies it. (deployer)

Requestbash
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

GET
/deployments/{id}/events

Append-only chronological list of deploy stage events. Used by the canvas drawer's activity feed. (deployer)

Responsejson
{
  "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.

GET
/deployments/{id}/loadbalancer

Get LoadBalancer status (assigned address, listeners, TLS certificate). (deployer)

Responsejson
{
  "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.