20.2.1.5 Compose Log Debugging
A focused guide to Compose Log Debugging, connecting core concepts with practical Docker and container operations.
Log debugging in a Docker Compose application means reading, filtering, and interpreting the output from one or more service containers to understand what is happening, diagnose failures, and trace request flows across services. Compose aggregates logs from all containers in the application, and the docker compose logs command provides several options for narrowing down what you see.
Viewing All Logs
docker compose logs
Shows the combined log output from all services since the containers started. Each line is prefixed with the service name in a distinct color (when the terminal supports color), making it possible to distinguish which service produced which output:
api-1 | Server listening on port 3000
db-1 | database system is ready to accept connections
cache-1 | Ready to accept connections
api-1 | GET /health 200 3ms
api-1 | Connected to database
By default this command is not live — it shows all stored log output and exits.
Following Logs in Real Time
docker compose logs -f
The -f (follow) flag streams log output continuously. New lines appear as services produce them. Press Ctrl+C to stop following.
This is the standard command for watching what a running application is doing — a replacement for tail -f on log files.
Filtering by Service
docker compose logs api
Shows only the logs for the api service. Multiple service names can be specified:
docker compose logs api db
Combined with follow:
docker compose logs -f api
Useful when you know which service is the source of a problem and do not want log lines from other services to obscure the output.
Limiting the Number of Lines
docker compose logs --tail 50
Shows only the last 50 lines from each service's log. Combined with follow, this starts from the recent history rather than the beginning:
docker compose logs --tail 50 -f api
For a high-volume service that has been running for a long time, this avoids reading thousands of historical lines before reaching the current output.
Adding Timestamps
docker compose logs --timestamps
Prepends each log line with the timestamp from the Docker log driver. This is essential for correlating events across services: if the api logs show an error at a specific time, you can look for the corresponding db log entry at the same timestamp.
api-1 | 2024-03-15T14:22:31.123Z GET /orders 500 45ms
db-1 | 2024-03-15T14:22:31.089Z ERROR: duplicate key value violates unique constraint
The timestamp on the db log entry is slightly before the api error, confirming the database returned an error that the API propagated.
Container Status and Health
Before reading logs, check whether services are actually running:
docker compose ps
NAME IMAGE STATUS PORTS
myapp-api-1 myapp-api running 0.0.0.0:3000->3000/tcp
myapp-db-1 postgres:15-alpine running (healthy) 5432/tcp
myapp-cache-1 redis:7-alpine exited (1)
The exited (1) status for the cache service means it stopped with a non-zero exit code. Check its logs immediately:
docker compose logs cache
A container that exits instead of staying running often has a startup error — misconfiguration, missing environment variable, failed command — visible in the last few log lines.
Inspecting a Container That Won't Start
If a container starts and immediately exits, docker compose logs <service> shows the output from the last run. Common causes visible in logs:
- Configuration error: The process read a config file with invalid syntax.
- Port conflict: The process tried to bind a port already in use.
- Permission denied: The process could not write to a required directory.
- Missing environment variable: The process crashed because a required value was not set.
For a container that was started but is not currently running, logs are still available:
docker compose logs --no-log-prefix db
The --no-log-prefix flag removes the service name prefix, useful when piping log output to a grep or other tool.
Searching Logs
Compose does not have a built-in search filter, but you can pipe the output:
docker compose logs api | grep "ERROR"
docker compose logs --timestamps api | grep "2024-03-15T14:22"
On Windows PowerShell:
docker compose logs api | Select-String "ERROR"
Running Commands Inside a Container for Debugging
When logs show an error but you need more context, run a shell inside the container:
docker compose exec api sh
From inside, you can:
- Check whether environment variables are set:
env | grep DATABASE - Test network connectivity to another service:
nc -zv db 5432 - Check whether files exist:
ls /app - Run the application's diagnostic commands directly
For Alpine-based images, use sh. For Debian/Ubuntu-based images, bash is available.
Testing Inter-Service Connectivity
A common debugging scenario: the API cannot connect to the database. From inside the API container:
docker compose exec api sh -c "nc -zv db 5432"
db (172.20.0.3:5432) open
If this succeeds, the TCP connection works and the issue is at the application level (wrong credentials, wrong database name). If it fails, there is a network or service naming issue.
Log Driver Configuration
By default, Docker stores logs in memory-backed JSON files on the host. For high-volume services, logs can grow large. Configure the log driver in compose.yml to limit size:
services:
api:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
This keeps the last three 10MB log files per container and rotates them automatically. Without this, a long-running service can accumulate gigabytes of logs.
Structured Logs and Compose
Applications that emit JSON-formatted log lines output them as raw JSON strings in docker compose logs. The structure is not parsed by Docker — each line is treated as opaque text. To benefit from structured logging in development, pipe Compose logs to a JSON-aware formatter:
docker compose logs -f api | jq .
If the application emits JSON like {"level":"error","msg":"connection refused","ts":1234567890}, jq . pretty-prints each line for readable output in the terminal.