Skip to main content

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​

  1. The cluster key. Secrets are sealed with a 32-byte key stored in one file per node, by default /etc/clouduler/secrets.key (mode 0640, 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 own node.key.
  2. Sealing. Each secret is encrypted with XChaCha20-Poly1305 using its name as authenticated data, so a sealed value cannot be moved to another name.
  3. Stacks reference them. A stack declares configs: and secrets: with a file: 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.
  4. 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.
Back up the cluster key

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​

Open Settings, Secrets and configs (/settings/secrets). The page has three panels: Cluster key (tag held here or not held here), Secrets and Configs.

/settings/secrets
Secrets and configs. Values are never shown.

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.

Use them in a stack​

stack.yaml
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.

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.").

What to back up​

ItemWhyWhere
secrets.keyThe only thing that opens sealed secrets. Losing every copy loses every secret./etc/clouduler/secrets.key on each node. Keep an offline copy.
age identityDecrypts the secrets in the git backup. Clouduler never stores it.Wherever you saved clouduler-age-key.txt.
restic repository passwordsAuto-generated, sealed, never shown. Needed to read your drive backups.Recoverable only via the cluster key or a git export.
node.keyThis 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.yamlLives 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​

Option
Type / default
Reload
Description
absolute pathdefault /etc/clouduler/secrets.key
needs restart
Where the cluster key lives on this node.
Watch out: SIGHUP (systemctl reload) re-reads the file contents only. Not editable in the UI.
absolute pathdefault /etc/clouduler/node.key
needs restart
This node's key pair used to receive the cluster key.
booldefault true
needs restart
Create a new key on first launch when none exists. false refuses and fails instead.
Watch out: A node missing its key file and allowed to generate creates a different key that cannot open existing secrets. Set false on nodes that should only receive the key.
booldefault inherits secrets.generate_if_missing
needs restart
Per-node override. clouduler init --join sets false.
pathno default
n/a
Stack file: plaintext config bundled with the stack.
Watch out: Max 1 MiB. Redeploy to change.
pathno default
n/a
Stack file: secret value read from a file and sealed on deploy.
Watch out: Final newline is kept.
namedefault entry name
n/a
Service reference (long form): which object to use.
absolute container pathdefault secret /run/secrets/<source>; config /<source>
n/a
Where the file appears.
Watch out: Error: target X must be an absolute path in the container. Cannot combine with env.
octal stringdefault 0444 for secrets
n/a
File permissions.
Watch out: Error: X is not a valid octal file mode. Quote it ("0440").
integerno default
n/a
File owner and group.
Watch out: Cannot combine with env.
variable nameno default
n/a
Expose a secret as an environment variable.
Watch out: Visible to docker inspect and /proc. Cannot combine with target, mode, uid, gid or write.
booldefault false
n/a
Config only: container edits are written back.
Watch out: Error on a secret: write is not allowed on a secret, only on a config.
one of threeno default
n/a
Source of the value. With neither flag it reads stdin.
Watch out: --value is not newline-trimmed; --file and stdin lose one trailing newline.
flagdefault off
n/a
Keep the trailing newline of --file or stdin input.
node nameno default
n/a
On secret and registry commands: send through this node.
string / flagno default
n/a
Login details. With no password option the CLI prompts.

Quirks and gotchas​