Docker & ContainersIX ยท Docker ComposeCompose profiles
Profiles โ optional services in one file
What you'll learn
- Assign profiles and predict exactly which services a given command starts
- Explain why `docker compose down` can leave containers running
- Handle `depends_on` edges that cross a profile boundary
- Choose between profiles and override files for the job in front of you
Prerequisites
Verified against Docker Engine 29.x ยท Docker Engine 28.x ยท Docker Compose 2.x ยท containerd 2.x ยท runc 1.2.x ยท BuildKit 0.20+ ยท Linux kernel 5.15+ ยท Ubuntu 24.04 LTS ยท Debian 12 (Bookworm) ยท 2026-08-12
A profile is a label on a service that decides whether that service is included when Compose resolves the project. It lets one file carry optional services โ debug tools, exporters, one-off jobs โ without starting them every time.
The rule is short enough to memorise and is the thing most people get backwards:
A service with no
profiles:attribute is always enabled. A service with aprofiles:attribute is enabled only when one of its profiles is active.
Enabling a profile adds services. It never removes any.
Defining profiles
name: shop
services:
web:
image: nginx:1.27
ports: ["8080:80"]
api:
image: myorg/api:1.4.0
# Opt-in: a Prometheus exporter nobody needs by default
exporter:
image: prom/node-exporter:v1.8.2
profiles: [metrics]
# Opt-in and dangerous: see the callout below
debug-tools:
image: myorg/debug-tools:1
profiles: [debug]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
web and api start on every docker compose up, with or without any
profile flag. exporter starts only when metrics is active. debug-tools
only when debug is.
Activating profiles
# Default: web and api only
docker compose up -d
# Add the debug profile
docker compose --profile debug up -d
# Multiple profiles
docker compose --profile debug --profile metrics up -d
# Everything, including every profiled service
docker compose --profile "*" up -d
The environment-variable form takes a comma-separated list and is what CI systems usually use, because it survives being set once for a whole job:
export COMPOSE_PROFILES=debug,metrics
docker compose up -d
Profile names must match [a-zA-Z0-9][a-zA-Z0-9_.-]+, which among other
things means a single-character profile name is invalid.
PROJECT=shop
# What is actually running under this project, profiles or not
docker ps -a --filter "label=com.docker.compose.project=$PROJECT" \
--format '{{.Label "com.docker.compose.service"}}' | sort -u > /tmp/running.txt
# What a plain up would consider part of the project
docker compose config --services | sort > /tmp/declared.txt
comm -23 /tmp/running.txt /tmp/declared.txtAnything printed by that comm is a container the daemon is running that a
plain docker compose down will not touch. On a clean stack it prints
nothing.
Dependencies across a profile boundary
Profiles cut depends_on edges, and the failure is loud in one direction
and quiet in the other.
services:
api:
image: myorg/api:1.4.0
depends_on:
migrate:
condition: service_completed_successfully
migrate:
image: myorg/api:1.4.0
command: ["./migrate", "up"]
profiles: [tools] # <-- api can never start without --profile tools
api is always enabled and depends on a service that is not. A plain
docker compose up -d cannot satisfy that dependency, so it fails rather
than silently skipping it. The documented rule is that a gated dependency
must be in the same profile as its dependent, started separately, or not
profiled at all.
The useful half of the same mechanism: explicitly targeting a profiled
service auto-enables its profiles, along with its depends_on
dependencies. That is what makes one-off jobs pleasant:
# Runs migrate and starts db, even though 'tools' was never enabled
docker compose run --rm migrateNote the limit: only the targeted service and its declared dependencies
start. Other services sharing the tools profile stay down unless they are
targeted too or the profile is enabled outright.
What profiles are good for
- Debug and admin tools โ a database console, a shell container, a profiler sidecar.
- Optional observability โ exporters, tracing agents, log forwarders you want on some hosts and not others.
- One-off jobs โ migrations, seed loaders, backup runners, invoked with
docker compose run. - Alternate backends โ a real Postgres for integration tests versus an in-memory stand-in for unit tests.
- Hardware-conditional services โ anything needing a GPU or a specific device that only some hosts have.
What profiles are not for
Environment differences. This is the common misuse and it does not work the way people hope:
# Wrong: a profile is not a security boundary and this file is in git
services:
api:
profiles: [prod]
environment:
DB_PASSWORD: "REPLACE_ME"
A profile decides whether a service is started. It does nothing to hide
its configuration: anyone with the file can read the value, and
docker compose --profile prod config will print it back for them. Nothing
about a profile is a boundary.
The tool for environment differences is override files:
# compose.yml โ the shape of the application
services:
api:
image: myorg/api:1.4.0
# compose.prod.yml โ what is different in production
services:
api:
environment:
DATABASE_URL: ${PROD_DATABASE_URL}
deploy:
resources:
limits:
memory: 2g
docker compose -f compose.yml -f compose.prod.yml up -d
The override file can live outside version control, be templated by your
config management, or be generated per host. It also composes with profiles
rather than competing with them โ a production override plus a metrics
profile is a perfectly normal combination.
Knowledge check
Knowledge check ยท 5 questions
Q1. Services without a `profiles:` key are skipped when you run `docker compose --profile debug up`.
Q2. A stack was started with `docker compose --profile debug up -d`. A plain `docker compose down` is then run. What happens to the debug-profiled container?
Q3. An always-enabled `api` service has `depends_on` a `migrate` service that is gated behind the `tools` profile. What happens on a plain `docker compose up -d`?
Q4. Which are appropriate uses of profiles? Select all that apply.
Q5. You set `COMPOSE_PROFILES=mtrics` (a typo) in a CI job. What does Compose do?
Passing score: 75%. Answers are checked in this browser.