Docker Logs for Beginners: Read, Cap and Rotate Logs
Learn docker logs from a real Compose file: stdout and stderr, the json-file and journald drivers, max-size rotation, and reading logs when a deploy fails.
By Matthews WongReviewed on
Key takeaway
Docker logs are whatever a container's main process writes to standard output and standard error. Docker captures those two streams, stores them through a logging driver (json-file by default), and docker logs reads them back. Without a size cap the default driver never rotates, so a chatty container can slowly fill the disk.
You will be able to
- Read a container's logs with docker logs and docker compose logs: follow, tail, since and timestamps.
- Explain where a log line is stored under the json-file and journald drivers.
- Cap log size with max-size and max-file, and find the real error in a failed CI or deploy log.
Step through the diagram
Press Play, or step with the arrows. Every step is written out below the diagram.
Where a log line goes, and who reads it
The app writes to stdout and stderr, Docker's logging driver stores the lines in a capped file or in the systemd journal, and docker logs or journalctl reads them back.
Keys: ← → step · Home / End · K play or pause
A container writes lines, a logging driver stores them, and a reader asks for them later. The journal on the right is the alternative storage; it stays faded for now.
Docker captures the main process's standard output and standard error. The app does not manage a log file itself; it just prints, and Docker catches every line.
Predict the next step The driver is json-file and max-size is not set. As the app keeps printing, what does the log file do? (Stays small on its own · Grows with no limit · Rotates every day)The default json-file driver appends each line, with its stream and time, to a JSON file on the host. The default max-size is unlimited, so nothing ever rolls.
With max-size 10m and max-file 3, the driver rolls the file at the size limit and removes the oldest one once there are more than three. The disk stays bounded.
logging: driver: json-file options: max-size: "10m" max-file: "3"docker logs asks Docker for the stored lines. Add follow to keep streaming, tail to see only the end, and timestamps to see when each line was written.
docker logs --tail 100 --follow webPredict the next step You switch the driver to journald. Where do the lines live now? (Still in the JSON file · In the systemd journal · Nowhere)With the journald driver the lines go to the systemd journal instead. Each entry carries the container name, so journalctl can filter on it.
journalctl CONTAINER_NAME=web --since today
What does a container's log contain?
By default, docker logs shows the command's standard output and standard error. Standard output is usually normal output and standard error is where error messages go. What you see depends almost entirely on the container's main command: if it prints, you get logs.
Read more (1)Show less
The catch is an app that writes to its own log files instead. Then docker logs may show nothing useful. The official nginx image solves this by linking its log files to the standard output and error devices, so the same habit works for it too.
Which logging driver stores the lines?
Docker uses the json-file driver unless you change it. It stores each line as a JSON object with the text, the stream (stdout or stderr) and a time. The other driver worth knowing first is journald, which forwards the lines to the systemd journal, where journalctl, the journal API or docker logs can read them.
Read more (1)Show less
You can set the driver per container or daemon-wide in daemon.json, but a changed default applies only to containers created afterwards. Existing containers keep the options they were created with, so re-create one to switch it. To check what a container uses, run docker inspect with a format on HostConfig.LogConfig.Type.
Why cap log size, and how does rotation work?
For json-file, max-size defaults to -1, unlimited, and max-file defaults to 1. Rotation needs both: max-file only takes effect when max-size is also set. When rolling would create too many files, the oldest one is removed, which is exactly what you want from a log you only read when something breaks.
Read more (1)Show less
A rotated file is a trade: it bounds the disk, but older lines are gone for good. Anything you must keep longer, such as an audit trail, belongs in a place built for it, not in the container's own log.
How do you read logs without drowning in them?
docker logs takes follow to stream new lines, tail to limit output to the last lines (the default is all), since to show only newer lines and timestamps to print when each line was written. docker compose logs does the same for the services of a Compose project, and accepts service names.
Read more (1)Show less
With the journald driver, use journalctl instead. A field match such as CONTAINER_NAME filters to one container, since and until set a time range, lines shows the most recent entries, and a priority filter keeps only entries at or above a level.
How do you read the log of a failed deploy?
A deploy log is not a container log: it is the output of a GitHub Actions run. Open the run, click the job, and the failed step opens automatically. Use the search box to find a phrase, and download the log archive when you want it on your own machine.
Read more (1)Show less
Read the first error in the failed step, not the last line, because later lines are usually fallout. And make your own steps easy to read: this repo's smoke workflow redirects the server's output to a file and prints it only when a step fails, so the evidence is there when you need it and quiet when you do not.
The real config behind this lesson
Excerpts from the repository that builds and deploys this website, quoted as they are.
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"The whole logging policy of the production container. The driver is named even though json-file is the default, so the intent is visible. With these options the container keeps at most three files of about 10 MB each, and the oldest is dropped when a fourth would be created.
- name: Serve
run: |
PORT=3000 npm start > server.log 2>&1 &
for i in $(seq 1 60); do
if curl -sf -o /dev/null http://localhost:3000/en/play/; then exit 0; fi
sleep 2
done
echo "server did not come up"
cat server.log
exit 1The same idea as a container log, done by hand: start the server in the background with stdout and stderr redirected to server.log, then wait for it. If it never answers, the step prints the file before failing, so the run explains itself instead of just saying exit 1.
# The hosted runner's disk is small, and this build is not: the restored
# .next/cache, node_modules and a ~1,586-page build output that Docker
# copies twice (into .next/standalone, then into the runner image). On
# 2026-10-07 it ran out mid-build ("no space left on device" at the
# standalone COPY) and every deploy after a852ccf failed the same way.
# The image ships ~20-30 GB of toolchains this job never uses; delete
# them first. `df` is printed before and after, so the headroom shows
# in the log.
- name: Free disk space on the runner
run: |
df -h / | tail -1A real failure found by reading a log: the message no space left on device appeared mid-build. The fix is paired with a log line, because df is printed before and after cleanup, so the next failure shows how much room there was instead of leaving a guess.
Mistakes that cost real time
max-file set, max-size forgotten
max-file only takes effect when max-size is set. With max-size left at its unlimited default, nothing ever rolls and one file grows until the disk is full. Always set the two together, and check with docker inspect that the container actually has them.
Changing the default driver and expecting old containers to follow
A new default in daemon.json applies only to containers created after the Docker restart. Existing containers keep the options they were created with, so the old ones still log the old way until you re-create them.
docker logs is empty because the app writes its own file
If the app sends output to a log file inside the container, docker logs has nothing to show. Point the app at standard output and standard error, or link the file to them the way the official nginx image does.
Reading only the last line of a failed run
The last line is usually fallout from an earlier error, such as a generic exit code. Open the failed step, scroll to the first error, and use the log search for the exact phrase.
| json-file (default) | journald | |
|---|---|---|
| Where lines are stored | A JSON file per container | The systemd journal |
| How you read them | docker logs | journalctl, the journal API or docker logs |
| Size control | max-size and max-file | Not set with max-size or max-file |
| Filter by container | Name the container | A CONTAINER_NAME field match |
Do it on your own machine
You need Docker only. Start a container that prints a line every second, read its logs, then recreate it with a size cap and confirm the setting.
Start a container that prints a timestamped line every second.
docker run -d --name chatty alpine sh -c 'while true; do date; sleep 1; done'Read the last five lines with timestamps, then follow new ones. Stop following with Ctrl-C.
docker logs --tail 5 --timestamps chatty docker logs --tail 5 --follow chattyAsk Docker which driver the container uses.
docker inspect -f '{{.HostConfig.LogConfig.Type}}' chattyRemove it and start it again with a cap of three small files, then confirm the options were stored.
docker rm -f chatty docker run -d --name chatty --log-opt max-size=1m --log-opt max-file=3 alpine sh -c 'while true; do date; sleep 1; done' docker inspect -f '{{.HostConfig.LogConfig.Config}}' chattyClean up.
docker rm -f chatty
Three questions before you move on
Pick an answer to see why it is right or wrong. Nothing is saved or graded, so guess freely.
Answer in your head, then check
1.How much log history can this site's production container keep?Show the answer
Answer:At most three files of about 10 MB each, because the Compose file sets max-size to 10m and max-file to 3. When a new file is needed, the oldest is removed.
2.Which command streams the last 100 lines of a container's logs and keeps following?Show the answer
Answer:docker logs --tail 100 --follow followed by the container name. For a Compose project, docker compose logs takes the same flags plus service names.
3.A deploy run fails. Where do you look first?Show the answer
Answer:In the run's failed job: the failed step opens automatically. Find the first error in it, search the log for the exact phrase, and download the archive if you need it locally.
Related deep dives on this site
Sources
Primary sources checked on the research date. Follow them for the current version.