Managing multi-container development environments has always been a challenge. Docker Compose has evolved significantly, and two features — profiles and watch mode — have transformed how developers run services locally. This guide walks you through both features with practical examples you can use today.
Why Profiles and Watch Mode Matter
In a typical project, you might have a web app, API server, database, Redis cache, monitoring stack, and debug tools. You don’t always need all of them running. Profiles let you group services and start only what you need. Watch mode automatically syncs file changes into containers without rebuilding — replacing clunky volume mounts.
Understanding Docker Compose Profiles
Profiles let you tag services so they only start when explicitly requested. Services without a profile always start. Here’s a real-world example:
# docker-compose.yml
services:
api:
build: ./api
ports:
- "3000:3000"
depends_on:
- postgres
frontend:
build: ./frontend
ports:
- "5173:5173"
postgres:
image: postgres:17
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: devpass
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
profiles: ["cache"]
ports:
- "6379:6379"
mailhog:
image: mailhog/mailhog
profiles: ["email"]
ports:
- "8025:8025"
pgadmin:
image: dpage/pgadmin4
profiles: ["debug"]
environment:
PGADMIN_DEFAULT_EMAIL: dev@local.com
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
prometheus:
image: prom/prometheus
profiles: ["monitoring"]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
profiles: ["monitoring"]
ports:
- "3001:3000"
volumes:
pgdata:Running Specific Profiles
With this setup, docker compose up only starts api, frontend, and postgres — the core services. To include optional services:
# Start core + caching
docker compose --profile cache up
# Start core + debugging tools
docker compose --profile debug up
# Start core + monitoring stack
docker compose --profile monitoring up
# Combine multiple profiles
docker compose --profile cache --profile monitoring up
# Start everything
docker compose --profile cache --profile email --profile debug --profile monitoring upProfile Tips
- A service can belong to multiple profiles:
profiles: ["debug", "monitoring"] - Services without profiles are always started — use this for core infrastructure
- Set default profiles via environment variable:
COMPOSE_PROFILES=cache,debug - Add this to your
.envfile so your team shares the same defaults
Docker Compose Watch Mode
Watch mode (docker compose watch) monitors your source files and automatically updates running containers. It supports three actions:
The Three Watch Actions
- sync — Copies changed files into the container (like a smart volume mount)
- rebuild — Triggers a full image rebuild when specific files change
- sync+restart — Syncs files and restarts the container (for config changes)
services:
api:
build: ./api
ports:
- "3000:3000"
develop:
watch:
# Sync source code changes instantly
- action: sync
path: ./api/src
target: /app/src
ignore:
- "**/*.test.ts"
- "**/__mocks__"
# Rebuild when dependencies change
- action: rebuild
path: ./api/package.json
# Sync and restart for config changes
- action: sync+restart
path: ./api/config
target: /app/config
frontend:
build: ./frontend
ports:
- "5173:5173"
develop:
watch:
- action: sync
path: ./frontend/src
target: /app/src
- action: rebuild
path: ./frontend/package.jsonStarting Watch Mode
# Start services and watch for changes
docker compose watch
# Or start detached, then watch
docker compose up -d
docker compose watchWhy Watch Mode Beats Volume Mounts
Volume mounts have been the go-to for local development, but they come with problems:
- Performance on macOS/Windows: File system notifications across the VM boundary are slow and unreliable
- node_modules conflicts: Mounting your entire project overwrites container-installed dependencies
- Platform differences: Native modules compiled on your host may not work inside the container
- Permission issues: UID/GID mismatches between host and container cause access errors
Watch mode avoids all of these by copying files into the container’s own filesystem. Changes are fast, dependencies stay intact, and platform-specific builds work correctly.
Combining Profiles and Watch for a Complete Workflow
Here’s the full developer workflow:
# Morning: Start core services + cache
docker compose --profile cache up -d
# Start watching for code changes
docker compose --profile cache watch
# Need to debug a database issue? Add pgadmin
docker compose --profile cache --profile debug up -d pgadmin
# End of sprint: Test with full monitoring
docker compose --profile cache --profile monitoring watch
# Clean up everything
docker compose --profile cache --profile debug --profile monitoring downPro Tips for Production-Ready Compose Files
1. Use .env for Team Defaults
# .env
COMPOSE_PROFILES=cache
POSTGRES_PASSWORD=devpass
API_PORT=30002. Create a Makefile for Common Commands
# Makefile
.PHONY: dev dev-full debug
dev:
docker compose up -d && docker compose watch
dev-full:
docker compose --profile cache --profile email up -d && \
docker compose --profile cache --profile email watch
debug:
docker compose --profile debug --profile monitoring up -d3. Health Checks for Reliable Startup
postgres:
image: postgres:17
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5This ensures dependent services wait for Postgres to be truly ready, not just running.
Conclusion
Docker Compose profiles and watch mode eliminate the two biggest pain points of container-based development: running too many services and slow feedback loops. Profiles keep your resource usage lean by only starting what you need, while watch mode gives you instant code syncing without the headaches of volume mounts. Add both to your docker-compose.yml today — your laptop’s fans will thank you.

Leave a Reply