Lesson 20 of 30
One compose file rarely fits every environment as-is — profiles let you selectively start services, and layered override files let dev, CI, and staging diverge without three copies of the same YAML silently drifting apart.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A service is tagged with `profiles: ["debug"]` in the base compose file. What happens when you run `docker compose up` without specifying that profile?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The earlier Compose lesson in this track covered the basics: one file, multiple services, a shared network where service names resolve as hostnames. That's enough for a single environment. In practice, a team runs the same application in several contexts — a developer's laptop, CI, staging — and each needs slightly different services running. A CI pipeline doesn't need a debugging UI. A laptop doesn't need the same replica count as staging. Copy-pasting the compose file per environment works for a week and then quietly drifts, because nobody remembers to update all three copies when something changes.
Profiles solve the "which services start" half of this: tag a service with one or more profile names, and docker compose up only starts services with no profile (always-on) plus whichever profiles you explicitly request. Multiple compose files merged together solve the "how does a service's config differ per environment" half: a base file holds what's shared everywhere, and a second file layered on top overrides or adds to it for one specific context.
# docker-compose.yml (base, committed)
services:
api:
build: ./api
ports:
- "4000:4000"
db:
image: postgres:16
adminer:
image: adminer
profiles: ["debug"]
ports:
- "8080:8080"
# docker-compose.override.yml (local-only, typically gitignored)
services:
api:
volumes:
- ./api:/app # bind-mount source for hot reload, dev only
environment:
- DEBUG=true
docker compose up # api + db only — adminer is skipped
docker compose --profile debug up # api + db + adminer
adminer (a database admin UI) never starts in CI or on a teammate's machine unless they explicitly ask for the debug profile — it costs nothing to leave declared in the base file. Meanwhile docker-compose.override.yml loads automatically alongside the base file with no extra flag, layering the bind-mount and debug flag on top for local development only, while staging runs from the base file (or its own override) with neither.
docker compose up --build or dependency resolution the way you'd expect. If an active service depends_on a profile-gated one, Compose starts it anyway regardless of profile — profiles gate direct invocation, not the dependency graph.-f chain override matching keys, they don't fully replace the service block. docker-compose.yml plus docker-compose.staging.yml merges field-by-field (a list like ports is replaced, not appended, in most Compose versions) — assuming "override" means "wholesale replacement" leads to surprises on list-valued keys specifically."How do you keep local dev, CI, and staging from drifting apart when they all use Docker?" tests whether a candidate has actually maintained a multi-environment Compose setup or only ever run docker compose up against a single, static file — naming profiles and layered override files as the actual mechanism (not just "we're careful") is the concrete, checkable answer.