16.4 Compose Troubleshooting
A focused guide to Compose Troubleshooting, connecting core concepts with practical Docker and container operations.
Compose troubleshooting covers problems specific to multi-service stacks defined and managed through Compose, layered on top of, but distinct from, the troubleshooting that applies to any individual container, since Compose introduces its own additional concerns: how multiple configuration files merge together, how services depend on and discover each other, and how Compose's own command-line tooling reports on a stack as a whole rather than a single container in isolation.
Verifying the actual resolved configuration first
Before investigating any specific symptom, confirming exactly what configuration Compose has actually resolved, after merging the base file with any override files, removes a significant source of confusion when troubleshooting begins with an assumption about configuration that may not match reality:
docker compose config
This command outputs the fully merged, resolved configuration, exactly as Compose will actually apply it, which is the most direct way to confirm whether an expected environment variable, volume mount, or port mapping survived the merge across however many layered files are actually in use, rather than assuming based on reading the individual files separately.
Checking the status of every service at once
docker compose ps provides a stack-wide view of every service's current state in one command, which is generally the right starting point for any Compose-specific investigation rather than checking individual containers one at a time:
docker compose ps
NAME STATUS
api Up 2 hours
db Up 2 hours (healthy)
worker Restarting (1) 5 seconds ago
Seeing the full picture at once, including which specific service is in a restart loop while others remain stable, immediately narrows the investigation to the one problematic service rather than requiring a more exhaustive, individual check of every container in the stack.
Viewing logs across multiple services together
docker compose logs aggregates output across services, with each line prefixed by the originating service name, which is valuable for understanding interactions between services during the same time window without needing to manually correlate timestamps across several separate docker logs invocations:
docker compose logs -f api db
api | Connecting to database...
db | received connection
api | Connection established
This interleaved view directly shows the sequence of events across service boundaries, which is considerably more useful for diagnosing a startup ordering or connectivity issue than viewing each service's logs in isolation.
Startup ordering and dependency conditions
A frequent category of Compose-specific issue is a service starting before a dependency it actually needs is ready, distinct from whether that dependency's container has merely started:
services:
api:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
Confirming a depends_on condition is actually configured with a health check requirement, rather than only the default, weaker guarantee of container start order, resolves the most common version of this specific Compose troubleshooting category.
Network and service name resolution issues
Services within the same Compose file are automatically placed on a shared, project-scoped network and can reach each other by service name, and a connectivity failure between two services in the same stack is worth checking against this expectation directly:
docker compose exec api ping -c 1 db
docker network ls
Confirming the expected Compose-managed network actually exists, and that both services are actually attached to it (which is the default unless a more specific, custom network configuration overrides it), rules out or confirms the most basic explanation before investigating anything more specific.
Environment variable precedence and the .env file
Compose resolves environment variables from several possible sources, the Compose file's own environment key, an env_file reference, a .env file in the project directory, and the shell's own environment, each with a defined precedence order, and confusion about which source actually won for a given variable is a common source of unexpected configuration values:
docker compose config | grep -A5 "environment:"
Checking the fully resolved configuration directly, as mentioned earlier, is the most reliable way to confirm which value actually took effect, rather than reasoning abstractly about precedence rules across several possible sources.
Project name and resource naming confusion
Compose derives container, network, and volume names from a project name, by default based on the directory the Compose command is run from, which means running the same Compose file from different working directories, or with an explicit -p override, produces an entirely separate, independently named set of resources rather than reusing a previously created stack:
docker compose -p my-project up -d
docker compose ps
cd ..
docker compose up -d
Running the second command from a different directory, without the same explicit project name, creates a second, separate stack rather than interacting with the first one, which can produce confusing duplication or apparent inconsistency if not recognized explicitly.
Rebuilding versus restarting
A common point of confusion is the difference between restarting a service's existing container and rebuilding its image before starting a new one, since changes to a Dockerfile or source code referenced by a build directive are not reflected until an actual rebuild occurs:
docker compose restart api
docker compose up -d --build api
The first command restarts the existing container using its current image, unchanged; the second rebuilds the image first, then replaces the container with one based on the newly built result, which is necessary whenever the underlying source code or Dockerfile has actually changed.
Common mistakes
- Investigating individual container behavior without first checking
docker compose configto confirm what configuration was actually resolved after merging. - Using
depends_onwithout a health condition, allowing a dependent service to start before its dependency is genuinely ready, only checking the wrong, weaker guarantee. - Restarting a service expecting a code change to take effect, rather than rebuilding, which is the only way Dockerfile or build-context changes are actually picked up.
- Running the same Compose file from different working directories or without a consistent project name, unintentionally creating separate, duplicate stacks rather than interacting with the original.
- Not using
docker compose logsacross multiple services together when diagnosing a cross-service interaction, missing the value of seeing interleaved, correlated output.
Compose troubleshooting builds on everything that applies to individual containers, but adds its own specific first steps, checking the fully resolved configuration, reviewing stack-wide status and aggregated logs, and confirming dependency and networking assumptions explicitly, which together resolve the large majority of Compose-specific issues before any deeper, single-container investigation becomes necessary.