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.ymlfile. - 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
--profileto enable/disable services. - Use
docker-compose.override.ymlonly 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)