Docker Compose profiles let you keep the services you always need and the ones you only turn on occasionally in a single file. Instead of maintaining one docker-compose.yml for production and another for development, you tag each service with a profile and decide at start time which ones to bring up. This guide covers the profiles attribute, how to enable it and how it interacts with dependencies. The same explanation is available in Spanish.

Key takeaways

  • A profile is a label you attach to a service with the profiles attribute; that service only starts when you enable its profile.
  • Services without a profiles attribute are always enabled and come up with a normal docker compose up.
  • Profiles are enabled with the --profile flag or the COMPOSE_PROFILES environment variable; you can enable several at once.
  • The feature arrived in Docker Compose 1.28.0 (January 2021) and works the same in version 2 of the docker compose plugin.
  • If you name a profiled service on the command line, Compose starts it even without enabling the profile, together with its dependencies.

What problem do profiles solve?

A single project often needs different services depending on the context. In production you want only the application and its database, but on your laptop a mail catcher, a web database UI or a one-shot backup container come in handy. The traditional answer was to keep several files and chain them with -f, which multiplies maintenance and lets them drift apart.

Profiles solve this inside one docker-compose.yml. You tag the accessory services with a label and they stay idle: docker compose up never touches them. When you need them, you enable their profile and they appear. The official docs put it plainly: services without the profiles attribute are always enabled, and the rest run only when their profile is active. It is the same selective-startup philosophy we apply to restart policies and healthchecks to control each container’s lifecycle.

How to assign services to a profile

The profiles attribute goes inside each service definition and takes a list of names. A valid profile name follows the pattern [a-zA-Z0-9][a-zA-Z0-9_.-]+: letters, digits, hyphens, dots and underscores. This example keeps app and db always on and hides three services behind the debug, dev and tools profiles:

services:
  app:
    image: nginx:1.27-alpine
    restart: unless-stopped
    ports:
      - "8080:80"
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16.4-alpine
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: change_this_secret
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  adminer:
    image: adminer:4.8.1
    restart: unless-stopped
    profiles: ["debug"]
    ports:
      - "8081:8080"
    depends_on:
      - db

  mailpit:
    image: axllent/mailpit:v1.20
    restart: unless-stopped
    profiles: ["dev"]
    ports:
      - "8025:8025"

  backup:
    image: postgres:16.4-alpine
    profiles: ["tools"]
    entrypoint: ["pg_dump", "-h", "db", "-U", "postgres", "app"]
    depends_on:
      - db

volumes:
  db_data:

Here app is a sample web server and db a PostgreSQL database with its healthcheck; neither carries profiles, so they always start. adminer (web database UI), mailpit (test mailbox) and backup (a one-shot pg_dump) are reserved for their profiles. A service can belong to several profiles at once: just list more names, for example profiles: ["debug", "dev"].

We follow the Compose v2 convention: no version: key, named volumes, restart: unless-stopped and a healthcheck on the database. If you expose any of these services to the internet, put each one behind a reverse proxy and pair profiles with sensible restart policies and healthchecks.

Enabling profiles with –profile and COMPOSE_PROFILES

With the file above, a normal start brings up only the core:

docker compose up -d

To turn on the services of a profile, use the --profile flag before the subcommand. You can repeat it to enable several, or pass a comma-separated list in COMPOSE_PROFILES:

docker compose --profile debug up -d
docker compose --profile debug --profile dev up -d
COMPOSE_PROFILES=debug,dev docker compose up -d

The "*" wildcard enables every profile at once, handy to bring the full project up or to stop it entirely without leaving orphan containers:

docker compose --profile "*" up -d
docker compose --profile "*" down

Watch out on shutdown: docker compose down only stops the services of the profiles active at that moment. If you started adminer with --profile debug and then run down without that profile, the adminer container keeps running. That is why you should shut down with the same profile (or with "*") you started with.

Use cases: dev, debug and optional services

Profiles shine in three common self-hosting situations:

  • Development environment (dev): tools you only want on your machine, such as Mailpit to catch outgoing mail or a hot-reload service. You never enable that profile on the production server.
  • Debugging (debug): diagnostic utilities like Adminer, a pgweb or a container with curl and dig. You switch them on when something breaks and off when you finish, without leaving them permanently exposed.
  • One-off tasks (tools): single-use containers, such as a pg_dump for backups or a migration script. They pair well with docker compose run, which starts the service, runs its command and exits.

This last case leads to an important perk: when you name a profiled service directly on the command line, Compose starts it without you having to enable its profile by hand.

docker compose run --rm backup

That command brings up backup (profile tools) and, since it depends on db, also makes sure the database is available, all without writing --profile tools.

Combining profiles and dependencies

The relationship between profiles and depends_on has one rule worth keeping clear: an always-on service cannot depend on a service hidden behind an inactive profile. If app declared depends_on: [adminer] and you did not enable the debug profile, Compose would fail because the dependency does not exist in that run. The fix is for both to share a profile or for the dependency to carry none.

By contrast, when you start a profiled service, its depends_on dependencies come up with it even if they sit in another profile or none. That is why in the example adminer and backup can depend on db without trouble: db has no profile, so it is always available. Combine this with resource limits and good log management and you get a single file able to serve production, development and maintenance without duplicating anything.

One final piece of advice: do not treat profiles as security. A service being in the debug profile does not stop anyone from starting it; it only keeps it from coming up by default. For sensitive services, combine profiles with authentication and with not publishing their ports to the outside.

Frequently asked questions

Do services without a profile always start?

Yes. Any service that does not declare the profiles attribute is always enabled and comes up with a normal docker compose up, regardless of which profiles you activate. Profiles only affect services that carry the label.

Can I enable every profile at once?

Yes, with the --profile "*" wildcard. It enables all profiles defined in the file in a single command, for both up and down. It is the most convenient way to bring the whole project up or down when you have many optional services.

Since which version have profiles existed in Docker Compose?

Profiles arrived in Docker Compose 1.28.0, released in January 2021, and work the same in version 2 of the docker compose plugin, which is what you will use today on any recent install. You do not need the version: key in the file.

Conclusion

Profiles turn a docker-compose.yml into a flexible definition that adapts to the context: production, development or maintenance, all in one place. Tag accessory services with profiles, enable them with --profile or COMPOSE_PROFILES when you need them, and let profile-less dependencies always start. The natural next step is to review healthchecks and restart policies so that every service, active or not, behaves predictably.

Sources

  1. Docker Docs: Using profiles with Compose
  2. compose-spec: profiles attribute (GitHub)
  3. freeCodeCamp: Docker Compose for production

Route: Self-hosting with Docker: from zero to production