Dash0 Raises $110M Series B at $1B Valuation

  • 10 min read

How to View Log Output from Docker Compose

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:

bash
1
docker compose up
text
1234
api-1 | 2026-05-25 14:23:01 INFO Server listening on :8080
db-1 | 2026-05-25 14:23:02 LOG: database system is ready to accept connections
redis-1 | 1:M 25 May 2026 14:23:02.143 * Ready to accept connections
api-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:

bash
1
docker compose up --no-attach redis --no-attach db

To run in the background and view logs on demand, use -d:

bash
1
docker 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):

bash
1
docker 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:

bash
1
docker compose logs -f
text
123
api-1 | 2026-05-25 14:23:05 INFO GET /healthz 200 1.2ms
api-1 | 2026-05-25 14:23:10 INFO POST /orders 201 45.3ms
db-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:

bash
1
docker compose logs -f -t

Filtering by service name

Pass one or more service names to scope the output to just those services:

bash
1
docker compose logs -f api
bash
1
docker 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:

bash
1
docker compose logs -f api --index 2

For substring matching across services, Compose doesn't have a built-in filter. Pipe to grep:

bash
1
docker 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:

bash
1
docker 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:

bash
1
docker 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:

bash
1
docker compose logs --tail 0 -f

Pair it with --since and --until for time-bounded queries:

bash
12
docker compose logs --since 10m api
docker 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.