← Blog
Three ceramic tabs in a cream base, with a raised green center tab and coral selector knob on a lavender background.
Published on
2026-11-02

Docker Compose profiles: optional services and dependencies

Docker Compose profiles let you keep optional services in one Compose file and select them when needed. A web application can start normally while a diagnostic tool stays inactive until you ask for it. The important part is understanding how profile selection interacts with explicit service names and dependencies.

This guide uses a small local example to compare those choices. You can inspect every configuration without starting containers. Runtime commands are included for a separate test environment; we checked the configuration models with Docker Compose v5.1.4 but did not pull images or run the containers.

How Docker Compose profiles select services

Put profiles under a service and give it a list of names. Services without that attribute remain enabled. A service with several profiles becomes available when any matching profile is active; it does not require all of them.

The Compose profile specification also distinguishes service selection from other top-level definitions. Profiles do not conditionally remove top-level network or volume declarations. Keep required services outside profiles and assign profiles to the optional tasks around them.

A Compose file with optional diagnostic tools

Create a new directory and save this as compose.yaml. Use the current docker compose plugin and a Docker Engine configured for Linux containers if you intend to run it. Choose an unused local port if 8089 is already occupied.

services:
  web:
    image: nginx:stable-alpine
    ports:
      - "127.0.0.1:8089:80"
  probe:
    image: alpine:3.22
    profiles: [debug]
    command: ["wget", "-qO-", "http://web:80"]
    depends_on: [web]
  toolbox:
    image: alpine:3.22
    profiles: [debug, ops]
    command: ["sleep", "3600"]

The web service is the core application. The one-off probe requests its default page. The toolbox service stays running for one hour so it can serve as a temporary diagnostic container. The example uses readable image tags; record tested versions or digests for a repeatable deployment.

The probe reaches web:80 through the project network. A browser on the host uses http://127.0.0.1:8089. Our Compose networking guide explains why these are different addresses.

Inspect the active model before starting anything

From the example directory, run:

docker compose -p profiles-demo config --profiles
docker compose -p profiles-demo config --services
docker compose -p profiles-demo --profile debug config --services
docker compose -p profiles-demo --profile ops config --services

docker compose config resolves the configuration. The --profiles option lists declared profile names; it is not a list of running containers. The --services option shows the services in the selected model.

SelectionServices in this example’s model
No profileweb
debugweb, probe, toolbox
opsweb, toolbox
debug and opsweb, probe, toolbox

These combinations passed our local configuration checks. Their service order in command output is not significant. Use a clean test directory without an inherited COMPOSE_PROFILES setting or an extra Compose override file when reproducing the no-profile row.

Start a profile or target one service

For a runtime test, choose the first command for the core service alone, or the second for the debug selection:

docker compose -p profiles-demo up -d
docker compose -p profiles-demo --profile debug up -d

up -d creates and starts the selected application in the background. In the debug selection, the probe exits after its request, while the web and toolbox services can remain running. A selected service does not have to be a long-running daemon.

To run only the diagnostic task and its declared dependency, explicitly target it:

docker compose -p profiles-demo run --rm probe
docker compose -p profiles-demo --profile "*" ps -a

Docker’s profile guide explains this exception: targeting a profiled service makes that service available without a separate profile flag. With no other profile selection, running probe brings in web through depends_on; it does not start toolbox merely because both share debug. Containers left running from an earlier command are separate state, so start from a clean project when comparing these outcomes.

The run command creates a one-off container; --rm removes that container after exit. It does not clean up the dependency it started. It also does not publish the service’s configured ports by default. This probe needs no inbound port.

Keep profile selection separate from readiness

The short depends_on: [web] form establishes startup order. It does not prove that Nginx has begun accepting requests. The probe has no retry loop, so a connection error during startup can be a readiness problem even when profile selection is correct.

For this exercise, verify the web page in the host browser and rerun the probe if necessary. For application dependencies, use an appropriate health check with condition: service_healthy where required, and retain application retry behavior for later failures. Docker’s startup-order documentation and our Compose healthcheck guide cover that distinction.

Use COMPOSE_PROFILES deliberately

You can repeat the CLI flag, for example --profile debug --profile ops. To persist that selection for a project, the COMPOSE_PROFILES variable accepts a comma-separated list. A project .env file can contain:

COMPOSE_PROFILES=debug,ops

This is input to the Compose command. Placing it inside a service’s environment section only sets a variable in that container; it does not tell the host-side Compose invocation which profiles to select. Our environment-variable guide explains the different roles of .env, service environment values, and env_file.

Remove the example setting when you finish. A forgotten shell or project setting can make optional tools appear to start by default. In shared scripts, make the intended profiles visible in the command or document where the selection comes from.

A dependency in another inactive profile can invalidate the model

Save this separate demonstration as cross-profile.yaml. The service called database is only a placeholder container for testing dependency selection, not a database server.

services:
  web:
    image: nginx:stable-alpine
  database:
    image: alpine:3.22
    profiles: [data]
    command: ["sleep", "3600"]
  task:
    image: alpine:3.22
    profiles: [tools]
    command: ["echo", "example"]
    depends_on: [database]

Compare the two configuration checks:

docker compose -p profiles-demo -f cross-profile.yaml --profile tools config --quiet
docker compose -p profiles-demo -f cross-profile.yaml --profile tools --profile data config --quiet

The first fails because task requires database, but the data profile is inactive. The second enables both profiles and passes. We reproduced both outcomes locally. Resolve this by making a required dependency part of the core model, giving related optional services a shared profile, or deliberately activating both profiles. Do not assume depends_on overrides every profile restriction.

Stop optional tools without stopping the application

These commands have different scopes. The first stops only the toolbox container. The second removes the disposable example project across all profiles:

docker compose -p profiles-demo --profile debug stop toolbox
docker compose -p profiles-demo --profile "*" down

stop leaves the container in place. down removes the selected project’s containers and managed networks. Quote the wildcard so the shell passes it to Compose unchanged, and check the project name before running cleanup.

A profile-specific down also includes services without profiles. It is therefore unsuitable when your aim is simply to stop one optional tool while keeping the core application running. Use an explicit service target for that narrower task.

When to use an override file instead

Profiles work well when you want to include or omit whole services. If the same service needs a different command, port mapping, mount, or image in another environment, use an override file and inspect the merged result. A profile name alone does not change those fields.

Treat profiles as a configuration convenience, not an authorization boundary. Anyone able to run the relevant Compose project can explicitly select an optional service. Keep credentials, network exposure, and permissions appropriate to the service itself.

For each supported profile combination, keep a short record of the expected service set and a configuration check. Then test startup, readiness, and cleanup on a disposable runtime. A valid model gives you a clear deployment plan; it does not prove that images are available or that the application will work.

Your next workload belongs on Hivenet.

Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.