When your Compose setup gets complex, docker compose config becomes your best debugging tool. Especially with profiles.
The basics
docker compose config
This shows you the actual configuration that Docker Compose will run:
- Environment variables are replaced with their values
- Relative paths become absolute
- Default values are applied
- Multiple compose files are merged
- YAML anchors are resolved
What you write:
services:
web:
image: myapp:${VERSION:-latest}
volumes:
- ./data:/app/data
environment:
DATABASE_URL: ${DATABASE_URL}
What docker compose config shows you:
services:
web:
image: myapp:1.2.3 # VERSION was set to 1.2.3
volumes:
- /home/user/project/data:/app/data # Absolute path
environment:
DATABASE_URL: postgresql://localhost:5432/mydb # Actual value
Understanding variable resolution
See exactly how your variables expand:
# Show resolved values
docker compose config
# Keep variables as-is
docker compose config --no-interpolate
# Check what services will run
docker compose config --services
Complex merge scenarios
When using multiple files and overrides:
docker compose -f compose.yml -f compose.dev.yml -f compose.override.yml config
This shows the final merged result. It’s invaluable for debugging why a service isn’t configured as expected.
CI/CD validation
# Validate all profiles
for profile in dev staging prod; do
docker compose --profile $profile config --quiet || exit 1
done
This catches issues where profiles break due to missing dependencies or circular references.
The real power: debugging profiles
Profiles can be tricky. Services get pulled in through dependencies even without having the profile:
services:
web:
image: nginx
profiles: ["frontend"]
depends_on:
- api
api:
image: myapi
profiles: ["frontend"]
depends_on:
- db
db:
image: postgres
# No profile - always runs
cache:
image: redis
profiles: ["backend"]
worker:
image: worker
profiles: ["backend"]
depends_on:
- db
- cache
What happens with --profile backend?
docker compose --profile backend config --services
You get: db, cache, AND worker. But here’s the trick - db runs even without the profile because it has no profile defined. Services without profiles are always started.
Even trickier - dependencies across profiles will fail:
services:
test-runner:
image: test-runner
profiles: ["test"]
depends_on:
- web
- db
web:
image: nginx
profiles: ["frontend"]
db:
image: postgres
# No profile - always runs
Running docker compose --profile test config errors out:
service "test-runner" depends on undefined service "web": invalid compose project
The web service isn’t available because the frontend profile isn’t active. You need BOTH profiles:
docker compose --profile test --profile frontend config --services
# Now you get: db, web, test-runner
This is why docker compose config is invaluable - it catches these dependency issues before runtime.