Docker Compose profiles let you selectively start subsets of your services defined in a docker-compose.yml file.
Here’s a docker-compose.yml file that defines a web application with a frontend, a backend API, and a database, using profiles:
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "80:80"
profiles:
- web
backend:
build: ./backend
ports:
- "5000:5000"
depends_on:
- database
profiles:
- api
database:
image: postgres:14-alpine
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
profiles:
- db
volumes:
db_data:
Now, let’s see how we can use these profiles to start specific services.
Starting only the frontend:
If you only want to run the frontend service, which is often useful for local development of the UI, you can use the web profile:
docker compose --profile web up
This command will build and start the frontend service. Since the frontend service has a depends_on for backend, and backend has a depends_on for database, Compose might try to resolve those dependencies. However, because backend and database are not explicitly started via their profiles, they won’t be included in the up operation. The frontend service will start, but it won’t have its dependencies running. If your frontend actually needs the backend to function, this is where you’d typically encounter issues, but for isolated UI development, this is perfect.
Starting the backend API and its database:
To work on the backend, you’ll likely want to start the API and its database:
docker compose --profile api --profile db up
This command starts both the backend and database services. The backend service will wait for the database to be ready due to the depends_on directive. The frontend service, not being included in any of the specified profiles, will not be started.
Starting all services:
To start everything defined in the docker-compose.yml file, you can omit the --profile flag entirely. This is equivalent to running docker compose up without any profile arguments:
docker compose up
This will start all services that do not have a profile explicitly defined, and all services that are defined within the profiles you explicitly pass. In our example, if you run docker compose up without any profiles, nothing will start because every service has a profile assigned. This is a common point of confusion: if a service has a profile, it’s only started if that profile is requested.
To start everything when all services have profiles, you need to activate all the profiles:
docker compose --profile web --profile api --profile db up
This explicitly tells Compose to include services tagged with web, api, and db.
The mental model:
Think of profiles as labels you attach to your services. When you run docker compose up --profile <profile_name>, you’re essentially saying, "Start all services that have the <profile_name> label, and any services they depend on that also have labels." If a service doesn’t have a profile, it’s treated as if it belongs to a default, unnamed profile that is always activated. However, in the example above, every service does have a profile, so none are started by default.
The depends_on directive still works as expected within the context of the selected profiles. If service A depends on service B, and you start service A using a profile, Compose will ensure service B is also started (if it’s part of the selected profiles or has no profile itself) and ready before starting service A.
The surprising truth about depends_on and profiles:
While depends_on ensures a service is started, it doesn’t guarantee it’s fully ready to accept connections. For databases, this means the database process might be running, but the actual database initialization (creating tables, running migrations) might not be complete. For robust applications, you’ll often need a health check or a wait-for-it script to ensure dependencies are truly ready before proceeding.
This approach is incredibly powerful for managing complex applications where different environments (development, testing, CI/CD) or different feature sets require distinct sets of services to be running. For instance, you might have a dev profile that starts your frontend and backend, a test profile that starts everything needed for integration tests, and a ci profile for your continuous integration pipeline.
The next logical step in managing complex service dependencies is to explore health checks within Docker Compose, which go beyond just checking if a container is running to verifying if the application inside is actually responding.