Skip to content

Deployment — REST API surface

{
"tenantId": "a74adad8-0c9e-4a6f-9f2a-3f5a1d2b7c40",
"name": "web",
"image": "nginx:1.27-alpine",
"replicas": 1
}
Validated against Deployment · testdata/corpus/types/deployment-minimal.json

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

  • canary
  • capAdd
  • capDrop
  • command
  • dns
  • dnsSearch
  • entrypoint
  • expose
  • extraHosts
  • hostname
  • imagePullSecret
  • logging
  • networkAliases
  • nodeId
  • ports
  • privileged
  • probe
  • readOnly
  • restartPolicy
  • secretsVersion
  • securityOpt
  • stack
  • stopGracePeriod
  • tenantId
  • user
  • workingDir

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.