Hello! Welcome to the next lesson in our Docker series.
In our last session, we focused on managing application stability and performance by configuring CPU and memory limits for your services. Now that you know how to constrain individual services, let's explore how to manage which groups of services run in the first place.
Today's learning outcome is to use Compose profiles to selectively enable and disable groups of services. This is a powerful feature that helps you maintain a single, clean docker-compose.yml file while adapting it for different environments like development, testing, and production. It's a core concept for practicing Infrastructure as Code and keeping your configurations DRY (Don't Repeat Yourself).
1. What Are Docker Compose Profiles?
Imagine you have services that you only need for specific tasks:
- A database admin tool like
phpmyadminorpgadminfor development. - An email-catching service like
mailhogfor local testing. - A separate service that runs your integration test suite.
- A monitoring stack (
prometheus,grafana) that you might not want running all the time.
In the past, you might have managed this by commenting out services in your docker-compose.yml, using multiple override files, or maintaining separate files altogether (docker-compose.dev.yml, docker-compose.test.yml). These approaches can lead to configuration drift and become difficult to manage.
Compose profiles solve this by letting you assign one or more "profile" names to a service. A service with a profile will only start if that profile is explicitly activated.
- Services without a
profileskey are considered part of a default group; they are enabled when you rundocker compose upwithout any profile flags. - Services with a
profileskey are disabled by default and only run when their profile is active.
Let's look at a visual example.

This image neatly illustrates the core idea: one file, multiple configurations, activated on demand. This is perfect for a situation where you might use a standard CPU-based service for development and testing, but a more powerful GPU-based service in production.
2. Defining and Activating Profiles
The syntax is straightforward. You add a profiles key to any service, followed by a list of profile names.
services:
web:
image: nginx
# No profiles key, so this is an "always-on" service.
db:
image: postgres
profiles: ["prod"] # Only runs when the 'prod' profile is active.
tests:
image: my-app-tests
profiles: ["test"] # Only runs when the 'test' profile is active.
You can activate profiles using the --profile flag on the command line.
# Start only the 'web' service (default)
docker compose up -d
# Start 'web' and 'db' services
docker compose --profile prod up -d
# Start 'web' and 'tests' services
docker compose --profile test up -d
Notice that the service without a profile (web) is included when you activate other profiles.
Stop Struggling with Docker Compose – Use These 10 Tricks Instead!
To see a quick demonstration of this in action, the following video clip from VirtualizationHowto offers a clear, concise walkthrough.
Watch from 11:00 to 12:46. The presenter shows how to add the profiles key to services in the YAML file and uses the docker compose --profile <name> up -d command to start specific service groups.
3. A Practical Use Case: Managing Dev, Prod, and Test Environments
The real power of profiles comes from managing entire application environments. As a DevOps Engineer, this is a pattern you'll find incredibly useful.
Let's walk through an example that sets up distinct prod, dev, and test environments using profiles.
Leveraging Docker Compose profiles
The article 'Leveraging Docker Compose profiles' provides an excellent, step-by-step guide on refactoring a single docker-compose.yml to handle different environments. It starts with a problem and progressively applies profiles as the solution.
Please read the entire article. Focus on: The challenges the author faces (e.g., different database hostnames, separating test data). How profiles: ["prod"], profiles: ["dev"], and profiles: ["test"] are added to services to isolate them. The commands used to launch each environment (docker compose --profile prod up, etc.). How this single-file approach simplifies configuration management.
This article clearly demonstrates how you can define entirely different services (like db for production and db-dev for development) and use profiles to select the correct one at runtime.
Test your understanding!
You have a docker-compose.yml with the services webapp, database, cache, and monitoring. Your goal is to:
- Have a
coregroup of services (webapp,database,cache) that run for normal operation. - Have the
monitoringservice run only on-demand, alongside the core services, but not by default.
How would you assign profiles to these services to achieve this? What commands would you run?
Show answer
A clean way to do this is to assign all services to explicit profiles.
services:
webapp:
profiles: ["core"]
# ...
database:
profiles: ["core"]
# ...
cache:
profiles: ["core"]
# ...
monitoring:
profiles: ["monitor"]
# ...
Commands:
- To run the main application stack:
docker compose --profile core up -d - To run the main stack and the monitoring service:
docker compose --profile core --profile monitor up -d
This approach makes your service groups explicit and avoids potential confusion with the "always-on" behavior of services without profiles.
4. Advanced Concepts and Best Practices
For professional use, especially in automated CI/CD pipelines, there are a couple of advanced features you should know. The following video from Nick Janetakis, a well-regarded expert in the Docker community, covers these perfectly.
Docker Compose v2 and Profiles Are the Best Thing Ever
This video, 'Docker Compose v2 and Profiles Are the Best Thing Ever', covers two critical features that make profiles invaluable in a professional setting.
Please watch the following segments: Defining Profiles (05:23 - 06:39): A quick review of how profiles are added to services. Using COMPOSE_PROFILES (07:01 - 10:13): This is a key best practice. The video explains how to activate profiles using an environment variable instead of a command-line flag, which is ideal for CI/CD and declarative setups. It also shows a practical dev vs. prod scenario. depends_on Behavior (10:13 - 13:07): This is the most important part. It demonstrates how depends_on now works seamlessly even if the dependency is in an inactive profile. This is a massive improvement over older Docker Compose behavior and a common source of confusion.
Let's summarize the two key takeaways from the video:
-
Use the
COMPOSE_PROFILESEnvironment Variable: Instead of--profileflags, you can define an environment variable, typically in your.envfile:# In .env file COMPOSE_PROFILES=core,monitorNow, a simple
docker compose up -dwill activate thecoreandmonitorprofiles. This is the preferred method for CI/CD pipelines and production deployments because it keeps the configuration self-contained and the command simple. -
depends_onWorks Intelligently: In modern Docker Compose, ifservice-Ahasdepends_on: service-B, butservice-Bis not in an active profile, Compose simply ignores the dependency. This is a huge benefit. For example, yourwebappcandepend_onadatabaseservice for local development, but in production (where you use a managed cloud database), you can simply run with a profile that excludes thedatabaseservice, and your application will start without errors.
Conclusion
You've now learned how to use Docker Compose profiles to create flexible, manageable, and environment-aware application stacks. This technique is fundamental to professional DevOps workflows.
Key Takeaways:
- Profiles allow you to label services to be enabled or disabled in groups.
- They are a superior alternative to multiple Compose files or commented-out services for managing different environments (
dev,test,prod). - Activate profiles with the
docker compose --profile <name>flag or, preferably, by setting theCOMPOSE_PROFILESenvironment variable. - Modern Docker Compose handles
depends_onintelligently, ignoring dependencies on services in inactive profiles, which simplifies using external services (like managed databases) in production.
In our next lesson, we will tackle another critical aspect of multi-service applications: implementing a strategy for managing secrets in Docker Compose. We'll look at how to handle sensitive data like API keys and passwords securely, comparing different methods like environment files and Docker's built-in secrets management.