Skip to main content
Skip to main content

Deployment Lifecycle

Zerops build & deploy pipeline: temporary build container runs prepareCommands + buildCommands, uploads artifact via deployFiles, then deploys to runtime containers with optional readiness checks. Default is zero-downtime rolling deployment. Build has a 60-minute timeout. The pipeline emits events trackable via zerops_events.


Build Phase​

Build Container Lifecycle​

The build container is temporary -- created on demand, destroyed after completion or failure.

Step-by-step execution order:

  1. Container creation -- base environment from build.base (<os>/<tech>@<ver>; the prefix selects Alpine or Ubuntu, a bare value resolves to Alpine)
  2. Source code download -- from GitHub, GitLab, or zcli push to /var/www
  3. Cache restoration -- cached files moved to /build/source (no-clobber, source wins)
  4. prepareCommands -- install additional tools/packages (skipped if cache valid)
  5. buildCommands -- compile, bundle, package your application
  6. Artifact upload -- files matching deployFiles stored in internal Zerops storage
  7. Cache preservation -- files matching cache: moved to /build/cache
  8. Container deletion -- build container destroyed regardless of outcome

Build Limits​

  • Resources: CPU 1-5 cores, RAM 8 GB fixed, Disk 1-100 GB (auto-scales, not charged separately)
  • Timeout: 60 minutes hard limit -- no retry, must trigger new pipeline
  • Cancellation: only available before build finishes -- once artifact uploaded, deploy cannot be cancelled

Command Exit Codes​

  • Exit 0 -- success, next command runs
  • Non-zero -- build cancelled, check build log for errors
  • YAML list items = separate shells; use | block scalar for single shell (shared env/cwd)

Runtime Prepare Phase (Optional)​

Runs after build, before deploy when run.prepareCommands is defined. Creates a custom runtime image with additional system packages.

Execution order:

  1. Create prepare container from run.os + run.base
  2. Copy files from build.addToRunPrepare to /home/zerops/
  3. Execute run.prepareCommands in order
  4. Snapshot as custom runtime image
  5. Image cached for future deploys

Cache invalidation triggers:

  • Change to run.os, run.base, or run.prepareCommands
  • Change to build.addToRunPrepare file contents
  • Manual invalidation via GUI

DO NOT include application code in the runtime prepare image. Deploy files arrive separately.


Deploy Phase​

First Deploy​

For each new container (count based on auto scaling settings):

  1. Install runtime -- base image or custom runtime image
  2. Download artifact -- from internal storage to /var/www
  3. initCommands -- optional per-container initialization (runs every start/restart)
  4. start command -- launch application
  5. Readiness check -- if configured, gates traffic routing
  6. Container active -- receives incoming requests

Multiple containers deploy in parallel.

Subsequent Deploys (Rolling Deployment)​

Default behavior (temporaryShutdown: false):

  1. New containers started (same count as existing)
  2. New containers go through steps 1-6 above
  3. Both old and new versions run simultaneously during transition
  4. Old containers removed from load balancer (stop receiving new requests)
  5. Old container processes terminated
  6. Old containers deleted

Deploy strategy & checks​

temporaryShutdown (in the deploy block) controls cutover order: false (default) starts new containers before removing old ones (zero-downtime); true stops old before new (downtime, use only when two versions cannot coexist — exclusive DB migrations, singleton locks). The readiness check gates traffic to the new container during a deploy; the health check monitors the live app continuously. For check behavior, params, the httpGet/exec timeouts, and the production pattern, see zerops_knowledge uri="zerops://guides/readiness-health-checks".


Event Timeline (zerops_events)​

Typical pipeline events in chronological order:

  1. stack.build process RUNNING -- build container created, pipeline started
  2. stack.build process FINISHED -- build complete, artifact uploaded
  3. appVersion build event ACTIVE -- the new version is deployed and running (this is the terminal success state, NOT "launching")
  4. Service status returns to RUNNING -- all containers active, deploy complete

Terminal states:

  • Build done: stack.build process status = FINISHED
  • Build failed: stack.build process status = FAILED
  • Deploy done: service containers all active, new appVersion is ACTIVE

DO NOT keep polling after stack.build shows FINISHED -- that means the build itself is complete. The ACTIVE status on appVersion means deployed and running.


Build Event Polling Checklist​

When monitoring a build/deploy via zerops_events:

  1. Filter by service: always use serviceHostname parameter to avoid stale events from other services or previous iterations
  2. Check stack.build process: look for status FINISHED (success) or FAILED (error). Once FINISHED, the build is done — stop polling build status
  3. Check appVersion build event: status ACTIVE means deployed and running. This confirms deploy completion
  4. Do NOT confuse build events: stack.build process RUNNING = build in progress. appVersion ACTIVE = already deployed. These are different events
  5. Timeout guidance: builds have a 60-minute hard limit. If no FINISHED after ~5 minutes for typical apps, check build logs via zerops_logs
  6. Stale events: project-level events may include old builds from previous deploys. Always verify the event timestamp and service match

Application Versions​

Zerops keeps 10 most recent versions. Older auto-deleted. Any archived version can be restored -- activates that version, archives current, restores env vars to their state when that version was last active.

Gotchas​

  1. initCommands run on EVERY container start -- including restarts and horizontal scaling, not just deploys
  2. initCommands failures DO fail the deploy -- run.initCommands run during runtime-prepare, BEFORE the start command, and are deploy-gating: a non-zero exit aborts the deploy. The platform emits RUN.INIT COMMANDS FINISHED WITH ERROR in the runtime log and surfaces the failed command + exit code on the stack.build process (commandExec / "init command failed"). The new appVersion goes to DEPLOY_FAILED and is never activated; the start command never runs and the previously-active version keeps serving. Diagnose via appVersion.status (DEPLOY_FAILED) + activationDate (null), NOT the service status — the service stays ACTIVE on the old version, so service-status alone reads as "fine". Keep init commands idempotent and exit 0.

SSHFS Mount and Deploy Interaction​

When using SSHFS (zerops_mount) for dev workflows, deploy replaces the container. This has important consequences:

  1. After deploy, run container only has deployFiles content. All other files (including zerops.yml if not in deployFiles) are gone — persistent data belongs in a storage service. Use deployFiles: [.] for dev services to ensure zerops.yml and source files survive the deploy cycle.
  2. SSHFS mount auto-reconnects only while the service is running. Usually no remount is needed, but if the mount goes stale after a deploy (stat/ls returns empty, writes hang), recover it explicitly with zerops_mount action="mount". A stopped service has no live mount until it starts again.
  3. zerops.yml must be in deployFiles for dev self-deploy lifecycle. Without it, subsequent deploys from the container fail because zerops.yml is missing.

Three kinds of "mount" (disambiguation):

  • zerops_mount -- SSHFS tool, mounts service /var/www locally for development. This is a dev workflow tool.
  • SeaweedFS mount -- weed mount (or zsc shared-storage mount <hostname>) run as a run.startCommands entry in zerops.yml, mounts a SeaweedFS service's filer at a directory of your choice. There is no mount field in zerops.yml (a run.mount is silently ignored) and the import-level mount: of the deprecated Shared Storage is rejected. These are completely unrelated features.
  • Local Storage volume -- platform feature, mounts a Local Storage service's persistent volume via the run.volume field in zerops.yml (volume: {hostname, mountPath, readOnly}). The volume survives deploys: new containers get the same volume attached at the configured mountPath.