When you've got a multi-container app running under Docker Compose, the logs you need are scattered across each container's stdout and stderr streams. Compose aggregates them for you, but exactly what you see depends entirely on how you started the stack. docker compose up attaches to everything by default, up -d shows you nothing, and docker compose run only streams the one service you targeted. (If you're working with a single container rather than a Compose stack, see how to view Docker container logs.)
This article covers the four commands you'll actually use day-to-day: viewing logs while Compose is running, viewing them after the fact, filtering down to specific services, and limiting the volume so you're not scrolling through hours of irrelevant output.
How up and run handle logs differently
docker compose up without -d starts every service in your compose.yaml and attaches your terminal to all of them at once. Compose interleaves the output and color-codes it by service:
1docker compose up
1234api-1 | 2026-05-25 14:23:01 INFO Server listening on :8080db-1 | 2026-05-25 14:23:02 LOG: database system is ready to accept connectionsredis-1 | 1:M 25 May 2026 14:23:02.143 * Ready to accept connectionsapi-1 | 2026-05-25 14:23:05 INFO GET /healthz 200 1.2ms
That's useful for catching startup errors during development, but it has two consequences worth knowing about. First, your terminal is now bound to the stack: pressing Ctrl+C sends SIGTERM to every container and shuts the whole thing down. Second, output from a single chatty service can drown out everything else you care about. If a particular service is too noisy to be useful (looking at you, healthchecks), the --no-attach flag lets you keep it running but hide its logs:
1docker compose up --no-attach redis --no-attach db
To run in the background and view logs on demand, use -d:
1docker compose up -d
docker compose run is for one-off commands against a single service. It starts your target service plus any services it depends on, then attaches your terminal to that one container with a TTY (the same way docker run -ti works):
1docker compose run --rm api python manage.py migrate
You'll see logs from the api container as the migration runs, but logs from the database it depends on are not streamed to your terminal. They're being written to the container's log stream in the background. To see those, run docker compose logs db in a separate window. This trips people up when migrations fail with a connection error and they're staring at a silent terminal wondering what the database is doing.
docker compose logs -f
For everything else, docker compose logs is the command you want. By itself it dumps the accumulated log history of every service and exits. Add -f (short for --follow) to keep the stream open and watch new entries as they're written:
1docker compose logs -f
123api-1 | 2026-05-25 14:23:05 INFO GET /healthz 200 1.2msapi-1 | 2026-05-25 14:23:10 INFO POST /orders 201 45.3msdb-1 | 2026-05-25 14:23:10 LOG: duration: 12.450 ms statement: INSERT INTO orders ...
This is the equivalent of tail -f across every container in your stack at once. Press Ctrl+C to stop following — your containers keep running. That's the main reason to prefer up -d plus logs -f over plain up for anything beyond a quick smoke test.
For timestamp prefixes that don't depend on whatever your application emits, add -t:
1docker compose logs -f -t
Filtering by service name
Pass one or more service names to scope the output to just those services:
1docker compose logs -f api
1docker compose logs -f api worker
Service names come from the keys under services: in your compose.yaml, not the container names Docker generates (which usually have a -1 suffix for the replica index). If you're running the same service with multiple replicas, target a specific one with --index:
1docker compose logs -f api --index 2
For substring matching across services, Compose doesn't have a built-in filter. Pipe to grep:
1docker compose logs -f | grep -i error
If you're combining -f with grep, force line buffering or output will come through in chunks once grep's buffer fills:
1docker compose logs -f | grep --line-buffered -i error
Limiting output with --tail
By default, docker compose logs -f dumps the entire log history before it starts streaming. For a service that's been running for hours, that means thousands of lines of context you probably don't need. The --tail flag (-n also works) caps the initial output:
1docker compose logs -f --tail 50 api
This shows you the last 50 lines for grounding, then streams new entries as they come in. The most useful combination for live debugging is --tail 0 -f, which skips history entirely and shows you what happens from this moment forward:
1docker compose logs --tail 0 -f
Pair it with --since and --until for time-bounded queries:
12docker compose logs --since 10m apidocker compose logs --since 2026-05-25T14:00:00Z --until 2026-05-25T15:00:00Z api
--since accepts both ISO timestamps and relative durations like 5m, 1h, or 2d.
Common pitfalls
The biggest source of frustration with compose logs is the -f flag flooding your terminal with hours of historical output before it starts streaming. This is the single most common reason people complain that compose logs are "useless" for long-running services. Adding --tail 100 (or --tail 0 to skip history entirely) makes the output immediately usable.
Closely related is the Ctrl+C behavior of docker compose up. A quick foreground up to inspect logs ends with you accidentally shutting down the whole stack. If you want to detach from logs but keep things running, start with -d and tail separately with docker compose logs -f.
There's also a class of problems where compose logs simply can't see what's happening. Only stdout and stderr are captured, so if your application writes logs to a file inside the container, docker compose logs will show nothing. Either reconfigure the application to log to stdout (the twelve-factor recommendation), or docker exec into the container to tail the file. Java apps configured with Log4j file appenders are the most common case. For the deeper picture of how Docker handles container output, see the Mastering Docker Logs guide.
The docker compose run command has a related blind spot. Dependency logs aren't streamed to your terminal, so when a one-off task fails because of a database or queue dependency, you won't see the error from that side. Open a second window and run docker compose logs -f <dependency> before starting the run command so you can correlate the two.
Where to go from here
Compose logs work well while you're developing on your laptop. They don't persist after docker compose down, you can't query them across services without piping through grep, and once you have more than a single host involved, they don't help with staging or production at all. As soon as that's the situation, you need somewhere to ship them.
Dash0's log management ingests logs from your containers using the OpenTelemetry Collector and correlates them with distributed traces and infrastructure metrics, so you can pivot from a failed request to the exact container log line that explains it. For a deeper walk-through of compose logging (logging drivers, structured output, and centralization patterns), see the complete guide to Docker Compose logs.
Start a free trial to centralize your container logs, metrics, and traces in one place. No credit card required.