Job kinds
The deploy-queue job kinds the control plane enqueues and the agent claims — what each does, the JobSpec fields it uses, how executeJob runs it, and how secrets are delivered.
Job kinds
The deploy queue is how the control plane makes a Node do work without ever opening a socket to it. The control plane enqueues a job; the agent daemon claims pending jobs on its heartbeat, runs OUR build → run → route engine, and reports state + streams logs back. The agent is pull-only — no inbound connection to the box.
This page enumerates the real job kinds, the JobSpec fields each uses, and how
the agent executes them. It is grounded in three files:
- Control plane:
control-plane/web/lib/platform/jobs.ts— theJobKindunion, theJobSpecinterface,validateJobSpec, and the enqueue/claim/cancel store. - Agent client:
agent/internal/jobs/jobs.go— theSpec+Jobtypes and the/v1/jobs/*client. - Agent executor:
agent/internal/cli/cli.go—runJoband theexecuteJobswitch.
For the daemon loop that drives this, see
CLI reference §hostssh agent.
The queue lifecycle
A job moves through the states defined in jobs.ts (JobState):
pending → claimed → running → succeeded | failed | cancelled.
- Enqueue. The control plane calls
enqueueJob(licenseKey, spec, { kind, fingerprint, deploymentId }).validateJobSpecForKindruns first — a bad spec throws at enqueue time instead of failing mid-deploy. A job may be pinned to one hostfingerprintor left unpinned for any Node on the license. - Claim. The daemon polls
POST /v1/jobs/claim(jobs.Claim) every ~15s.claimJobatomically claims the oldest pending job (or reclaims a stale claim past theLEASE_MS= 5-minute lease) usingFOR UPDATE SKIP LOCKED. - Run.
runJobreportsrunning, then callsexecuteJobunder a 30-minute context (jobTimeout). Output is batched toPOST /v1/jobs/{id}/logs(AppendLogs); streaming logs also keeps the lease fresh. - Cancel. While running, the agent polls
GET /v1/jobs/{id}/cancel(CheckCancel) every ~3s. An operator cancel setscancel_requested; on observing it the agent cancels the context (killing the build/run subprocess) and reportscancelled(distinct fromfailed). - Report.
ReportStatuswrites the terminal state viaPOST /v1/jobs/{id}/status. Both status and log writes carry the agent'sfingerprint, which fences the write to the claim owner so a late report from a dead/reclaimed agent can't clobber a re-run.
All jobs run one at a time, serially in the daemon's job goroutine — no overlapping deploys.
The kinds
The control-plane JobKind union (jobs.ts) is the authoritative set of kinds the
queue enqueues:
deploy · redeploy · stop · remove · db · restore · firewall ·
prune · expose · unexpose
The agent's executeJob switch (cli.go) matches these, and additionally accepts a
few internal aliases (provision-db → db, load-snapshot → restore,
harden → firewall). A kind the build doesn't handle returns
job kind %q is not supported in this build.
| Kind | What the agent does | Executor call |
|---|---|---|
deploy / redeploy | Build → run → route the app. | deploy.New().Deploy(...) |
db (provision-db) | Provision a managed database container on this Node. | database.Provision(...) |
restore (load-snapshot) | Load a registered snapshot onto this node via the clone engine, rewriting the IP. | engine.RestoreTo(...) |
firewall (harden) | Apply the host firewall posture. | firewall.Apply(...) |
stop | Stop the app's container. | runtime.NewDocker().Stop(...) |
remove | Remove the app's container. | runtime.NewDocker().Remove(...) |
prune | Reclaim Docker disk (retention + dangling + build-cache GC). | reaper.Run / reaper.Preview |
expose | Publish a Node-local port to the internet via a cloudflared tunnel. | expose.Expose(...) |
unexpose | Withdraw a public exposure. | expose.Unexpose(...) |
Recovery-view honesty note.
jobs.tsalso reads rows of kindcloneandmigrate(inlistRecoveryJobs) to render the Recovery page, and those kinds appear in theRecoveryKindtype. But they are not in the enqueuingJobKindunion and not in the agent'sexecuteJobswitch — this build neither enqueues nor executesclone/migratejobs. Treat them as reserved.
deploy / redeploy
Build (HostPack/Dockerfile) or run a prebuilt image, then route it through Traefik.
redeploy is identical — same executor arm. The workhorse kind.
JobSpec fields used (mapped 1:1 into deploy.Spec in executeJob):
| Field | Meaning |
|---|---|
app | App name (required; validated). |
source | Repo path / git URL / image ref to build or run. |
ref | Git branch/tag (default branch if omitted). |
domain, port, readinessPath | Public routing + health probe. |
network | Docker network shared with the proxy (default hostssh). |
builder | hostpack or image (validated); Dockerfile build is auto-detected/selectable in the agent. |
imageTag, imageLimitMb | Image tag; fail-before-run size cap (≤ MAX_IMAGE_BUDGET_MB). |
migrateCmd | Release-phase command run before the app starts. |
verifyCmd | Drift gate — pre-flight check after migrate; non-zero exit fails the deploy. |
command | Run instead of the image's default CMD (e.g. a worker). |
slot, cpus, memoryMb, pidsLimit | Slot binding + resolved resource caps (from slotCaps(size)); the agent applies docker --cpus/--memory/--pids-limit + a hostssh.slot label. |
env | Plain (non-secret) env KEY=VALUE map. |
generateSecrets, secretSources | Secret keys/store names only — see Secret delivery. |
db (provision-db)
One-click managed database. The database name is the job's app field;
engine/version/network come from the spec. Postgres provisions full pgvector by
default.
JobSpec fields used: app (DB name), engine (postgres | mariadb |
redis, validated; defaults to postgres), version (image tag override),
network (default hostssh). Executes database.Provision(...).
restore (load-snapshot)
The slot-snapshot load verb: restore a registered snapshot onto this node
via the proven clone engine, streaming progress to the job log. Default is a
non-destructive clone (engine restores to /var/restore + a clone-tagged PG
container). The destructive --rewrite-coolify is opt-in; --activate-wg is
deliberately not exposed via a job (replacing a live source box is an
out-of-band decision).
JobSpec fields used (assembled into engine.RestoreTo flags): snapshot
(restic snapshot id or latest), tag (capture tag), oldIp (source IP,
rewritten away), newIp (this node's IP, rewritten in), pgImage (postgres image
for the restored DB), rewriteCoolify (off by default). app is a human label for
the load.
firewall (harden)
One-click hardening applied from the dashboard.
JobSpec fields used: sshPort (SSH port to keep open; 0 → the agent uses its
default), allowTcp (inbound TCP; defaults to [80, 443] when empty), allowUdp
(e.g. 51820 for WireGuard), firewallReset (reset rules before applying).
Executes firewall.Apply(...).
stop / remove
Container lifecycle. Both take only app. stop → runtime.NewDocker().Stop(app);
remove → runtime.NewDocker().Remove(app).
prune
Docker disk hygiene: keep last-N app images, drop dangling images, bound the build cache. Volumes are never pruned.
JobSpec fields used: keepLast, builderGb, builderUntilHours (each override
the env-derived reaper.PolicyFromEnv() defaults when > 0), dryRun (→
reaper.Preview, else reaper.Run). The control plane surfaces recent prune jobs +
reclaimed-GB in the Fleet drawer (listPruneJobs, parseReclaimedGb).
expose / unexpose
Publish (or withdraw) a Node-local port to the internet through the operator's named
cloudflared tunnel (HOSTSSH_CF_TUNNEL, read from the agent's env).
JobSpec fields used: targetPort (the local port, required + validated 1–65535
for expose), publicHostname (validated as a real DNS hostname before it reaches
an agent), exposureId (ties the job back to the persisted exposures row the agent's
report updates). Executes expose.Expose / expose.Unexpose.
The JobSpec
JobSpec (TS in jobs.ts) and Spec (Go in jobs.go) are the same shape on both
sides of the wire. app is the only universally required field; the rest are
optional and interpreted per kind. validateJobSpecForKind enforces the contract at
enqueue time — a summary of what it checks:
| Field | Rule |
|---|---|
app | Required, non-empty, ≤ 200 chars. |
port, targetPort | Integer 1–65535 when present (targetPort required for expose). |
readinessPath | Must start with /, no whitespace/control chars, ≤ 200 chars, valid HTTP path. |
builder | hostpack or image; image needs an imageTag or source. |
imageLimitMb | Positive integer ≤ MAX_IMAGE_BUDGET_MB. |
network | ^[A-Za-z0-9._-]{1,128}$. |
engine | postgres | mariadb | redis. |
publicHostname | Valid multi-label DNS hostname (lowercase, no shell/whitespace) for expose/unexpose. |
keepLast / builderGb / builderUntilHours | Nonnegative integers ≤ 100 / 500 / 8760. |
generateSecrets | Array of ≤ 100 valid env keys (^[A-Za-z_][A-Za-z0-9_]{0,255}$). |
secretSources | Array of ≤ 20 valid store names (^[a-z0-9][a-z0-9-]{0,62}$). |
Secret delivery
Secret values never travel in the JobSpec (which is persisted in the jobs
table) — only keys and store names do. There are three delivery fields, mirrored in
jobs.ts, jobs.go, and consumed by executeJob's deploy arm:
| Field | Carries | Where it lives |
|---|---|---|
spec.generateSecrets | Secret env keys the agent mints on-host and injects (template secrets marked generate:true). Names only. | In the JobSpec — safe, no values. |
spec.secretSources | Other on-host secret store names to fold into this app's env (e.g. a managed DB's name so the app gets its DB creds). Names only. | In the JobSpec — safe, no values. |
job.secrets | User-entered secret values, delivered with the claim response — decrypted from at-rest ciphertext, carried over pinned TLS, and not persisted in the jobs table. | Attached per claim-response (Job.Secrets in jobs.go), passed as DeliverSecrets to deploy.Spec. |
The deploy executor arm folds all three together: GenerateSecrets (mint on host),
SecretSources (fold in linked stores), and DeliverSecrets (the values that rode
the claim). Because a DB dump of the jobs table never contains plaintext, a leaked
queue store is not leaked secrets.
For the full custody model and the generate / sealed / linked delivery paths, see Environment variables & secrets.
See also: CLI reference · Agent protocol · API & CLI · Environment variables & secrets