Configs and secrets
A config is a named plaintext file (an nginx.conf, a settings file) stored in the cluster and mounted into containers. A secret is a named value (a database password, an API token, a registry login) stored encrypted. Stacks refer to both by name, so you never paste a password into a stack file.
Secret values are write-only: the web UI, the CLI and the API never show them again. Configs are not encrypted and anyone who can read the cluster state can read them. Never put a secret in a config.
How it works
- The cluster key. Secrets are sealed with a 32-byte key stored in one file per node, by default
/etc/clouduler/secrets.key(mode0640, owned by root, group of the process that created it). The sealed bytes replicate through the cluster database; the key does not. Every node that must start a container using a secret needs a copy of the key file. A joining node gets one from a peer, wrapped for that node's ownnode.key. - Sealing. Each secret is encrypted with XChaCha20-Poly1305 using its name as authenticated data, so a sealed value cannot be moved to another name.
- Stacks reference them. A stack declares
configs:andsecrets:with afile:for each, and services mount them. The files are bundled with the stack on deploy. A stack owns what it bundles; removing the stack deletes what it owns, unless another stack owns it too. - Materialising. Every 10 seconds each node writes the objects its instances need into a private folder (mode 0700) and bind-mounts them read-only. Secrets go to
/run/secrets/<name>by default, configs to/<name>. A secret can instead be an environment variable.
If every copy of secrets.key is lost, every stored secret is unrecoverable and Clouduler cannot fix it. Copy it somewhere private and offline now. See What to back up.
Manage secrets and configs
- Web UI
- CLI / config
Open Settings, Secrets and configs (/settings/secrets). The page has three panels: Cluster key (tag held here or not held here), Secrets and Configs.
Create a secret: click Create secret and name it. Choose one of three modes:
- Paste string: type the value.
- File: choose a file. The UI reads it as text, so binary files are corrupted (use the CLI for those).
- Generate: set the length (1 to 1024) and character set (
alphanumeric,hex,base64,printable).
The sheet warns: "The value is sealed on the server and never returned by the API, so this is the only chance to copy it down if you set it yourself." Saving an existing name overwrites it with no warning.
Create a config: click New config, fill Name and Content, then Save config.
Remove: the trash button asks "Remove secret NAME?". A stack that only references a secret does not own it, and removing it makes that stack fail to start.
When you deploy a stack with bundled files, fill each one in the Bundled files panel. Leave a field blank to keep the stored value.
# Value on the command line (never trimmed; visible in shell history and ps)
clouduler secret set db-password --value 'example-value'
# From a file (one trailing newline removed unless --no-trim)
clouduler secret set api-token --file ./token.txt
# From stdin (no flag: with neither --value nor --file, it reads stdin)
printf '%s' "$VALUE" | clouduler secret set api-token
# Keep a trailing newline
clouduler secret set cert-bundle --file ./bundle.pem --no-trim
clouduler secret list # name, revision, owner. Never values.
secret set seals locally, so run it on a node that holds the key. Otherwise: setting a secret needs the cluster key at ... Run this from a node that holds it, or use the web ui. On success it prints set NAME.
Configs are bundled by clouduler stack deploy; there is no separate config command.
Use them in a stack
configs:
app-settings:
file: ./files/app-settings.toml
secrets:
db-password:
file: ./files/db-password.txt
services:
web:
image: example/web:1.0
configs:
- app-settings # short form: mounted at /app-settings
- source: app-settings
target: /etc/app/settings.toml
mode: "0440"
secrets:
- db-password # short form: /run/secrets/db-password, mode 0444
- source: db-password
env: DB_PASSWORD # an environment variable instead of a file
file: entries are read relative to the stack file and bundled as raw bytes (a bundled secret keeps its final newline). Each object is at most 1 MiB; the CLI rejects larger files. If the stack has secrets and the node lacks the key: this stack has secrets, so the cluster key at ... is needed. Deploy from a node that holds it, or use the web ui.
A writable config (write: true, configs only) lets the container edit the file and writes edits back to the cluster. Only one instance may write it: NAME is writable but this service runs N replicas; only one instance may write a config. The node keeps .rev and .base bookkeeping files next to the config, and writes a .conflict file when the stored copy and the container's edit diverge.
Registry credentials
To pull private images (or avoid Docker Hub rate limits), store a registry login. It is checked against the registry before it is saved, sealed like any secret as _clouduler_registry_<server>, and used whenever an update pulls an image. See the private registries guide for provider specifics.
- Web UI
- CLI / config
Open Updates (/updates) and click Manage registry accounts. Pick a registry (or type one), enter username and password, click Test connection, then Save login. To remove a login: "Remove the login for NAME?" ("Images from this registry may stop pulling.").
clouduler registry login ghcr.io --username example-user --password-stdin < pw.txt
clouduler registry login ghcr.io --username example-user # prompts for the password
clouduler registry list
clouduler registry test ghcr.io
clouduler registry remove ghcr.io # alias: rm
Messages: logged in to ghcr.io as example-user, no registry logins stored, ghcr.io accepted the stored login, removed the login for ghcr.io. Errors: the password is empty, the username is empty. --password on the command line is visible to ps; prefer stdin.
What to back up
| Item | Why | Where |
|---|---|---|
secrets.key | The only thing that opens sealed secrets. Losing every copy loses every secret. | /etc/clouduler/secrets.key on each node. Keep an offline copy. |
| age identity | Decrypts the secrets in the git backup. Clouduler never stores it. | Wherever you saved clouduler-age-key.txt. |
| restic repository passwords | Auto-generated, sealed, never shown. Needed to read your drive backups. | Recoverable only via the cluster key or a git export. |
node.key | This node's key pair for receiving the cluster key. A lost node.key is recoverable: the node re-joins and gets the cluster key again. | /etc/clouduler/node.key |
cluster.yaml | Lives in the cluster; git backup also mirrors it. | See git backup. |
Keep the key copy private. A node that finds its key file readable by others refuses it: the cluster key at ... has excess permissions, run: chmod 640 .... A removed node deletes its key on next start, so copy the key first if you may want it back.
Options
absolute pathdefault /etc/clouduler/secrets.keyabsolute pathdefault /etc/clouduler/node.keybooldefault truebooldefault inherits secrets.generate_if_missingpathno defaultpathno defaultabsolute container pathdefault secret /run/secrets/<source>; config /<source>octal stringdefault 0444 for secretsvariable nameno defaultbooldefault falseone of threeno defaultstring / flagno default