Stack spec
A stack is one YAML file that describes a group of containers (called services) for Clouduler to run across your cluster. This page lists every field the file accepts. It is a subset of Docker Swarm / Compose v3 plus an x-clouduler block for things Compose has no concept of: reverse-proxy routes, drive-backed volumes, node pins, actions and alert rules.
For the task-oriented view, see Stacks. Terms like drive, overlay and placement are explained in the glossary.
Three rules to know before the tables:
- Unknown keys are an error. A typo fails the parse instead of being ignored, and
build:is not supported. - Everything takes effect on redeploy. The spec is stored as text. A change applies when you deploy a new revision, so the Reload column below reads
n/afor every field. - Types.
durationis a humantime value (30s,2m,1h30m).sizeis a byte size (10MB,64MiB,512M, or a plain number of bytes).
How validation works
Validation runs in two layers.
- Syntax runs on every save in the web UI and on every deploy. It needs no cluster: names, shapes, port collisions, dependency cycles, and so on.
- Cluster checks ask whether the network, drive and node you named actually exist and whether the service can be placed. The web UI, the HTTP API (and the MCP tools), the CLI (
clouduler stack deploy) and the git-backup pull all run them, and refuse the deploy with a message when a drive, network or node pin does not exist.
Because the UI errors come back without a path, the message carries the whole location, for example invalid config at services.web.env_file: ..., and the editor does not highlight a line for it.
A full example
This file touches most of the common fields. Comments explain each part.
version: "3.8" # ignored, optional
networks:
wan: {} # a cluster network with a domain, used by the proxy route
tailscale: {} # a second network, used by the SSH expose
secrets:
db_password:
file: ./secrets/db.txt # read at deploy time, sealed with the cluster key
configs:
gitlab_rb:
file: ./conf/gitlab.rb # bundled at deploy time
services:
gitlab:
image: gitlab/gitlab-ce:16.9.1-ce.0
restart: unless-stopped # the real restart policy lives here
networks: [wan, tailscale] # must be declared above
environment:
GITLAB_OMNIBUS_CONFIG: "external_url 'https://gitlab.example.com'"
volumes:
- fast/gitlab-data:/var/opt/gitlab # drive/volume:/target
- fast/gitlab-config:/etc/gitlab
- bulk/gitlab-logs:/var/log/gitlab
- type: tmpfs
target: /tmp/cache
tmpfs: { size: 64m }
secrets:
- source: db_password
env: DB_PASSWORD # exposed as an env var instead of a file
configs:
- source: gitlab_rb
target: /etc/gitlab/gitlab.rb
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/-/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 2m
stop_grace_period: 2m
deploy:
replicas: 1
resources:
limits: { cpus: "2.0", memory: 4G }
x-clouduler:
node: atlas # hard pin, optional
proxy:
- host: gitlab # becomes gitlab.<domain of wan>
network: wan
port: 80
tls: true
middlewares: [redirect_https]
max_body_size: 2GiB
preserve_host: true
response_timeout: 120s
expose:
- port: 22 # container port
published: 2222 # host port, bound on every node
protocol: tcp
network: tailscale
backup:
stop_before: true # stop the service during a restic backup
Top level
servicesrequiredmapno defaultmapdefault {}list of strdefault unrestrictedmapdefault {}mapdefault {}mapdefault {}mapno defaultStack names and service names
The stack name is not a spec key. It comes from the file name (CLI, without the extension) or the Stack name field in the web UI. It must be 1 to 63 bytes, start and end with a lowercase letter or digit, and use only a-z, 0-9, - and _. The UI hint says "Lowercase letters, numbers and dashes", but the server also accepts underscores.
Each key under services: must be a DNS label (lowercase letters, digits and hyphens, not starting or ending with a hyphen, at most 63 characters). It is the service's hostname on the overlay and part of the container name STACK_SERVICE_INDEX.
Service
imagerequiredstrno defaultstring or listdefault image defaultmap or list of KEY=valueno defaultpath or listno defaultmap or list of key=valueno defaultlist of namesno defaultlist of service namesno defaultmapno defaultdurationdefault cluster failover.stop_grace (30s)strdefault image STOPSIGNALlistno defaultlistno defaultmapno defaultVolumes
Volumes come from drives, from tmpfs, or are anonymous container paths. Host binds are not supported. See Drives and volumes.
strno defaultstrno defaultstrno defaultn/ano defaultPorts
strno defaulthost or ingressdefault hostA published port is bound on the node that runs the container and moves with it. For a port that follows the container across nodes, use x-clouduler.expose. Two services in the same stack cannot publish the same port. Collisions between different stacks are not checked.
Healthcheck
testrequiredstring or listno defaultConfigs and secrets in services
strno defaultabsolute pathdefault secrets: /run/secrets/NAME, configs: /NAMEstrno defaultbooldefault falseDeploy
intdefault 1replicated or globaldefault replicatedlist of strno defaultstr (float)no defaultsizeno defaultsame as limitsno defaultmapno defaultmapno defaultProxy and x-clouduler per service
x-clouduler is the Clouduler-specific block on a service.
strno defaultlist of servicesno defaultlist of routesno defaultlistno defaultstrno defaultstrno defaultdurationdefault drive sync_intervalbooldefault truebooldefault falsebooldefault drive backup target decides (stop)x-clouduler.docker_socket.targetrequiredpathno defaultlocal or cloudulerdefault localbooldefault falsemapno defaultactions.LABEL.cmdrequiredstr or listno defaultdurationdefault 30sbooldefault truefalse or mapdefault enabledProxy routes
Each entry of x-clouduler.proxy. See Proxy and TLS.
hostrequiredstrno defaultnetworkrequiredstrno defaultdurationdefault none (waits forever)booldefault falseExpose
Each entry of x-clouduler.expose. See Networking.
publishedrequiredintno defaultnetworkrequiredstrno defaultbooldefault falseMiddlewares
Written under middlewares: of a route, either as a bare name (redirect_https) or as a one-key map (cors: {origins: [...]}).
bare or mapdefault listdefault any logged-in userbooldefault truemapno defaultcors.originsrequiredlistno defaultredirect_regex.regexrequiredstrno defaultAlerts
Allowed at the stack level and the service level under x-clouduler.alerts. See Alerts.
booldefault true (enabled)listno defaultinfo, warning or criticaldefault per eventstrno defaultdurationdefault 5mlistdefault allCommon validation errors
The format is invalid config at PATH: MESSAGE, where PATH is a dotted path such as services.web.volumes[0].
Syntax errors (UI and CLI)
service name must be a valid DNS label (lowercase letters, digits, hyphens, not starting or ending with a hyphen) since it becomes an overlay hostnameenv_file is not supported yet, move its entries into environmentnetwork NET is not declared in this stack's top-level networksno service named DEP in this stack,a service cannot depend on itself,dependency cycle: a -> b -> aport P/PROTO is already published by services.X.ports[I]bind mounts are not supported, volumes must live on a drive so they can be replicated and migrated"SRC" must use drive/volume syntax, e.g. fast/gitlab-datamode: ingress is not supported, clouduler has no swarm-style ingress mesh; ...unknown restart policy "X", expected no, always, on-failure[:N] or unless-stopped"sigint" is not a signal name (e.g. SIGINT) or numbermust be greater than zeroormust be at most 600s(stop_grace_period)write is not allowed on a secret, only on a configCFG is writable but this service runs N replicas; only one instance may write a configN is more than the maximum of 1000(replicas)expected KEY=value, got "ENTRY"(list-form environment or labels)mode: clouduler is read-only; set read_only: trueallowed must be a list (write `allowed: []` to deny everything, or drop the key)invalid stack name "NAME": must be 1 to 63 bytes long,must start and end with a lowercase letter or digit,only lowercase letters, digits, - and _ are allowed
Cluster errors (CLI and git sync only)
no network named NET declared in the clusterno node implements network NET, this service could never be placedonly one egress network is allowed per service, found A and Bno drive named DRIVE declared in the clusterno node named NODE in the clusternetwork NET has no domain, so it cannot serve an HTTP routeno node has P/PROTO open on network NET, this port could never be reacheddrive D can be served by more than one node (...), so mounting D/. (its whole root) is not allowed: pin a specific volume under it instead
Deploy errors (UI and API)
config NAME needs content: it has no existing value to keep(and the same for a secret)this node holds no cluster key(the node you are connected to cannot seal secrets)stacks A and B would both use the overlay network ... which would break isolation between them: rename one of the stacks or networks
Deploy errors (CLI)
this stack has secrets, so the cluster key at PATH is needed. Deploy from a node that holds it, or use the web uiNAME is N bytes, over the 1048576 byte limit(each bundled config or secret file is capped at 1 MiB)