Docker Compose Profiles in 2026: Manage Multi-Service Dev Environments Like a Pro

Managing complex multi-service applications during development is a pain point every DevOps engineer and backend developer knows well. You spin up a database, a cache layer, a message queue, maybe a monitoring stack — and suddenly your docker-compose up launches 12 containers when you only needed three. Docker Compose profiles, introduced in Compose v2 and now mature in 2026, solve this elegantly by letting you selectively activate services based on the task at hand.

What Are Docker Compose Profiles?

Profiles let you tag services in your compose.yaml so they only start when explicitly requested. Services without a profile always start. Services with a profile only start when that profile is activated via --profile or the COMPOSE_PROFILES environment variable.

Think of it like feature flags for your infrastructure — you define everything in one file but activate only what you need.

Basic Setup: A Real-World Example

Let’s say you’re building a SaaS app with the following services:

  • API server (always needed)
  • PostgreSQL (always needed)
  • Redis (always needed)
  • Celery worker (needed for async tasks)
  • Mailhog (only for email testing)
  • Prometheus + Grafana (only for monitoring/debugging)
  • pgAdmin (only for database debugging)

Here’s how you structure your compose.yaml:

services:
  api:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - postgres
      - redis
    environment:
      - DATABASE_URL=postgresql://app:secret@postgres:5432/appdb
      - REDIS_URL=redis://redis:6379/0

  postgres:
    image: postgres:17
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  worker:
    build: .
    command: celery -A app worker -l info
    profiles: ["worker"]
    depends_on:
      - postgres
      - redis

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    profiles: ["mail"]

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    profiles: ["monitoring"]

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    profiles: ["monitoring"]

  pgadmin:
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@local.dev
      PGADMIN_DEFAULT_PASSWORD: admin
    ports:
      - "5050:80"
    profiles: ["debug"]

volumes:
  pgdata:

Using Profiles in Practice

Start Only Core Services

docker compose up

This starts only api, postgres, and redis — the services without profiles. Fast and lightweight.

Add the Background Worker

docker compose --profile worker up

Now you get the core services plus the Celery worker.

Activate Multiple Profiles

docker compose --profile worker --profile monitoring up

This starts core + worker + Prometheus + Grafana. Note that Grafana’s depends_on: prometheus is automatically handled.

Use Environment Variables

Instead of passing flags every time, set the COMPOSE_PROFILES variable:

# In your .env file or shell
COMPOSE_PROFILES=worker,mail

# Now just run:
docker compose up

Advanced Pattern: Per-Developer Profiles with .env

Here’s a pattern I love for teams. Create a .env.example that developers copy:

# .env.example — copy to .env and uncomment what you need
# COMPOSE_PROFILES=worker
# COMPOSE_PROFILES=worker,monitoring
# COMPOSE_PROFILES=worker,mail,debug

Each developer activates only what they need. The frontend dev doesn’t load monitoring. The backend dev doesn’t load pgAdmin. Everyone’s laptop stays cool.

Profile-Aware Commands

Profiles also affect other Compose commands:

# Stop only monitoring containers
docker compose --profile monitoring stop

# View logs for worker profile services
docker compose --profile worker logs -f

# Rebuild only debug-profile services
docker compose --profile debug build

One gotcha: docker compose down without a profile flag will not remove profiled containers that are running. Use docker compose --profile '*' down to tear down everything:

# Nuclear option: stop and remove ALL services
docker compose --profile '*' down -v

Combining Profiles with Compose Watch

In 2026, docker compose watch is the standard for hot-reload during development. Profiles work seamlessly with it:

services:
  api:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./requirements.txt

  worker:
    build: .
    profiles: ["worker"]
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
# Hot-reload both API and worker
docker compose --profile worker watch

Profile Naming Conventions

Adopt a consistent naming scheme across projects:

  • worker — background job processors
  • monitoring — Prometheus, Grafana, Jaeger
  • debug — pgAdmin, Redis Commander, debug tools
  • mail — Mailhog, mail testing
  • test — test databases, mock services
  • full — everything (assign to all optional services as a second profile)

The full profile trick is especially useful:

  worker:
    profiles: ["worker", "full"]
  mailhog:
    profiles: ["mail", "full"]
  prometheus:
    profiles: ["monitoring", "full"]
# Start absolutely everything
docker compose --profile full up

Common Mistakes to Avoid

  • Profiling your database: Don’t put core dependencies behind profiles. If your API needs Postgres, Postgres should always start.
  • Forgetting depends_on chains: If service A (no profile) depends on service B (has profile), Compose will error when B isn’t activated. Keep dependency chains profile-aware.
  • Overusing profiles: If you have 15 profiles, you’ve recreated the complexity you were trying to avoid. Keep it under 5-6.

Wrapping Up

Docker Compose profiles transform unwieldy multi-service setups into something manageable. You get one canonical compose.yaml that describes your entire stack, but each developer — or each task — only activates the slice they need. Combined with Compose Watch for hot-reload and environment-based activation, profiles are a must-know feature for any DevOps workflow in 2026.

Start by auditing your current compose.yaml — identify services that aren’t always needed, tag them with profiles, and watch your docker compose up time drop dramatically.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Privacy Policy · Contact · Sitemap

© 7Tech – Programming and Tech Tutorials