Docker logs location: where container logs are stored, and how to save them to a file

Ask Docker where a container’s log is and it will tell you. Then, under most setups, it tells you nothing at all.

Here is the command:

docker inspect --format '{{.LogPath}}' my-container

On a stock Linux install that prints a path like this:

/var/lib/docker/containers/4f1c…e9a2/4f1c…e9a2-json.log

An empty line instead means one of the cases below. Check which logging driver the container uses first:

docker inspect --format '{{.HostConfig.LogConfig.Type}}' my-container

Where the file is, by driver

Driver Log file LogPath shows it?
json-file (the default) /var/lib/docker/containers/<id>/<id>-json.log Yes
local /var/lib/docker/containers/<id>/local-logs/container.log No
journald None. The lines go into the systemd journal No
syslog, gelf, fluentd and the rest None you should read. A local cache of up to 100 MB sits at /var/lib/docker/containers/<id>/container-cached.log No
none Nothing is written anywhere No

The local driver leaves LogPath empty on purpose. Docker’s source says so: publishing the path would turn the file format into an API it has to keep stable.

Do not cat a local log or the cache file. Both are binary: length-prefixed protobuf records, with older files gzipped. Read them with docker logs.

Under journald, read the journal instead:

journalctl CONTAINER_NAME=my-container

Two more reasons the path is empty

The container never started. Docker fills in LogPath when the container starts. A container that was created and never run has no path, even under json-file.

You are on Docker Desktop. Mac and Windows run the engine inside a Linux VM. The path LogPath prints is inside that VM’s disk, not on your Mac. ls on the host finds nothing. Use docker logs.

When it is not under /var/lib/docker

/var/lib/docker is only the default data root. Two setups move it.

  • Rootless Docker keeps its data in ~/.local/share/docker, so the logs are under ~/.local/share/docker/containers/<id>/.
  • A custom data-root in /etc/docker/daemon.json moves everything, logs included. docker info --format '{{.DockerRootDir}}' prints where it went.

LogPath always prints the real location, so trust it over any path you remember.

What is inside a json-file log

One JSON object per line:

{"log":"GET /health 200\n","stream":"stdout","time":"2026-10-04T17:02:11.123456789Z"}

log is the text, newline included. stream says stdout or stderr. time is when Docker received the line, in UTC.

Lines longer than 16 KiB are split across several entries. Only the last piece ends in \n.

That detail matters when you read the file with jq. Use -j, not -r:

sudo jq -j '.log' "$(docker inspect --format '{{.LogPath}}' my-container)"

-r adds a newline after every entry. Each line comes out double-spaced, and every split line comes out in pieces. -j adds nothing, so the newlines already in log do the work.

Only stderr:

sudo jq -j 'select(.stream=="stderr") | .log' "$(docker inspect --format '{{.LogPath}}' my-container)"

Read the file. Do not edit it. Docker keeps its own count of the file’s size, and changing the file underneath it confuses rotation. Limiting Docker log size covers what goes wrong.

Save docker logs to a file

This looks right and loses half the output:

docker logs my-container > my-container.log

docker logs keeps the container’s two streams apart. Its stdout goes to your stdout and its stderr goes to your stderr. Most applications log errors to stderr, so the redirect above saves everything except the errors.

Send both:

docker logs my-container > my-container.log 2>&1

Or keep them apart on purpose:

docker logs my-container > stdout.log 2> stderr.log

A container started with -t is the exception. A TTY merges the two streams before Docker sees them, so everything arrives on stdout.

Narrow it before you save it:

docker logs --since 24h --timestamps my-container > my-container.log 2>&1
docker logs --since 2026-10-04T09:00:00 --until 2026-10-04T10:00:00 my-container > incident.log 2>&1

--since and --until take a duration like 30m or 3h, a date, or an RFC 3339 timestamp. A timestamp with no offset is read in your local time zone. --timestamps prefixes each line with the time Docker received it.

Compose

Compose behaves differently, and here it is the friendlier one:

docker compose logs --no-log-prefix api > api.log

docker compose logs merges stderr into stdout itself, so a plain > keeps both. It drops the colours when the output is not a terminal. --no-log-prefix drops the api-1 | in front of each line, which you want in a file of one service and do not want in a file of several.

Before you remove the container

docker rm deletes the container’s directory, and the log goes with it. So does docker compose down, and so does a docker compose up -d that recreates a container after a config change.

Save first if you might need it:

docker logs my-container > my-container.log 2>&1 && docker rm my-container

Stop saving files by hand

A saved file answers today’s question. The next incident is in a container that was redeployed an hour before anyone looked.

The fix is to keep a copy that does not belong to the container. Switch the logging driver to journald and docker logs keeps working. Each line also lands in the journal, tagged with CONTAINER_NAME. Then ship the journal with the CL Agent, and every container’s history survives the container.

Sending Docker container logs to a central server walks through the driver change, the Compose syntax, and the queries worth running once the logs land.

Where to go next

💌 Get notified on new features and updates

Only sent when a new version is released. Nothing else.