10 Lines to Better Docker Compose Secrets

This is a prac­ti­cal pat­tern I use when con­tainer­ized apps expect envi­ron­ment vari­ables but I want the secu­ri­ty ben­e­fits of file-​mounted secrets. Drop the shell script below next to your Docker Composefiles and you can do the same.

Quick overview

Secrets like pass­words and API keys belong out­side your repos­i­to­ry and appli­ca­tion image lay­ers. Docker Compose can mount such secretsin your con­tain­ers as files under /run/secrets, which keeps them out of images and ver­sion con­trol. But many apps still expect con­fig­u­ra­tion via envi­ron­ment vari­ables. Rather than chang­ing app code, I use a tiny wrap­per script that:

  • reads every file in /run/secrets
  • exports each file’s con­tents as an envi­ron­ment variable
  • then execs the orig­i­nal command

It’s small, pre­dictable, portable, and keeps secrets from mix­ing with your ver­sioned .env envi­ron­ment filesand out of your Compose files.

How it works

  • Location:Docker Compose mounts secrets into the con­tain­er at /run/secrets/<NAME>.
  • Mapping rule:The wrap­per uses those file names as envi­ron­ment vari­able names; the file con­tents become the val­ues. Secret names in your Compose file must be valid shell iden­ti­fiers(they become both the file names in /run/secretsand the export­ed vari­able names).
  • Execution:After export­ing vari­ables, the script uses exec "$@"so that the wrapped process replaces the shell and inher­its the export­ed environment.
  • Security mod­el:Secrets remain files you can per­mis­sion appro­pri­ate­ly on the host; they’re not baked into images or stored in your Compose YAMLas plain text.

The script

Let’s call it with-secrets.sh:

#!/bin/sh
set -eu

for secret_file in /run/secrets/*; do
  [ -e "$secret_file" ] || continue
  if [ -f "$secret_file" ]; then
    name=$(basename "$secret_file")
    export "$name=$(cat "$secret_file")"
  fi
done

exec "$@"

Notes about the script

  • set -eufails fast on unset vari­ables or errors.
  • Since it exports each secret using the file name as the vari­able name, san­i­tize the file name if you need dif­fer­ent envi­ron­ment vari­able names.
  • The final exechands con­trol to your app with­out leav­ing an extra shell process.

Example Compose snippet

services:
  app:
    image: your-app:latest
    secrets:
      - DB_PASS
      - API_KEY
    volumes:
      - ./with-secrets.sh:/with-secrets.sh:ro
    command: ["/with-secrets.sh", "your-original-command", "--with-args"]

secrets:
  DB_PASS:
    file: ./secrets/db_password.txt
  API_KEY:
    file: ./secrets/api_key.txt

Behavior: DB_PASSand API_KEYabove appear as files (/run/secrets/DB_PASS, /run/secrets/API_KEY); the mount­ed with-secrets.shwrap­per script exports them as DB_PASSand API_KEYenvi­ron­ment vari­ables for your-original-command --with-args.

Decision points and alternatives

  • Prefer native *_FILEsup­portif your app sup­ports it (e.g., PostgreSQL’s PGPASSFILE). That avoids the wrap­per entirely.
  • For multi-​host or high-​compliance deploy­ments,use an exter­nal secrets man­ag­er (e.g., Hashicorp Vault, cloud KMS, SOPS) rather than Compose secrets.
  • Build-​time secretsare a sep­a­rate con­cern; use BuildKit or ded­i­cat­ed build secret mech­a­nisms to avoid leak­ing cre­den­tials into your image layers.

Risks and mitigations

  • Risk:Accidentally log­ging or dump­ing envi­ron­ment vari­ables
    Mitigation: Neverprint envi­ron­ment vari­ables in logs and restrict debug output
  • Risk:Secret file names that are not valid shell iden­ti­fiers
    Mitigation:Normalize or map file names to safe envi­ron­ment vari­able names before exporting
  • Risk:Secrets checked into gitor oth­er ver­sion con­trol
    Mitigation:Keep secret files out of repos, add strict .gitignorerules, and inject secrets via CI/​CD or run­time provisioning

Final notes

This pat­tern is inten­tion­al­ly prag­mat­ic: it pre­serves the secu­ri­ty advan­tage of file-​mounted secrets while let­ting unmod­i­fied apps keep using envi­ron­ment vari­ables. It’s not a sil­ver bul­let for every environment–use it where Compose secrets are appro­pri­ate and pair it with stronger secret stores for production-​grade, multi-​host deployments.


Discover more from The Phoenix Trap

Subscribe to get the latest posts sent to your email.

Mark Gardner Avatar

Hi, I’m Mark.

Hi, I’m Mark Gard­ner, and this is my personal blog. I show software developers how to level up by building production-ready things that work. Clear code, real projects, lessons learned.

Your post must include a link to this post.

How does this work?
Write a response on your own website and include a link to this post. Then submit your response’s URL here. Your response may appear here after moderation. To update or remove it, edit or delete your original post and submit the same URL again. Learn more about Webmentions.