The core idea
Docker captures whatever a container's main process writes to stdout and stderr. That's all a container log is. So the rule for apps in containers is simple: log to the console, not to files, and let Docker (or whatever collects Docker's logs) handle storage.
Reading logs
$ docker run -d --name talky alpine sh -c 'for i in 1 2 3; do echo "out $i"; echo "err $i" >&2; sleep 1; done'$ docker logs talkyout 1err 1out 2err 2out 3err 3$ docker logs talky 2>&1 >/dev/nullerr 1err 2err 3$ docker logs -t --tail 2 talky2026-11-02T20:22:14.462506211Z out 32026-11-02T20:22:14.462563711Z err 3
docker logs keeps stdout and stderr separate, so your shell can split them. The flags you'll use most:
| Flag | Does |
|---|---|
-f | follow new lines as they arrive, like tail -f |
--tail 50 | start from the last 50 lines instead of the beginning |
--since 10m | only lines from the last 10 minutes (also takes timestamps) |
-t | prefix each line with its timestamp |
Logs survive a container stopping; they're deleted with docker rm.
Where logs are stored, and why they fill disks
By default Docker uses the json-file logging driver: one JSON file per container on the host, with no size limit.
A chatty container left running for months can fill a server's disk.
$ docker info -f '{{.LoggingDriver}}'json-file$ docker inspect -f '{{.LogPath}}' talky/var/lib/docker/containers/a6e2d74169.../a6e2d74169...-json.log
Cap it per container:
$ docker run -d --name rotated --log-opt max-size=10m --log-opt max-file=3 alpine true$ docker inspect -f '{{json .HostConfig.LogConfig}}' rotated{"Type":"json-file","Config":{"max-file":"3","max-size":"10m"}}
Or set the same defaults for every container in the daemon's daemon.json with "log-opts". Docker also offers a
local driver that rotates and compresses by default, plus drivers that ship logs to journald, syslog or cloud
services.
Getting inside: exec
docker exec runs an extra process in a running container's namespaces. With -it, that process is an interactive
shell:
$ docker exec -it notes sh$ docker exec notes whoaminode$ docker exec -u root notes whoamiroot
exec runs as the image's USER unless you pass -u. It only works on running containers, and only if the image
has the program you ask for: distroless images have no sh at all (02.07 Choosing a base image).
To move files without a shell, use docker cp, which works on stopped containers too:
docker cp notes:/app/package.json ./from-container.json
docker cp ./debug.json notes:/tmp/debug.jsonYour app writes its logs to /var/log/app.log inside the container. What does docker logs show?
The official nginx image handles this by symlinking its log files to /dev/stdout and /dev/stderr, a handy trick
for software you can't reconfigure.
Run the notes API, then in a second terminal docker logs -f notes. Post a few notes and watch nothing appear (it only logs at startup and shutdown). Stop it and watch the SIGTERM received line arrive. Start it again with --log-opt max-size=1m and confirm the setting with docker inspect. Use docker exec to list /app/dist.
STUCK? ▸
docker exec says the container isn't running: start it first. "executable file not found": the image has no shell at that path; try sh instead of bash.
Recap
Interview questions
How do logs work for Docker containers, and how would you handle them in production?