Suggest an editImprove this articleRefine the answer for “What are Docker Compose profiles and how to use them?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Profiles** let you tag services as opt-in. A service with `profiles:` only starts when its profile is requested via `--profile` (or `COMPOSE_PROFILES`). ```yaml services: app: image: myorg/app:1.0 # no profile = always started pgadmin: image: dpage/pgadmin4 profiles: ["debug"] # only with --profile debug loadtest: image: locustio/locust profiles: ["perf"] ``` ```bash docker compose up # app only docker compose --profile debug up # app + pgadmin docker compose --profile debug --profile perf up # all three ``` **Use cases:** dev tools (pgadmin, redis-commander), perf-test stacks, optional add-ons that you do not want running by default.Shown above the full answer for quick recall.Answer (EN)Image**Docker Compose profiles** are a way to mark services as opt-in. By default, a service in `compose.yaml` starts whenever you run `docker compose up`. Adding `profiles: ["name"]` to a service flips that: the service stays dormant unless you explicitly activate that profile. This lets you keep one Compose file that covers "dev stack", "dev stack + debug tools", "dev stack + load tests", etc., without juggling multiple files. ## Theory ### TL;DR - A service without `profiles` is **always active**. - A service with `profiles: ["x"]` is active only if profile `x` is enabled. - Enable via CLI: `docker compose --profile x up`. - Or via env: `COMPOSE_PROFILES=x docker compose up`. - Multiple profiles can be active at once (`--profile x --profile y`). - A service can belong to multiple profiles (any one matching activates it). - Dependencies (`depends_on`) of a profiled service must also be in the profile or always-on; otherwise startup fails. ### Why profiles Real-world Compose stacks accumulate optional pieces: - **Database admin UIs** (pgadmin, mongo-express, redis-commander) - **Mail catchers** (mailhog, mailpit) for dev - **Load testing** (k6, locust) - **Tracing/metrics** (Jaeger, Prometheus, Grafana) you only run when investigating - **Tools-only profiles**: a one-shot `migrate` service that runs DB migrations Without profiles, choices are: 1. **Multiple files** (`compose.yaml`, `compose.debug.yaml`) merged with `-f`. Works, gets verbose. 2. **Comment-out blocks**. Sloppy, not version-friendly. 3. **Profiles**: one file, one source of truth, opt-in execution. ### Activation rules - Compose collects all profiles activated via `--profile` or `COMPOSE_PROFILES` (comma-separated). - A service is **selected** if it has no `profiles:` key OR if any of its `profiles:` values match an activated profile. - The remaining services are ignored (not built, not pulled, not started). - Targeting a specific service by name auto-activates its profile: `docker compose up pgadmin` works even without `--profile debug` if `pgadmin` is in profile `debug`. ## Examples ### Basic dev/debug split ```yaml # compose.yaml services: app: image: myorg/app:1.0 ports: ["3000:3000"] environment: DATABASE_URL: postgres://app:app@db:5432/app depends_on: - db db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: app volumes: - dbdata:/var/lib/postgresql/data pgadmin: image: dpage/pgadmin4 profiles: ["debug"] environment: PGADMIN_DEFAULT_EMAIL: admin@local.test PGADMIN_DEFAULT_PASSWORD: admin ports: ["5050:80"] depends_on: - db volumes: dbdata: ``` Daily dev: ```bash docker compose up # starts app + db. pgadmin is dormant. ``` When you need pgadmin: ```bash docker compose --profile debug up # starts app + db + pgadmin ``` ### One-shot tool: migration runner ```yaml services: app: image: myorg/app:1.0 depends_on: db: condition: service_healthy db: image: postgres:16 healthcheck: test: ["CMD-SHELL", "pg_isready -U app"] migrate: image: myorg/app:1.0 profiles: ["tools"] command: ["npm", "run", "migrate"] depends_on: db: condition: service_healthy ``` ```bash # Run migrations once docker compose --profile tools run --rm migrate ``` The `migrate` service stays out of the regular `up`. Run it via `--profile tools run --rm migrate` and it runs once, exits, gone. ### Multiple profiles per service ```yaml services: jaeger: image: jaegertracing/all-in-one profiles: ["tracing", "observability"] ports: ["16686:16686"] ``` Activated by either `--profile tracing` or `--profile observability`. ### Combining profiles ```yaml services: app: image: myorg/app:1.0 db: image: postgres:16 redis: image: redis:7 profiles: ["cache"] worker: image: myorg/app:1.0 command: ["npm", "run", "worker"] profiles: ["workers"] loadtest: image: locustio/locust profiles: ["perf"] ``` ```bash # minimal docker compose up # with cache + workers docker compose --profile cache --profile workers up # perf benchmarks docker compose --profile cache --profile workers --profile perf up # via env (CI-friendly) COMPOSE_PROFILES=cache,workers docker compose up ``` ### Profiles + depends_on ```yaml services: app: depends_on: ["db"] db: image: postgres:16 pgadmin: image: dpage/pgadmin4 profiles: ["debug"] depends_on: ["db"] # OK: db is always-active pgadmin-monitor: image: myorg/pgadmin-monitor profiles: ["debug"] depends_on: ["pgadmin"] # OK: pgadmin is in same profile ``` If a profiled service depends on another profiled service that is **not** activated, Compose errors: ```yaml services: app: depends_on: ["redis"] # error: app is always-active, redis is profiled redis: profiles: ["cache"] ``` Fix: either always-activate `redis` (remove its profile), or move `app` into the `cache` profile too. ### CI-friendly: env-driven ```yaml # compose.yaml — same file used for dev and CI services: app: image: myorg/app:1.0 db: image: postgres:16 e2e: image: myorg/e2e-tests profiles: ["e2e"] depends_on: ["app"] ``` ```yaml # .github/workflows/test.yml - run: docker compose up -d # app + db - run: COMPOSE_PROFILES=e2e docker compose run --rm e2e - run: docker compose down ``` Dev gets a fast `docker compose up`; CI activates `e2e` to run the test suite. ## Real-world usage - **Internal-tool inclusion**: pgadmin, redis-commander, queue-monitor — opt-in. - **Heavy add-ons**: ELK stack for logs, Jaeger for traces — turn on when investigating. - **One-shot runs**: migrations, seed-data, cron-jobs — `profiles: ["tools"]`, run via `compose run --rm`. - **Multi-environment configs**: `dev`, `staging`, `e2e` profiles, swapping which services participate. - **Optional dependencies**: caching layer that can be disabled in dev for speed. ### Common mistakes **Forgetting `depends_on` constraints** ```yaml services: app: depends_on: ["queue"] queue: profiles: ["workers"] ``` `docker compose up` fails: `app` is always-active but its dependency is not. Either remove the profile from `queue` or move `app` to the same profile. **Using profiles when overrides are simpler** For environment-specific config (different image tags, different env vars), `compose.override.yaml` or `-f compose.staging.yaml` is more idiomatic. Profiles are best for **opting in optional services**, not transforming the same service across environments. **Activating profiles surprises** ```bash docker compose up pgadmin # pgadmin's profile is auto-activated. Other dev tools in the same profile also start. ``` If you mean to start only one profiled service, target it explicitly. If others in its profile come along, that is intentional (they share the profile). **Forgetting `--profile` on `down`** ```bash docker compose --profile debug up # 4 services running docker compose down # only the always-active 3 are stopped ``` `down` only stops what `up` would start with the current profiles. Match the profile flags on both ends, or use `docker compose down --remove-orphans` to catch the rest. ### Follow-up questions **Q:** Can a service be in multiple profiles? **A:** Yes. Activating any one of them turns the service on. **Q:** Does `docker compose ps` show profiled services that are dormant? **A:** No, only services that are part of the current selection. To see all defined services, use `docker compose config --services`. **Q:** Can a profile activate other profiles? **A:** No. Profiles are a flat list. If you want a meta-profile, activate multiple via env var. **Q:** What happens with `docker compose build` and profiles? **A:** Same selection rules. Without `--profile`, only always-active services build. With `--profile name`, the matching profiled services build too. **Q:** (Senior) When should profiles fall short, and what is the alternative? **A:** Profiles are great for opt-in services. They struggle when you need *different* config for the same service across environments (different image tags, replicas, env vars). Then prefer multiple Compose files merged via `-f compose.yaml -f compose.prod.yaml`. Each file can override or extend the previous. Profiles + override files are not mutually exclusive; they solve different problems. **Q:** (Senior) How do profiles interact with `docker compose watch`? **A:** `watch` only monitors services that are part of the active selection. If you `docker compose --profile debug watch`, debug-only services are watched too. Without the profile, they are ignored. So profiles let you scope watch to the relevant subset.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.