Deployment — REST API surface
Minimal example
Section titled “Minimal example”{ "tenantId": "a74adad8-0c9e-4a6f-9f2a-3f5a1d2b7c40", "name": "web", "image": "nginx:1.27-alpine", "replicas": 1}Deployment · testdata/corpus/types/deployment-minimal.jsonKeys this surface accepts that the YAML manifest does not
Section titled “Keys this surface accepts that the YAML manifest does not”These are settable through the REST API and the key-value store and are refused in a YAML manifest. The list is derived, not maintained by hand.
canarycapAddcapDropcommanddnsdnsSearchentrypointexposeextraHostshostnameimagePullSecretloggingnetworkAliasesnodeIdportsprivilegedprobereadOnlyrestartPolicysecretsVersionsecurityOptstackstopGracePeriodtenantIduserworkingDir
Deployment resource
Section titled “Deployment resource”Generated from Deployment. Hand edits to this table are overwritten on the next build — change the Go doc comment, or the generator.
| Field | Type | Required | Default | Mutability | Since |
|---|---|---|---|---|---|
canary |
CanaryConfig | no | — | in-place | 0.1.0 |
capAdd |
string[] | no | — | recreate | 0.1.0 |
capDrop |
string[] | no | — | recreate | 0.1.0 |
command |
string[] | no | — | recreate | 0.1.0 |
createdAt |
string | no | — | in-place | 0.1.0 |
dependsOn |
DependencySpec[] | no | — | in-place | 0.1.0 |
dns |
string[] | no | — | recreate | 0.1.0 |
dnsSearch |
string[] | no | — | recreate | 0.1.0 |
effectiveUpdateStrategy |
string | no | — | in-place | 0.47.0 |
entrypoint |
string[] | no | — | recreate | 0.1.0 |
envFile |
string[] | no | — | recreate | 0.1.0 |
environment |
object | no | — | recreate | 0.1.0 |
expose |
integer[] | no | — | recreate | 0.1.0 |
extraHosts |
string[] | no | — | recreate | 0.1.0 |
healthCheck |
HealthCheck | no | — | recreate | 0.1.0 |
hostname |
string | no | — | recreate | 0.1.0 |
image |
string | yes | — | recreate | 0.1.0 |
imagePullSecret |
string | no | — | recreate | 0.1.0 |
ingress |
IngressConfig | no | — | recreate | 0.1.0 |
init |
boolean | no | — | recreate | 0.21.1 |
labels |
object | no | — | recreate | 0.1.0 |
logging |
LoggingConfig | no | — | recreate | 0.1.0 |
name |
string | yes | — | recreate | 0.1.0 |
networkAliases |
string[] | no | — | recreate | 0.1.0 |
networks |
string[] | no | — | recreate | 0.1.0 |
nodeDistribution |
object | no | — | in-place | 0.1.0 |
nodeId |
string | no | — | in-place | 0.1.0 |
onNodeFailure |
string | no | — | in-place | 0.7.6 |
ports |
PortMapping[] | no | — | recreate | 0.1.0 |
privileged |
boolean | no | — | recreate | 0.1.0 |
probe |
ProbeSpec | no | — | recreate | 0.5.10 |
readOnly |
boolean | no | — | recreate | 0.1.0 |
replicas |
integer | no | — | recreate | 0.1.0 |
resources |
ResourceSpec | no | — | recreate | 0.1.0 |
restartPolicy |
string | no | — | recreate | 0.1.0 |
restartRetries |
integer | no | — | recreate | 0.6.7 |
revision |
integer | no | — | in-place | 0.1.0 |
scaling |
ScalingPolicy | no | — | in-place | 0.1.0 |
secrets |
SecretRef[] | no | — | recreate | 0.1.0 |
secretsVersion |
integer | no | — | recreate | 0.7.6 |
securityOpt |
string[] | no | — | recreate | 0.1.0 |
stack |
string | no | — | recreate | 0.1.0 |
status |
string | no | — | in-place | 0.1.0 |
statusMessage |
string | no | — | in-place | 0.1.0 |
stopGracePeriod |
integer | no | — | recreate | 0.1.0 |
tenantId |
string | no | — | recreate | 0.1.0 |
ulimits |
Ulimit[] | no | — | recreate | 0.21.1 |
update |
UpdatePolicy | no | — | in-place | 0.1.0 |
updatedAt |
string | no | — | in-place | 0.1.0 |
user |
string | no | — | recreate | 0.1.0 |
volumes |
VolumeMount[] | no | — | recreate | 0.1.0 |
workingDir |
string | no | — | recreate | 0.1.0 |
canary: Canary shifts traffic to the new version in weighted steps and promotes or
aborts on the error-rate and latency thresholds it carries.
capAdd: Add capabilities
capDrop: Drop capabilities
command: Command overrides the image’s CMD. Give it as argv; a single element is
treated as a shell-form command.
createdAt: CreatedAt is when the control plane first accepted this deployment.
Server-owned.
dependsOn: DependsOn is the startup ordering: this deployment waits for each named
deployment or job to reach the stated condition before it starts. It is
orchestration-only and excluded from the spec hash, so editing it never
recreates a running container.
dns: Custom DNS servers
dnsSearch: DNS search domains
effectiveUpdateStrategy: EffectiveUpdateStrategy is READ-ONLY observability (WP12.1 RP-9): the
strategy a spec-hash-drift replacement would actually execute right now,
stamped by the API on GET/LIST and zeroed before every write.
It exists because “no update policy declared” and “recreate declared” are
indistinguishable on the wire today — 0 of 87 dev deployments declare
update, so every one of them silently gets delete-then-create and
nothing says so. An operator cannot choose a safer strategy without first
being able to see which one is in force.
A REAL field, not an out-of-band key, because strictDecodeJSON uses DisallowUnknownFields: a client that GETs a deployment and PUTs it back must not eat a 422 for echoing what we just sent it.
Never persisted and never hashed — see hashExcluded in pkg/spec/completeness_test.go and the zeroing in spec.Hash.
entrypoint: Entrypoint overrides the image’s ENTRYPOINT, as argv.
envFile: EnvFile names files inside the container holding environment variables.
It is refused on this platform: put the values in Environment, or in
Secrets if they are credentials.
environment: Environment are plain environment variables set on every container
instance. Never put a credential here — use Secrets, which resolves from
Vault at dispatch and never stores the value.
expose: Internal ports (container-to-container)
extraHosts: host:IP mappings
healthCheck: HealthCheck is the container-level probe Docker runs. Only an exec probe
produces a real healthcheck: Odysseus never synthesises one from a URL or
a port, so an http or tcp declaration is refused rather than left inert,
because an inert probe silently satisfies the dependsOn and rolling-update
readiness gates it was meant to guard.
hostname: Container hostname
image: Image is the fully-qualified container image reference, including an
explicit tag. Never “:latest” in production: an unpinned tag makes a
rollback impossible to describe.
imagePullSecret: Registry credential name
ingress: Ingress publishes this deployment through the edge router. Setting it
generates the Traefik router, service and middleware labels; leaving it
unset keeps the deployment internal.
init: Init runs Docker’s tiny init (PID 1) inside the container so orphaned
processes are reaped. nil = leave the daemon default; false = an explicit
operator opt-out (e.g. an image that already runs tini and would be
double-wrapped); true = reap.
NormalizeHealthCheck defaults this to TRUE for EVERY Odysseus-managed deployment (OD-HP-4, owner ruling 8 — SUPERSEDES OD-HP-2’s exec-only conditioning). A timed-out exec probe is SIGKILLed and its orphan reparents to PID 1, and PID 1 was measured to be the APPLICATION in 8 of 8 in-scope containers — postgres x4, redis-server, node x2, registry — none of which ever calls wait(). The condition is NOT keyed off healthCheck, for three reasons: an image-declared probe leaks identically and no spec field can express it (athena-mcp is the live case); a subprocess-spawning app leaks with no probe at all; and a code default is inherited by every fresh Odysseus install with zero per-spec edits, which is what the standing SaaS principle requires.
omitempty is NOT a blast-radius control — do not restate it as one.
spec.Hash is json.Marshal over the whole Deployment (pkg/spec/hash.go:62)
and normalisation runs UPSTREAM of it, so under OD-HP-4 Init is non-nil on
every dispatch and omitempty suppresses nothing at hash time: all 119
stored specs’ hashes move once and 31 running containers recreate. It stays
for two narrower reasons: three-state round-tripping on the authoring
surfaces (an omitted init: must not surface as "init": null in API
responses or consul kv get), and pinning the hash property as a unit test
(TestHash_DoesNotMoveForInitlessDeployment, valid as a property of
spec.Hash over a literal with Init == nil).
labels: Labels are extra Docker labels applied to every container instance,
alongside the ones Odysseus sets itself.
logging: Logging selects the Docker logging driver and its options for this
deployment’s containers.
name: Name is the deployment’s identifier, unique within its tenant. It is the
container-name prefix and cannot be changed after creation.
networkAliases: NetworkAliases are extra DNS names the container answers to on its
networks, for service discovery by other containers.
networks: Networks are the Docker networks the container joins. Names are
tenant-prefixed by the reconciler; a backend should stay off
traefik-public and only a frontend should join it.
nodeDistribution: NodeDistribution maps a node name to the number of replicas placed there
when a deployment spans more than one node. Scheduler-assigned and
server-owned.
nodeId: NodeID is the node this deployment’s replicas run on when they all run on
one. Assigned by the scheduler; an operator may pin it through placement.
onNodeFailure: OnNodeFailure (WP6 NF-4, ruling ND5) is the dwell-ending condition for
THIS deployment’s replicas when their node goes Suspect: “reschedule”
(dwell timer alone ends it), “approve” (an operator must approve),
“keep” (never auto-evacuated). Empty defers to the cluster default
(config reconciler.node_default_policy, itself defaulting to “approve”).
CP-side only: it never rides the sv3 wire (pkg/spec/wirespec_build.go
zeroes it before dispatch, same as dependsOn/secretsVersion) and is
excluded from the spec-hash (pkg/spec/hash.go) so editing it never
recreates running containers. A node hosting several deployments takes
the MOST CONSERVATIVE policy among them (keep > approve > reschedule);
see pkg/reconciler/node_policy.go.
ports: Ports publishes container ports on the host. Container-to-container
traffic needs no entry here — use a shared network instead.
privileged: Privileged mode
probe: Probe declares an external blackbox target for monitoring. It is
observability only and never gates a rollout — that is HealthCheck’s job.
readOnly: Read-only root filesystem
replicas: Replicas is how many container instances the reconciler keeps running.
0 is accepted and means “declared but stopped”; above 1000 is refused.
resources: Resources are the CPU and memory limits and requests applied to each
container instance.
restartPolicy: RestartPolicy is what Docker does when the container exits.
restartRetries: RestartRetries caps Docker’s on-failure restart attempts (MaximumRetryCount).
0 = default (5). Only meaningful with RestartPolicy “on-failure”.
revision: Revision is the stored version of this spec, incremented by the control
plane on every accepted write. Server-owned: a client never sets it.
scaling: Scaling turns on autoscaling between a floor and a ceiling of replicas,
driven by the metrics it names.
secrets: Secrets are references to material in Vault, injected as environment
variables at dispatch. The value is never stored in the spec.
secretsVersion: SecretsVersion is the rotation trigger (WP2): bumped by the rotation API,
covered by the spec-hash, so a bump drifts the hash and recreates the
containers, which re-resolve current Vault values at dispatch. API-managed;
operators normally never set it.
securityOpt: Security options
stack: Stack is the logical grouping this deployment belongs to, emitted as the
com.docker.compose.project label so tools such as Portainer group its
containers together. Empty defaults to “odysseus”.
status: Status is the reconciler’s view of the deployment’s current state.
Server-owned.
statusMessage: StatusMessage is the reconciler’s last error or status detail, in prose.
Server-owned.
stopGracePeriod: StopGracePeriod is how many seconds the container gets to exit after
SIGTERM before it is killed.
tenantId: TenantID identifies which tenant owns this deployment. It is assigned by
the control plane from the caller’s tenant context, never taken from the
request body. For platform-level shared services, use
types.PlatformTenantID; check ownership with deployment.TenantID.IsPlatform().
ulimits: Ulimits are the container’s POSIX resource limits (#144). Docker’s
per-container –ulimit OVERRIDES default-ulimits from daemon.json, and a
tenant node with no default-ulimits key — the measured state of this host
— leaves the soft nofile limit at 1024. The supported remedy is this
field, never a hand-edited daemon.json, which is exactly the class of
runtime change spec §2.1 forbids.
update: Update is how a spec change is rolled out. Unset means delete-then-create,
which is why EffectiveUpdateStrategy exists: “nothing declared” and
“recreate declared” are indistinguishable on the wire without it.
updatedAt: UpdatedAt is when the control plane last accepted a write to it.
Server-owned.
user: User is the UID:GID or username the container process runs as.
volumes: Volumes are the bind mounts, named volumes and tmpfs mounts attached to
each container instance.
workingDir: WorkingDir is the directory the process starts in, overriding the image’s WORKDIR.