Age Encrypted Secrets Setup¶
Set up offline encrypted secrets management for ComposeFlux using age.
ComposeFlux natively supports decrypting *.age encrypted dotenv files directly in your Git repository at runtime without writing plaintext secrets to disk or calling external third-party secret manager APIs.
Overview¶
- Encrypted
.env.age(or any*.age) files are committed directly to your Git repository. - Secrets are decrypted in memory using a passphrase (
--age-passphraseorAGE_PASSPHRASE) and made available for Docker Compose interpolation. - When
*.agefiles are updated in Git, ComposeFlux detects the change and triggers an automatic redeploy. - Secret files must be regular files; ComposeFlux rejects symbolic links ending in
*.age.
Secret Hierarchy & Layering¶
ComposeFlux supports layered secrets:
- Shared Root Secrets: Place
*.agefiles at the root of yourSTACK_PATHdirectory (e.g.stacks/shared.env.age). These variables are available for interpolation in all stacks. - Stack-Specific Secrets: Place
*.agefiles in the stack directory next tocompose.yml(e.g.stacks/nextcloud/secrets.env.age). These are available only to that stack and override matching keys from shared root secrets. - Included Subdirectory Secrets: If a compose file uses
includedirectives targeting other directories, any*.agefiles located in those included directories are also loaded and mapped as dependencies.
Directory Structure Example¶
your-stacks-repo/
└── stacks/ ← STACK_PATH
├── stack.yml
├── shared.env.age ← Applied to all stacks
├── traefik/
│ ├── compose.yml
│ └── traefik.env.age ← Applied to traefik stack only
└── nextcloud/
├── compose.yml
└── secrets.env.age ← Applied to nextcloud stack only
How to Encrypt Secret Files¶
1. Install age CLI¶
Install the age CLI tool on your local machine:
# macOS (Homebrew)
brew install age
# Linux (Debian/Ubuntu)
apt install age
# Arch Linux
pacman -S age
# Go
go install filippo.io/age/cmd/...@latest
2. Create Plaintext Dotenv File¶
Create your secret environment file (e.g., .env.secret):
DATABASE_PASSWORD=supersecretpassword
API_KEY=1234567890abcdef
JWT_SECRET=supersecretjwtkey
3. Encrypt with Passphrase¶
Encrypt the file with a passphrase using age -p. We recommend ASCII armor (-a) so the ciphertext is stored in text format:
# Encrypt with armor (-a) and passphrase (-p)
age -p -a -o secrets.env.age .env.secret
Enter your secure passphrase when prompted.
Delete the unencrypted plaintext file after encryption:
rm .env.secret
4. Commit Encrypted File to Git¶
Commit the *.age file to your Git repository:
git add secrets.env.age
git commit -m "Add encrypted secrets for nextcloud"
git push
Configuring ComposeFlux¶
Provide the passphrase to ComposeFlux via the AGE_PASSPHRASE environment variable or --age-passphrase flag.
When neither is set, Age secrets are disabled and ComposeFlux does not scan for *.age files.
Compose Example¶
services:
composeflux:
image: ghcr.io/veerendra2/composeflux:latest
container_name: composeflux
restart: unless-stopped
environment:
GIT_REPO_URL: git@github.com:user/stacks-repo.git
STACK_PATH: stacks
AGE_PASSPHRASE: ${AGE_PASSPHRASE}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ~/.ssh/id_rsa:/.ssh/composeflux_id_rsa:ro
Provide AGE_PASSPHRASE through a protected runtime environment variable. Do not commit the passphrase to the Compose file or repository. If you use a local .env file, exclude it from Git and restrict its file permissions.
Usage in Compose Stacks¶
Decrypted keys from *.age files and shared secrets are available for standard Docker Compose environment interpolation in your compose files:
services:
app:
image: myapp:latest
environment:
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
API_KEY: ${API_KEY}
ComposeFlux does not automatically add every decrypted key to every container. A value reaches a container only when its Compose service explicitly references the key, as shown above.