Secrets Management for a Dockerised Stack: AWS Secrets Manager, SSM, and Local .env Parity
Production-ready pattern: AWS Secrets Manager and SSM Parameter Store for deployed environments, with genuine local .env parity—not aspirational.
Secrets Management for a Dockerised Stack: AWS Secrets Manager, SSM, and Local .env Parity
Managing secrets across local development, CI, and production is one of those problems that feels solved until you're debugging a 3am incident caused by a stale environment variable. Getting secrets management right in a Dockerised stack is not glamorous work, but it is load-bearing work.
This tutorial walks through a production-ready pattern: AWS Secrets Manager and SSM Parameter Store for deployed environments, with a local .env workflow that stays genuinely in parity — not aspirationally in parity.
Why "Just Use .env" Breaks at Scale
A .env file works on your laptop. Problems emerge with multiple services, multiple developers, a CI pipeline, and multiple deployment environments. You end up with .env.staging, .env.production, secrets scattered across machines, and rotation procedures nobody follows because they're too manual.
Two AWS services address this:
- AWS Secrets Manager — for secrets requiring rotation (database passwords, API keys, OAuth credentials). The per-secret monthly cost justifies itself through automation.
- SSM Parameter Store — for configuration values and non-sensitive parameters. The
SecureStringtype encrypts values with KMS, making it viable for secrets, and the standard tier is free.
Use Secrets Manager for anything that rotates or is genuinely sensitive at rest. Use SSM for configuration that varies by environment but isn't catastrophic if exposed.
The Core Pattern: Fetch at Container Startup
The most common mistake: baking secrets into Docker images as build-time arguments. Don't. Instead, fetch secrets at container startup via an entrypoint script.
Here's a minimal entrypoint for a Python service (Django or FastAPI):
Your Dockerfile calls this before the main process:
The container IAM role grants secretsmanager:GetSecretValue on the specific secret ARN only.
SSM for Per-Environment Configuration
For non-secret, environment-specific values (feature flags, service URLs, log levels), SSM Parameter Store with a naming convention is cleaner:
Fetch a whole prefix at once:
Add this to the entrypoint script before your exec call. Configuration and secrets are both resolved at runtime, never baked in.
Achieving Local .env Parity
Most tutorials stop here. They explain AWS and leave your .env drifting from production within a week.
The fix: a script that generates your local .env by pulling from the same SSM path and Secrets Manager secret your containers use:
Add .env to .gitignore. Commit scripts/generate-local-env.sh to the repo. Every developer pulls from the same source of truth as production. Rotating a secret requires running one script, not finding four .env files.
Docker Compose Wiring
For local development, load the generated .env:
For deployed environments, the entrypoint script handles everything. Never use env_file in production compose or ECS task definitions.
What This Solves
This pattern gives you a single source of truth (AWS), runtime injection identical across ECS, Kubernetes, or bare Docker, and a local workflow that stays in sync. Secret rotation becomes an AWS operation; containers pick up new values on next restart without image rebuilds.
It requires upfront effort, but it pays back the first time you rotate a compromised key at speed.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.