When building modern apps, we often juggle multiple environments: development, build pipelines, staging, and production. Wouldnβt it be great to handle them all with a single, unified docker-compose.yml
file? Yes, itβs possibleβand powerful!
This article walks you through creating a Compose file that works across the full lifecycle of your application: local development, building, testing, and deployingβeven to production with Docker Swarm. Letβs go!
π Why Use a Single Compose Design?
Managing multiple Compose files (docker-compose.override.yml
, docker-compose.prod.yml
, etc.) gets messy fast.
Instead, we can:
- Keep one main
docker-compose.yml
file. - Use profiles, build contexts, and secrets.
- Control behavior with CLI flags or environment variables.
ποΈ The App Structure
Letβs imagine a simple full-stack app:
my-app/
βββ backend/ (Spring Boot)
βββ frontend/ (React + Vite)
βββ nginx/ (Reverse Proxy)
βββ docker-compose.yml
βββ .env
π§ Compose Design Overview
Hereβs what we want in our single Compose file:
- Profiles to separate dev-only services (like hot reloading).
- Build contexts to build images during CI or locally.
- Secrets and configs for secure production-ready deployment.
- Deploy block for Swarm mode.
π§ͺ Development Stage
services:
backend:
build:
context: ./backend
ports:
- "8080:8080"
volumes:
- ./backend:/app
profiles: ["dev"]
frontend:
build:
context: ./frontend
ports:
- "5173:5173"
volumes:
- ./frontend:/app
profiles: ["dev"]
Run development services:
docker compose --profile dev up
ποΈ Build Stage (CI/CD or Local Image Build)
Still using the same file, just with the build option:
backend:
build:
context: ./backend
image: my-backend:latest
frontend:
build:
context: ./frontend
image: my-frontend:latest
Then build with:
docker compose build
Or push to a registry:
docker compose build && docker compose push
π Deploy Stage (Swarm or Production)
You can use the same Compose file with Docker Swarm:
services:
backend:
image: my-backend:latest
deploy:
replicas: 2
resources:
limits:
cpus: '0.5'
memory: 512M
secrets:
- db_password
frontend:
image: my-frontend:latest
deploy:
replicas: 1
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
Initialize Swarm:
docker swarm init
Deploy the stack:
docker stack deploy -c docker-compose.yml myapp
π Add Secrets (For Production)
secrets:
db_password:
file: ./secrets/db_password.txt
Use in backend:
services:
backend:
...
secrets:
- db_password
β‘ Environment Variables for Flexibility
Use a .env
file to switch behavior:
NODE_ENV=development
FRONTEND_PORT=5173
In Compose:
frontend:
ports:
- "${FRONTEND_PORT}:5173"
β Benefits of This Pattern
- One file to rule them all.
- No more config drift between dev/staging/prod.
- Can scale from local dev to cloud deployment.
- Great for teams and CI pipelines.
π Final Tips
- Use
--profile
to enable/disable services. - Use
docker-compose.override.yml
only for personal overrides, not team-wide differences. - Secure your secrets and configs!
- Combine with GitHub Actions or GitLab CI for automatic deploys.
π Conclusion
A well-designed, unified docker-compose.yml
can streamline your entire app lifecycleβfrom development to production. With smart use of profiles, secrets, and Swarm deployment blocks, you no longer need to duplicate configs or maintain complex scripts.
This pattern makes Docker work for you, not the other way around. π³π
Top comments (0)