Docker Compose Logs: The Complete Debugging Guide

Docker Compose Logs: A Practical Guide to Debugging Multi-Container Apps

Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

When a container silently dies or a service starts throwing errors at 2 AM, docker compose logs is usually the first command you reach for. It’s the fastest way to see what’s actually happening inside your stack without SSHing into individual containers or digging through log files scattered across the filesystem.

If you’re running Compose stacks in production — whether that’s a media server setup, a CI runner, or a full application backend — understanding log output is non-negotiable. Let’s get into it.

Why docker compose logs Matters

Unlike docker logs, which only works on a single container, docker compose logs aggregates output from every service defined in your docker-compose.yml. This matters because most real-world failures aren’t isolated — a database connection timeout in your API service might actually be caused by the database container failing to start, and you won’t see that correlation unless you’re looking at both logs side by side.

If you’re new to container orchestration in general, our Docker networking fundamentals guide is a good companion piece before diving deeper into log debugging.

Basic Syntax and Usage

The core command is straightforward:

docker compose logs

Run this from the directory containing your docker-compose.yml, and it dumps the combined stdout/stderr output from every running service, prefixed by service name and color-coded in your terminal.

To target a single service:

docker compose logs web

To target multiple specific services:

docker compose logs web db redis

This is useful when you have a stack with 10+ services (common in microservice architectures) and only care about the ones involved in a specific request path.

Following Logs in Real Time

By default, docker compose logs prints everything and exits. To keep the stream open and watch new log lines as they arrive — similar to tail -f — add the -f or --follow flag:

docker compose logs -f

This is the flag you’ll use most often during active debugging sessions. Combine it with a service filter to watch just one component while you reproduce a bug:

docker compose logs -f api

Press Ctrl+C to stop following without stopping the containers themselves.

Limiting Output with Tail

On long-running production services, log history can be enormous. Dumping the entire history to your terminal is slow and mostly useless. Use --tail to limit output to the most recent N lines:

docker compose logs --tail=100 api

Combine --tail with --follow to get recent context plus a live stream:

docker compose logs --tail=50 -f api db

This pattern — recent history plus live tail — is the single most useful log-debugging habit you can build.

Adding Timestamps

By default, Compose logs don’t show timestamps, which makes it hard to correlate events across services or figure out how much time elapsed between two log lines. Add the -t or --timestamps flag:

docker compose logs -t --tail=100 api

Output looks like this:

api_1  | 2026-07-02T14:22:01.123456789Z Starting server on port 3000
api_1  | 2026-07-02T14:22:03.987654321Z Connected to database

Timestamps are essential when you’re trying to line up an error in your app logs with a corresponding event in your reverse proxy or database logs.

Filtering by Time Range

If you know roughly when an incident occurred, you don’t need to scroll through hours of noise. Use --since and --until to bound the output:

docker compose logs --since 2026-07-02T14:00:00 --until 2026-07-02T14:30:00 api

You can also use relative durations:

docker compose logs --since 30m api

This pulls only logs from the last 30 minutes — extremely useful when triaging a fresh incident without wading through days of accumulated output.

Disabling the Log Prefix

When you’re only looking at one service and want cleaner output (for piping into grep or another tool), drop the service-name prefix with --no-log-prefix:

docker compose logs --no-log-prefix api | grep ERROR

This is handy when scripting log analysis or feeding output into external tools.

Piping and Searching Logs

docker compose logs output is just text, so standard Unix tools work fine on top of it. A few practical patterns:

  • Search for errors across all services: docker compose logs | grep -i error
  • Count occurrences of a specific warning: docker compose logs | grep -c "connection refused"
  • Save a snapshot for later analysis: docker compose logs --no-color > incident-2026-07-02.log
  • Watch only 500 errors in a web service: docker compose logs -f web | grep "500"
  • Combine tail and search for a fast triage loop: docker compose logs --tail=200 db | grep -i "deadlock"
  • These one-liners solve 90% of the ad-hoc debugging you’ll do day to day, without needing a dedicated log aggregation tool.

    Common Problems and How Logs Help Diagnose Them

    Container Restart Loops

    If a container keeps restarting, docker compose ps will show a high restart count, but docker compose logs tells you why. Run it without --tail truncation on a small window right after the crash:

    docker compose logs --since 5m --tail=200 worker

    Look for the last few lines before the process exits — that’s almost always where the actual error (missing environment variable, failed migration, out-of-memory kill) shows up.

    Silent Startup Failures

    Sometimes a service reports as “running” but isn’t actually healthy — for example, a database that’s accepting connections but hasn’t finished initializing. Following logs during startup catches this:

    docker compose up -d
    docker compose logs -f db

    Watch for the specific “ready to accept connections” message your database emits before assuming dependent services can connect.

    Cross-Service Correlation

    When your API returns a 500 error, you often need to see what the database or cache was doing at the exact same millisecond. Use timestamps and pull both services together:

    docker compose logs -t --since 10m api redis

    Having both streams interleaved by timestamp makes root-cause analysis dramatically faster than tailing them in separate terminal windows.

    For a deeper dive into container health checks that complement log-based debugging, see our Docker health check configuration guide.

    Beyond docker compose logs: When to Use a Real Logging Stack

    The built-in logging command is great for local development and quick production triage, but it has real limits: no persistent storage past the log driver’s retention, no full-text search across weeks of history, and no alerting. Once you’re running more than a handful of services, it’s worth shipping logs to a dedicated platform.

    For teams that don’t want to run their own Elasticsearch or Loki stack, a hosted monitoring and log management service removes the operational overhead. BetterStack offers log management and uptime monitoring that integrates cleanly with Docker Compose setups via simple log-driver configuration, so you get searchable, retained logs and alerting without babysitting another piece of infrastructure.

    If you’re hosting your Compose stack yourself, the underlying VPS matters too — slow disk I/O can bottleneck log writes on high-throughput services. Providers like DigitalOcean and Hetzner both offer NVMe-backed instances that hold up well under heavy container logging workloads, and either is a solid choice for a self-managed Compose deployment.

    Configuring the Logging Driver

    By default, Compose uses the json-file driver, which can grow unbounded if you don’t set limits. Add size and rotation limits directly in your docker-compose.yml:

    services:
      api:
        image: myapp:latest
        logging:
          driver: "json-file"
          options:
            max-size: "10m"
            max-file: "3"

    This caps each log file at 10MB and keeps a maximum of 3 rotated files per container, preventing a chatty service from filling your disk.

    Quick Reference: Useful Flags

  • -f, --follow — stream logs continuously
  • --tail=N — show only the last N lines
  • -t, --timestamps — prepend ISO 8601 timestamps
  • --since / --until — bound output by time
  • --no-log-prefix — remove service name prefix
  • --no-color — strip ANSI color codes (useful when redirecting to a file)
  • Memorize --tail and -f first — they cover the majority of day-to-day debugging. Add -t and --since once you’re troubleshooting timing-sensitive issues across multiple services.

    Recommended: Want to explore DigitalOcean yourself? DigitalOcean is a direct vendor link (not an affiliate/tracked link).

    FAQ

    Q: What’s the difference between docker logs and docker compose logs?
    A: docker logs <container> targets a single container by name or ID. docker compose logs operates on an entire Compose project, aggregating and interleaving output from all defined services (or a filtered subset) in one command.

    Q: Why don’t I see any output when I run docker compose logs?
    A: This usually means the containers aren’t running, or they’re writing logs to a file inside the container instead of stdout/stderr. Docker only captures stdout/stderr by default — check your app’s logging configuration to make sure it logs to the console, not a file.

    Q: How do I clear or reset the logs for a service?
    A: Compose doesn’t have a built-in “clear logs” command. You typically need to either restart the container (which starts a fresh log file depending on your driver) or manually truncate the underlying log file on the host if you have access to it.

    Q: Can I export logs to a file for sharing with a teammate?
    A: Yes. Run docker compose logs --no-color > output.log to save a clean, ANSI-free text file that’s easy to share or attach to a ticket.

    Q: Does --tail work with --follow at the same time?
    A: Yes, and it’s a very common combination. docker compose logs --tail=100 -f api shows the last 100 lines immediately, then continues streaming new lines as they arrive.

    Q: Why are my logs missing timestamps even in the raw JSON log file?
    A: The json-file driver does store timestamps internally, but docker compose logs only displays them when you pass -t/--timestamps. Without that flag, Compose strips them from the terminal output for readability.

    Wrapping Up

    docker compose logs is deceptively simple but covers most of what you need for day-to-day container debugging: filtering by service, following in real time, bounding by time range, and piping into standard Unix tools for search. Combine it with sensible logging-driver limits and, once your stack grows, a proper log aggregation service to avoid losing critical history.

    For related deployment topics, check out our Docker Compose production checklist to make sure your logging setup fits into a broader operational strategy.

    Comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *