Decrypt age-encrypted .env files in your repo at deploy time — Go binary on distroless
10K+
Decrypt age-encrypted .enc files to their plaintext siblings at deploy time so your orchestrator can read them: .env files, any other config, or a single file piped through stdin/stdout. Ciphertext stays tracked in git; plaintext is generated next to it and never committed.
Walks a mounted directory tree (or a single .enc file you name), finds every <name>.enc ciphertext source (binary or armored age format), and atomically writes its decrypted plaintext to the sibling <name>: apps/x/.env.enc becomes apps/x/.env. The source is never modified, so your working tree stays clean: git pull always applies rotated secrets, and the generated plaintext is just re-derived on the next pass. An --ext filter narrows a walk by the OUTPUT suffix (--ext .env selects .env.enc sources); a - target switches to a stdin-to-stdout pipe for a single file. Designed to run as a pre_deploy step before docker compose up reads the files.
The age-decrypt binary is a single static Go executable on gcr.io/distroless/static:nonroot:
decrypt --ext .env: decrypt every .env.enc in REPO_ROOT to its .env sibling (the deploy use case)decrypt /path: decrypt a specific .enc file or every .enc source under a directory treedecrypt -: pipe, stdin ciphertext in, stdout plaintext outhealth: file-based health probe for Docker HEALTHCHECKThe decrypt subcommand always requires you to say what to decrypt (an extension filter, a path, or -). Server mode (no subcommand) is the always-on container entrypoint that idles as a docker exec target (see the Server mode note under Subcommands for details).
<name>.enc is tracked in git and never touched; <name> is generated and gitignored. Your compose file references apps/<x>/.env like usual, git status stays meaningful on live checkouts, and a git pull can never conflict with a decrypted secret (the failure mode of in-place rewriting, which v2 used).enc source that will not decrypt, an unreadable subtree, or stray age ciphertext sitting at a plaintext path (an un-migrated secret) all exit non-zero and block the deploy; ciphertext can never be silently consumed as config.env, and a source can never be corrupted (it is opened read-only)--ext any matching nonregular plaintext path fails the passHEALTHCHECK CMD ["/age-decrypt", "health"])docker pull cplieger/docker-age:latest
Also published to ghcr.io/cplieger/docker-age with identical images and tags. Release versions are tagged vX.Y.Z alongside latest.
# Example compose for docker-age. See the README for all configuration options and hardening.
# Trigger (.env.enc -> .env): docker exec age /age-decrypt decrypt --ext .env
services:
age:
image: ghcr.io/cplieger/docker-age:latest
container_name: age
restart: unless-stopped # always-on: stays up between deploys as an exec target
environment:
IDENTITY_PATH: "/age/keys.txt" # required; age identity file
volumes:
- "/path/to/age-keys:/age:ro" # dir holding the age identity (keys.txt)
- "/path/to/repo:/repo" # tree to decrypt (or a folder of repos)
Apache-2.0. See LICENSE.
The image carries the license text of every bundled component under /usr/share/licenses/, including the BSD-3-Clause text of the age library it links.
Content type
Image
Digest
sha256:57750acd4…
Size
3 MB
Last updated
about 4 hours ago
docker pull cplieger/docker-age