Skip to main content Skip to content

Deployments

What a ZopDay deployment is, how revisions flow through the pipeline, and what survives a restart.

6 min read Last updated: 

A deployment is the catalog row for one shippable thing; a repo + branch + config that ZopDay can roll out repeatedly. A revision is one rollout of that deployment; the specific image hash + Helm release that’s live or pending.

The split is deliberate: catalog data is stable and editable; revision data is append-only and immutable once shipped. Editing a deployment changes future rollouts; it doesn’t change the past.

Deployment canvas for the payment-service production environment showing five app services (billing-svc, notification-svc, audit-logger, search-indexer, api-gateway) with source repo, branch, port, and health

A deployment environment canvas; each service with its git source, port, replica count, and health status.

Catalog vs runtime

LayerServiceWhat it owns
CatalogConfig serviceprojects, environments, deployments, deployment_spaces. Stable rows you create, edit, and delete.
RuntimeDeployer servicedeployment_revisions (versioned rollouts + work queue), deployment_events (append-only timeline), service_connections (edges between deployments).

The deployer is stateless with respect to the catalog: it reads catalog data from the config service and never keeps its own copy.

Deployment fields

FieldNotes
env_idThe parent environment. Defines the space, namespace, and registry.
repo_urlGit URL of the source repo (HTTPS or SSH).
branchThe branch to track. ZopDay only deploys this branch on auto-triggers; manual deploys can target any commit.
current_revision_idPointer to the latest successfully shipped revision. Updated atomically when a revision goes active.
statusidle / building / deploying / active / failed / dlq; derived from the latest revision.
last_deploy_atTimestamp of the last successful deploy. Drives the project list’s “last deploy” column.
canvas_position{x, y} for the architecture canvas.

Infrastructure fields (which space, which namespace, which registry) hydrate from the parent environment via JOIN. They are not stored on the deployment itself; change the environment and every deployment inside it picks up the change.

Revisions and the work queue

Each revision doubles as a work-queue item, moving through these states:

Terminal window
pending → claimed → build_dispatched → pending → claimed → deploying → active | failed | dlq

Keeping the version metadata and the work-queue state together avoids reconciliation issues if a worker restarts mid-deploy.

Revision columnPurpose
statusThe state machine above
delivery_idWebhook idempotency. Replays of the same GitHub delivery are ignored and no-op.
payload_snapshot JSONThe payload that triggered this revision; repo state, env, registry. Frozen at claim time so a mid-flight env edit doesn’t surprise the build.
stage_markerWhich pipeline stage the worker last completed. Used for resume on pod restart.
retry_countHow many times this revision has been retried. Caps at 3 before DLQ.
claimed_atWhen the current worker pod last claimed this row. Drives stale-lock recovery.
config_onlyTrue if this revision is a config-only redeploy (no rebuild).
snapshot_atWhen payload_snapshot was frozen.

The pipeline (per revision)

Terminal window
Resolve → Clone → Detect → dispatch build → callback → Deploy → WatchRollout

Each stage emits an append-only event at its start and on success or failure. This is the same pipeline described in Pipelines and run in the Quickstart, viewed here per revision.

  1. Resolve

    Read the parent deployment + environment + space via configclient. Verify the space is reachable.

  2. Clone

    Git clone the repo at the configured branch. Fetches the git credentials over the same path ZopDay uses for all repo access.

  3. Detect

    Railpack walks the cloned repo to detect language, runtime version, and build approach. If a Dockerfile is present, it’s respected as-is; Railpack only takes over when there isn’t one.

  4. Dispatch build

    ZopDay dispatches the build to GitHub Actions, which handles the build entirely (not an in-cluster Kaniko build). After dispatch the revision is parked in a build-dispatched state so it isn’t re-picked before the build callback resumes it.

  5. Callback (from builder)

    The builder streams log chunks and terminal status back to ZopDay over a signed callback. On success, the deploy stage is queued.

  6. Deploy

    Render Helm chart with merged values (base + environment + branch overrides). Apply via the kubeauth-minted kubeconfig. Image hash pushed to the registry resolved from the environment (or the cloud default if no registry integration).

  7. WatchRollout

    Poll the Helm release status until the rollout reaches steady state. Health gates run here; pod readiness, probes, custom SLOs. See Pipelines for the gate detail.

On success → status='active', current_revision_id updated atomically. On any stage failure → retry up to 3 times, then the revision moves to the DLQ.

Webhook idempotency

Each GitHub delivery ID is recorded on the revision. Replays of the same delivery are ignored: no duplicate revisions, no duplicate builds, even if GitHub re-fires the webhook hours later after a transient delivery error.

Rollback creates a revision

Rollback is not an edit to an existing revision; it’s the creation of a new revision that mirrors the target. The image hash on the new revision is the one that was already on the cluster, so no rebuild fires.

This keeps the revision history readable: every change to what’s running is its own row, with its own actor and timestamp.

See Rollbacks for the full rollback contract.

Teardown

Deleting a deployment triggers a teardown before the catalog row is soft-deleted. Teardown is:

  • helm uninstall of the release on the bound space’s namespace
  • Revision and event cleanup
  • Idempotent; re-runs return already_clean=true

If the cluster is unreachable when teardown fires, the catalog row remains in a pending_teardown state. Re-trigger from the UI once the cluster recovers; the teardown is idempotent so a partial first attempt won’t double-uninstall.

Editing a deployment

Common edits and their effects:

EditTriggers new revision?
repo_url changeNo. Next manual or webhook trigger creates a new revision against the new repo.
branch changeNo. Same as above.
Helm values change via PATCH /deployments/{id}/configsYes: config-only revision. config_only=true on the new row. No rebuild fires; the same image hash is applied with the new values.
canvas_position changeNo. Visual only.
Environment edits that change space/namespace/registryNo. Existing deployments flagged with divergence; re-deploy explicitly to land.

Stateless w.r.t. credentials

Worth re-emphasising: the deploy pod holds zero standing credentials on target clusters. The kubeconfig is minted per-deploy via kubeauth, scoped to ~13–45 minutes depending on cloud, and discarded when the pipeline stage completes. There is no long-lived service account, no in-cluster agent, no kubeconfig file on disk past the deploy pod’s lifetime.

This matters when teams ask “what does ZopDay have access to on our cluster right now?”; the answer is: nothing standing. Only what was minted for the most recent active deploy, and that token has already expired.

Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001 · zero-trust· 30% average cloud cost cut· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001 · zero-trust· 30% average cloud cost cut· 4 platforms · 1 console·