Docker Compose Integration for Multi-Service Apps
Introduction
Complex applications require multiple containerized services (databases, message queues, cache layers). This section covers integrating Docker Compose within DevContainer environments to orchestrate multi-service deployments while maintaining deterministic configuration.
Sections
1. Docker Compose Architecture & Service Topology
Define docker-compose.yml files in .devcontainer/ to declare application services. Reference Compose files in devcontainer.json via dockerComposeFile property.
Design service networks explicitly using user-defined bridge networks. Service names automatically resolve to container IPs within the network. Avoid hardcoded IPs—define service dependencies via Docker DNS.
2. Service Orchestration & Volume Management
Mount application code volumes with cached or delegated consistency modes to optimize performance on Mac/Windows hosts. Use named volumes for database data to persist across container rebuilds.
Define port forwarding via forwardPorts in devcontainer.json to expose services to the host. Configure auto-forwarding notifications for developer awareness.
3. Networking & Inter-Service Communication
Docker Compose creates isolated networks for services. Containers within a network communicate via service names (DNS resolution). External services access containers via forwarded ports or host networking.
Configure DNS resolution explicitly when services require cross-network communication. Document service discovery patterns for developers.
4. Compose Override & Environment Variables
Create docker-compose.override.yml for local development overrides (mount points, logging verbosity). Keep docker-compose.yml as the source of truth for production-like configurations.
Use .env files to inject environment variables. Never commit secrets—use .env.example as a template.
Code Blocks
devcontainer.json with Compose integration
{
"name": "Multi-Service Dev Environment",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"forwardPorts": [3000, 5432],
"portsAttributes": {
"3000": { "label": "Application" },
"5432": { "label": "PostgreSQL" }
},
"postCreateCommand": "npm ci && npm run build"
}
docker-compose.yml with services
version: '3.9'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspace:cached
- /workspace/node_modules
ports:
- "3000:3000"
environment:
DATABASE_URL: "postgresql://postgres:password@db:5432/app"
REDIS_URL: "redis://redis:6379"
depends_on:
- db
- redis
networks:
- app-network
db:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: password
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
networks:
- app-network
redis:
image: redis:7-alpine
networks:
- app-network
volumes:
db-data:
networks:
app-network:
driver: bridge
Lifecycle hook for service health checks
#!/usr/bin/env bash
set -euo pipefail
# Wait for PostgreSQL to be ready
until pg_isready -h db -p 5432 -U postgres; do
echo "Waiting for PostgreSQL..."
sleep 1
done
# Run database migrations
npm run migrate:up
# Wait for Redis
until redis-cli -h redis ping; do
echo "Waiting for Redis..."
sleep 1
done
echo "✓ All services ready"
Common Pitfalls
- Hardcoded service IPs: Using IP addresses instead of service names breaks DNS resolution. Always reference services by name.
- Missing health checks: Services taking time to initialize can cause race conditions. Use health checks or explicit wait commands.
- Volume mount conflicts: Bind-mounting large directories causes performance issues. Use named volumes or
cachedconsistency mode. - Environment variable leakage: Committing
.envfiles with secrets to Git compromises security. Use.env.exampleinstead. - Network isolation violations: Using
network_mode: hostbypasses service isolation. Keep services on defined bridges.
FAQ
How do I connect to services from my IDE debugger?
Forward service ports via forwardPorts in devcontainer.json. Configure debugger to connect to localhost:PORT (not service name). This routes connections through the host.
What’s the difference between docker-compose.yml and docker-compose.override.yml?
docker-compose.yml is committed to Git and defines production-like configurations. docker-compose.override.yml is local-only and provides development overrides (extra logging, mount points, etc.).