Secrets
Composia manages encrypted secret files in the desired-state repository using age encryption. Encryption and decryption happen on the controller. Agents never access the age private key.
Configuration
Secrets require an age key pair. Set up in the controller config:
controller:
secrets:
provider: age
identity_file: "/app/configs/age-identity.key"| Key | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Must be age. |
identity_file | string | Yes | Path to the age private key file. |
recipient_file | string | No | Path to file containing age recipients (public keys). If omitted, the recipient is derived from the private key. |
armor | bool | No | Use ASCII-armored output. Defaults to true. |
Generate a key pair:
age-keygen -o age-identity.keyOptional: extract the public key as a recipient:
age-keygen -y age-identity.key > age-recipients.txtHow secrets are stored
Secret files in the repository have a .enc extension by convention. They are stored as age-encrypted ciphertext:
my-app/
├── docker-compose.yaml
├── composia-meta.yaml
└── .secret.env.enc (encrypted with age)The controller encrypts plaintext on write and decrypts on read. The repository contains only ciphertext. Secrets never appear as plaintext in the repo, in task logs, or in transit to agents.
How secrets reach agents
During the render step of a deploy or update task, the controller:
- Reads encrypted files from the service directory in the repo.
- Decrypts each file using the age private key.
- Injects the decrypted content into the service bundle as
.composia-secret.env.
The bundle is streamed to the agent over the agent report connection. The agent writes the bundle to disk and proceeds with docker compose up. The decrypted secret environment is available to the Compose services without the agent ever seeing the private key.
CLI usage
Write an encrypted secret file:
composia secret update my-app .secret.env.enc --file ./local-plain.envRead and decrypt a secret file:
composia secret get my-app .secret.env.encEdit a secret in place (opens your editor):
composia secret edit my-app .secret.env.encAll secret write operations include a base revision check to prevent conflicts with concurrent changes.
File path rules
Secret file paths must:
- Be relative to the service directory (not absolute).
- Not contain path traversal sequences like
../. - Point to a file inside the service directory.
The controller locates the service, resolves the file path relative to the service directory, and operates on the repo file.
Error conditions
- Secrets not configured:
GetSecretandUpdateSecretreturnFailedPreconditionwhencontroller.secretsis not set. - File not found:
GetSecretreturns an empty content response rather than an error when the file does not exist. This lets clients distinguish between missing files and decryption failures. - Base revision conflict:
UpdateSecretuses CAS (compare-and-swap) against the repo HEAD. If the repo changed since the last read, the write fails with a revision conflict.