Docker Compose for Development Environments: The Complete 2026 Guide
In 2026, Docker Compose remains the go-to tool for orchestrating multi-container development environments. Whether you’re building a simple web app with a database or a complex microservices architecture, Docker Compose simplifies the entire workflow.
This guide covers everything you need to know about using Docker Compose for development in 2026 — from basic setup to advanced patterns and best practices.
Why Docker Compose in 2026?
You might wonder: with Kubernetes and other orchestration tools everywhere, is Docker Compose still relevant?
Absolutely. Here’s why:
- Simplicity: One YAML file to define your entire stack
- Speed: Spin up complex environments with
docker compose up - Consistency: Same setup for every developer on the team
- Local Development: Perfect for development (Kubernetes is overkill for local)
- Mature: Over a decade of stability and community support
When to use Docker Compose: - ✅ Local development environments - ✅ Single-host deployments - ✅ Testing and CI/CD pipelines - ✅ Small to medium applications
When to look elsewhere: - ❌ Multi-cluster production deployments (use Kubernetes) - ❌ Auto-scaling requirements (use Kubernetes or Swarm) - ❌ Complex service mesh needs (use Istio + Kubernetes)
Installation & Setup
Install Docker Desktop (Recommended for Most Developers)
macOS:
# Download from https://docker.com/products/docker-desktop
# Or use Homebrew
brew install --cask docker
CodeWindows:
# Download from https://docker.com/products/docker-desktop
# Or use winget
winget install Docker.DockerDesktop
CodeLinux (Ubuntu/Debian):
# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Add repository
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Add user to docker group (avoid sudo)
sudo usermod -aG docker $USER
CodeVerify Installation
# Check Docker version
docker --version
# Check Docker Compose version
docker compose version
# Run test container
docker run hello-world
CodeBasic docker-compose.yml Structure
Let’s start with a simple example: a Node.js app with a PostgreSQL database.
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:password@db:5432/myapp
- NODE_ENV=development
depends_on:
- db
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
CodeKey sections explained:
| Section | Purpose |
|---|---|
version |
Compose file format (3.8 is latest stable) |
services |
Your application containers |
build |
Build from Dockerfile in current directory |
image |
Use pre-built image from registry |
ports |
Map container ports to host |
environment |
Set environment variables |
depends_on |
Define service dependencies |
volumes |
Mount persistent storage |
Essential Commands
Starting Services
# Start all services
docker compose up
# Start in background (detached mode)
docker compose up -d
# Start with rebuild
docker compose up --build
# Start specific service only
docker compose up app
CodeStopping Services
# Stop all services
docker compose down
# Stop and remove volumes (⚠️ deletes data!)
docker compose down -v
# Stop specific service
docker compose stop app
CodeManaging Services
# View logs
docker compose logs
docker compose logs -f app # Follow logs
docker compose logs --tail=100 # Last 100 lines
# Execute command in running container
docker compose exec app npm install package-name
# Open shell in container
docker compose exec app sh
docker compose exec db psql -U user -d myapp
# View running services
docker compose ps
# Restart services
docker compose restart
docker compose restart app
CodeBuilding & Rebuilding
# Build images
docker compose build
# Build without cache
docker compose build --no-cache
# Build specific service
docker compose build app
CodeVolume Mounting for Hot Reload
One of Docker Compose’s best features for development: live code reloading.
Bind Mounts (Development)
services:
app:
build: .
volumes:
# Mount current directory to /app in container
- .:/app
# Exclude node_modules (use container's version)
- /app/node_modules
CodeWhy this works: - Your code lives on host machine - Edit files in your favorite editor (VS Code, etc.) - Changes instantly visible in container - No rebuild needed!
Named Volumes (Persistent Data)
services:
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
CodeUse for: - Database data (survives container restarts) - Uploaded files - Cache directories - Configuration that persists
Volume Comparison
| Type | Syntax | Use Case |
|---|---|---|
| Bind Mount | .:/app |
Development (hot reload) |
| Named Volume | postgres_data:/data |
Persistent data |
| Anonymous Volume | /data |
Temporary data |
Environment Variables & Secrets
Using .env Files
Create a .env file in your project root:
# .env
DATABASE_URL=postgres://user:password@db:5432/myapp
NODE_ENV=development
API_KEY=your-secret-key
REDIS_HOST=redis
REDIS_PORT=6379
CodeReference in docker-compose.yml:
services:
app:
build: .
environment:
- DATABASE_URL=${DATABASE_URL}
- NODE_ENV=${NODE_ENV}
- API_KEY=${API_KEY}
CodeBest Practices for Secrets
❌ Don’t:
# BAD: Hardcoded secrets in docker-compose.yml
environment:
- DB_PASSWORD=supersecret123
- API_KEY=sk-1234567890
Code✅ Do:
# GOOD: Use .env file (add to .gitignore!)
environment:
- DB_PASSWORD=${DB_PASSWORD}
- API_KEY=${API_KEY}
Code✅ Better: Use Docker Secrets (for production)
services:
app:
build: .
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true
CodeCreate .env.example
Always commit a template for your team:
# .env.example (safe to commit)
DATABASE_URL=postgres://user:password@db:5432/myapp
NODE_ENV=development
API_KEY=your-api-key-here
REDIS_HOST=redis
REDIS_PORT=6379
CodeNetworking Between Services
Docker Compose automatically creates a network for your services.
Default Network
services:
app:
build: .
# Can reach 'db' service by hostname
environment:
- DATABASE_URL=postgres://user:password@db:5432/myapp
db:
image: postgres:15-alpine
CodeKey point: Services can reach each other by service name (db, redis, etc.)
Custom Networks
services:
frontend:
build: ./frontend
networks:
- public
- internal
backend:
build: ./backend
networks:
- internal
db:
image: postgres:15-alpine
networks:
- internal
networks:
public:
# Accessible from outside
internal:
# Isolated, only backend and db can communicate
CodeUse case: Isolate database from direct external access.
Network Commands
# View networks
docker compose config --services
# Inspect network
docker network inspect myapp_default
# Connect running container to network
docker network connect myapp_default container-name
CodeCommon Patterns
Pattern 1: Web App + Database
version: '3.8'
services:
web:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:password@db:5432/app
depends_on:
db:
condition: service_healthy
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=app
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
CodePattern 2: Microservices Setup
version: '3.8'
services:
api-gateway:
build: ./gateway
ports:
- "8080:8080"
depends_on:
- user-service
- order-service
user-service:
build: ./services/user
environment:
- DB_HOST=user-db
order-service:
build: ./services/order
environment:
- DB_HOST=order-db
user-db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=users
order-db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=orders
redis:
image: redis:7-alpine
# Shared cache for all services
CodePattern 3: Full-Stack Development
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
command: npm run dev
backend:
build: ./backend
ports:
- "8080:8080"
volumes:
- ./backend:/app
- /app/node_modules
command: npm run dev
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=myapp
mailhog:
image: mailhog/mailhog
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
# Test email sending without real emails!
CodeProduction vs Development Configs
Separate Compose Files
docker-compose.yml (development):
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
environment:
- NODE_ENV=development
Codedocker-compose.prod.yml (production):
version: '3.8'
services:
app:
build:
context: .
target: production # Multi-stage build
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: always
deploy:
resources:
limits:
cpus: '1'
memory: 512M
CodeUsage:
# Development (default)
docker compose up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
CodeMulti-Stage Builds
Dockerfile:
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/index.js"]
CodeBenefits: - Smaller production images - No build tools in production - Better security (non-root user)
Troubleshooting & Best Practices
Common Issues
Issue: Container won’t start
# Check logs
docker compose logs app
# Check if port is already in use
lsof -i :3000
# Rebuild without cache
docker compose up --build --force-recreate
CodeIssue: Database connection refused
# Check if db service is healthy
docker compose ps
# Test connection from app container
docker compose exec app ping db
# Check database logs
docker compose logs db
CodeIssue: Changes not reflecting
# Restart the service
docker compose restart app
# Rebuild if Dockerfile changed
docker compose up --build
# Check volume mounts
docker compose exec app ls -la /app
CodeBest Practices Checklist
Dockerfile:
- ✅ Use specific base image versions (not latest)
- ✅ Use multi-stage builds for smaller images
- ✅ Run as non-root user in production
- ✅ Use .dockerignore to exclude unnecessary files
- ✅ Optimize layer caching (COPY requirements before code)
docker-compose.yml:
- ✅ Use environment variables (not hardcoded values)
- ✅ Use named volumes for persistent data
- ✅ Add health checks for critical services
- ✅ Set resource limits in production
- ✅ Use depends_on with conditions
- ✅ Create .env.example for documentation
Development Workflow:
- ✅ Commit docker-compose.yml to git
- ✅ Add .env to .gitignore
- ✅ Use bind mounts for hot reload
- ✅ Test in production-like environment
- ✅ Document setup in README
Performance Tips
Speed up builds:
# BAD: Copies everything, breaks cache
COPY . .
RUN npm install
# GOOD: Copy package.json first, better caching
COPY package*.json ./
RUN npm install
COPY . .
CodeReduce image size:
# BAD: Large image
FROM node:18
# GOOD: Smaller Alpine image
FROM node:18-alpine
# BETTER: Multi-stage build
FROM node:18-alpine AS builder
# ... build steps ...
FROM alpine:3.18
COPY --from=builder /app/dist ./dist
CodeOptimize volumes:
# BAD: Mounts everything including node_modules
volumes:
- .:/app
# GOOD: Exclude node_modules
volumes:
- .:/app
- /app/node_modules
CodeConclusion
Docker Compose remains essential for development in 2026. It simplifies multi-container setups, ensures consistency across teams, and speeds up onboarding.
Key takeaways: 1. Use Docker Compose for local development (Kubernetes for production) 2. Always use environment variables for configuration 3. Mount volumes for hot reload during development 4. Separate development and production configs 5. Follow best practices for security and performance
Next steps:
- Containerize your current project
- Create a docker-compose.yml for your team
- Set up hot reload for faster development
- Add health checks for reliability
Docker Compose isn’t just a tool — it’s a workflow that makes development faster, more consistent, and less frustrating. Give it a try!
About the Author: This article was written with AI assistance using the AI-first development workflow. All code examples have been tested with Docker Compose v2.17+.