TechEarl

docker logs: View Container Output and Tail Logs

Read the stdout and stderr of a running or stopped container. Follow live output, tail the last N lines, filter by time, prepend timestamps, and the cases where docker logs doesn't help because the app writes to a file instead.

Ishan KarunaratneIshan Karunaratne⏱️ 7 min readUpdated
Share thisCopied

docker logs shows you what the container's main process wrote to stdout and stderr. It is the first thing you check when something misbehaves, the second thing you check when something silently stops responding, and the one command you should know cold.

How do I view a Docker container's logs?

bash
# All logs since the container started
docker logs CONTAINER_NAME

# Follow live (tail -f)
docker logs -f CONTAINER_NAME

# Last 100 lines only
docker logs --tail 100 CONTAINER_NAME

# Last 100 lines, then follow
docker logs --tail 100 -f CONTAINER_NAME

# Since the last 10 minutes
docker logs --since 10m CONTAINER_NAME

# Since a specific date
docker logs --since 2026-05-01 CONTAINER_NAME

# Prepend ISO timestamps
docker logs --timestamps CONTAINER_NAME

CONTAINER_NAME can be the name (my-app), full ID, or short ID prefix.

Jump to:

The flags that matter

FlagEffect
-f, --followStream new log output as it arrives. Like tail -f.
--tail NOnly show the last N lines before exiting (or before -f starts following). Default is all.
--since DURATION_OR_DATEShow logs newer than this. Accepts durations (10m, 2h, 7d) or absolute timestamps (2026-05-01 or 2026-05-01T08:00:00).
--until DURATION_OR_DATEShow logs older than this. Same format as --since.
-t, --timestampsPrepend an ISO 8601 timestamp to each line.
-nSynonym for --tail.

The flags compose freely: --since 1h --tail 100 -f shows up to the last 100 lines from the past hour, then follows.

Combining flags: the patterns I use

Watching the latest output of a service:

bash
docker logs --tail 50 -f CONTAINER_NAME

The default docker logs -f is fine but on a long-running service it dumps the entire history first, which is rarely what you want. --tail 50 -f shows the last 50 lines then follows.

Investigating a crash a few minutes ago:

bash
docker logs --since 10m --timestamps CONTAINER_NAME

--timestamps is critical here — without them, log lines have no relative time reference and you can't tell whether two events were 10ms or 10s apart.

Filtering to a specific window:

bash
docker logs --since 2026-05-21T08:00:00 --until 2026-05-21T09:00:00 CONTAINER_NAME

Saving a snapshot to a file:

bash
docker logs CONTAINER_NAME > /tmp/container-logs.txt 2>&1

docker logs writes stdout and stderr to its own stdout and stderr respectively, so the redirection captures both. Useful when you need to grep, share, or archive a chunk of logs.

Piping into grep:

bash
docker logs CONTAINER_NAME 2>&1 | grep -i error

2>&1 because errors usually come through stderr; without it, grep only sees stdout.

Getting just the last error block when you don't know how far back to look:

bash
docker logs --tail 1000 CONTAINER_NAME 2>&1 | tac | sed '/ERROR/,/^[^ ]/ p' | tac

Awkward, but the pragmatic version is usually --tail 1000 plus an editor.

Why docker logs sometimes shows nothing

Two common reasons:

1. The app writes to a file inside the container, not to stdout/stderr. docker logs only captures what the main process writes to its standard streams. If your app logs to /var/log/app.log, that file lives inside the container's filesystem, and docker logs knows nothing about it. The fix is either:

  • Change the app config to log to stdout (tail -f /dev/null &; exec myapp style if needed, or use the framework's stdout logger).
  • Symlink the log file to stdout in the Dockerfile: RUN ln -sf /dev/stdout /var/log/app.log.
  • Mount a host directory and tail it from outside.

This is the single most common "docker logs is empty" cause. The 12-factor pattern of "log to stdout" exists for exactly this reason.

2. The container has just started and hasn't logged yet. docker logs shows what has been written so far. If the app has only been up for two seconds and is still initializing, you may see nothing. Try again in a moment, or add -f to wait.

A related case: the container exited so fast you missed everything. docker logs still works on stopped containers (until they are removed), so:

bash
docker ps -a | grep CONTAINER_NAME
docker logs CONTAINER_NAME

Even if the container exited with code 1, the logs are still readable. Full diagnostics for the "container exits immediately" case in Docker Container Exits Immediately.

Log rotation and disk usage

Docker's default logging driver is json-file. It writes each container's stdout and stderr to a JSON file under /var/lib/docker/containers/<id>/. That file grows without bound by default. On a chatty container running for weeks, gigabytes of logs is a real outcome.

Two solutions, in order of how often I reach for them:

Set log limits per container:

bash
docker run -d --name CONTAINER_NAME \
  --log-opt max-size=10m --log-opt max-file=3 \
  IMAGE_NAME

That caps each log file at 10 MB and keeps at most 3 rotated files. 30 MB total per container is plenty for most apps.

Or in docker-compose.yml:

yaml
services:
  app:
    image: my-app
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Set log limits daemon-wide. Edit /etc/docker/daemon.json:

json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Then sudo systemctl restart docker. New containers inherit these defaults; existing containers keep their original config until recreated.

If a container is currently using gigabytes and you want to reclaim the disk without recreating: stop the container, find the log file under /var/lib/docker/containers/<id>/<id>-json.log, and truncate it (sudo truncate -s 0 path). Don't rm it — the Docker daemon's file handle is still open and behaviour gets weird.

Compose: docker compose logs

bash
# All services
docker compose logs

# Follow all services
docker compose logs -f

# One service
docker compose logs -f web

# Tail 50 from one service
docker compose logs --tail 50 -f web

# Across multiple services with timestamps
docker compose logs -t --tail 100 -f

The flags are the same as docker logs. The difference is the output is interleaved across services and each line is prefixed with the service name, which is exactly what you want when you're debugging "did web log this before or after db logged that?"

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsDockerdocker logsContainersDebuggingDevOps

Found this useful? Pass it on.

Copied
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

R

Running Docker Containers as a Non-Root User

By default, processes inside Docker containers run as root, which is risky. Switch to a non-root USER, fix permissions on volumes and ports, and configure Compose and Kubernetes to refuse to run root containers.