DockerSelf-Hosting

Docker Log Rotation for Homelabs: The Fix I Apply Before json-file Eats My Disk

Stop Docker logs from filling your homelab server. Marcus Chen explains json-file rotation, local logging, Compose overrides, and verification.

AU

Author

Marcus Chen

Docker Log Rotation for Homelabs: The Fix I Apply Before json-file Eats My Disk

FTC disclosure: This article contains affiliate links. If you purchase through these links, we may earn a commission at no additional cost to you. We only recommend products we have personally tested or thoroughly researched.

Key Takeaways

  • Docker does not rotate json-file logs by default, which is how one noisy container quietly turns a calm server into a disk-space hostage situation.
  • For most homelabs, the two sane options are: keep json-file and set max-size / max-file, or switch the host default to Docker's local logging driver.
  • Changing /etc/docker/daemon.json only affects newly created containers. That line has ruined many evenings, including one of mine.
  • You should set a host-wide default, then override per service in Compose when a container is especially noisy or retention-sensitive.
  • Rotation is not the same thing as observability. Rotate local logs so the host survives, then centralize what you actually need to keep.

After running Docker long enough, you stop fearing dramatic failures and start fearing the boring ones. The really annoying outages are not kernel panics or dead SSDs. They are things like a single chatty container dumping gigabytes of logs into /var/lib/docker/containers until the host runs out of space and starts making terrible life choices.

I learned this the usual way - by not noticing it until image pulls failed, disk alerts lit up, and one service decided that writing to a full filesystem was an exciting new hobby. Docker log rotation is one of those fixes that takes ten minutes, saves hours, and somehow still gets postponed because the server is "working fine" right up until it isn't.

If your homelab runs more than a handful of containers, you should fix this now.

Why Docker log rotation matters more in a homelab than people admit

A lab box is usually small, quiet, and always on. That is part of the appeal. It is also why bad logging defaults hurt more than they would on a larger production platform with aggressive monitoring and dedicated log infrastructure.

A mini PC with a single NVMe drive does not care that your container only emits "a little" debug output every second. It cares about math. And math is famously unsentimental.

By default, Docker uses the json-file logging driver and does not impose rotation unless you configure it. That means the log file keeps growing until you do something about it.

The failure mode is annoying because it sneaks up on you:

  1. Everything looks normal.
  2. One container gets noisy.
  3. The host disk slowly fills.
  4. Pulls, writes, backups, or updates start failing in weird ways.
  5. You spend half an hour investigating the wrong thing before remembering to check disk usage.

If this sounds oddly specific, that is because I have already made the mistake for both of us.

Where Docker logs live on the host

When Docker uses json-file, container logs usually end up under a path like this:

/var/lib/docker/containers/<container-id>/<container-id>-json.log

You can prove what is happening on your host with a few commands:

sudo du -sh /var/lib/docker/containers
sudo find /var/lib/docker/containers -name '*-json.log' -printf '%s %p\n' | sort -nr | head -20
sudo docker info --format '{{.LoggingDriver}}'

That last command tells you the Docker daemon's default logging driver. On a lot of homelab boxes, the answer is still json-file.

If you want a broader refresher on Docker logging before you tighten retention, read Docker Logs: A Complete Beginner's Guide to Container Logging in Your Homelab. This article assumes you already know the basics and want the part that keeps the host alive.

The two sane approaches

There are really two practical paths here.

Option 1: Keep json-file, but rotate it properly

This is the safest upgrade if you want minimal behavioral change. You keep the default driver, but set a size limit and a file count.

That usually means adding:

  • max-size
  • max-file

For many homelabs, something like 10 MB per file and 3 to 5 files per container is a decent starting point. Not sacred. Just sane.

Option 2: Switch the host default to local

This is the option I recommend for many standalone Docker hosts.

Docker's own docs now explicitly note that the local logging driver performs rotation by default and is generally better at preventing disk exhaustion outside compatibility-sensitive situations. In other words, Docker is politely telling you that old defaults are old defaults, not a badge of wisdom.

My recommendation

If your homelab is plain Docker or Docker Compose on a normal Linux host, I would strongly consider using local as the host default. If you want maximum familiarity or you rely on json-file behavior for something specific, keep json-file but set explicit limits.

If you are running Docker as part of something with stricter compatibility assumptions, read the official docs first and stay conservative.

Quick comparison: json-file vs local

Driver Good at Watch out for My take
json-file Familiarity, compatibility, easy examples everywhere No rotation by default unless you configure it Fine if you set limits explicitly
local Built-in rotation, more efficient local storage for many homelab use cases Less commonly covered in older tutorials Best default for many self-hosted Docker boxes

This is also a good example of why copying old Compose examples from random blog posts is risky. Plenty of those posts are still useful, but many were written when "works at all" counted as best practice. We can aim higher now.

For a broader cleanup pass on Compose hygiene, pair this with Docker Compose Best Practices in 2026: The Rules I Actually Follow on Real Homelab Servers.

Approach A: Set a host-wide json-file rotation policy

If you want to keep json-file, define the default in /etc/docker/daemon.json:

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

A few things matter here:

  • max-size is the size limit for each log file
  • max-file is how many rotated files Docker keeps
  • values in log-opts should be strings in daemon.json

That last detail is small and irritating (a classic configuration-file move). But if you forget the quotes, you are creating future-you's problem.

After editing the file:

sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
sudo systemctl restart docker
sudo systemctl status docker --no-pager

If Docker does not come back cleanly, check:

journalctl -u docker -n 100 --no-pager

Approach B: Make local the default logging driver

This is what I would use on a fresh general-purpose Docker host unless I had a specific reason not to.

{
  "log-driver": "local"
}

You can also set options with it, but the big win is simple: local rotates by default.

Apply the change the same way:

sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
sudo systemctl restart docker
sudo docker info --format '{{.LoggingDriver}}'

If the command returns local, the daemon default changed successfully.

Per-service Docker Compose overrides

Even if you set a host-wide default, I still like explicit logging config for especially noisy services. It documents intent, helps future maintenance, and prevents one copy-pasted service from ignoring the standards you thought everyone understood.

Here is a Compose example using json-file rotation:

services:
  app:
    image: ghcr.io/example/app:latest
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

And here is a slightly heavier service with more retention:

services:
  paperless:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "25m"
        max-file: "5"

This is one of those places where consistency matters more than cleverness. Pick a small set of defaults and reuse them.

If you already rely on health-aware startup sequencing, it also pairs well with Docker Health Checks Explained: The Probes I Actually Trust in Compose. Compose files age much better when the operational decisions are visible instead of implied.

The mistake I made: changing the daemon and assuming old containers were fixed

Here is the thing nobody tells you loudly enough: changing the daemon logging config does not retrofit existing containers.

I am going to repeat that because it causes real confusion.

New default logging settings apply to newly created containers. Existing containers keep the logging configuration they were created with.

So if you edit daemon.json, restart Docker, and then assume the old container from fourteen months ago is now magically rotating logs, that assumption is doing no useful work.

You need to recreate the container.

For Compose-managed services, that usually means:

docker compose up -d --force-recreate

Or, if you want to be slightly more deliberate:

docker compose pull
docker compose up -d --force-recreate

I prefer doing this during a small maintenance window rather than discovering at 11:40 PM that the settings changed cleanly for every service except the one actually causing the problem.

How to verify the new logging config is really active

Do not stop after editing config files. Verify.

Check the daemon default

docker info --format '{{.LoggingDriver}}'

Expected output should match your intended host default, such as json-file or local.

Check a specific container's logging config

docker inspect <container_name> --format '{{json .HostConfig.LogConfig}}'

Example output for a Compose service using json-file limits might look like:

{"Type":"json-file","Config":{"max-file":"3","max-size":"10m"}}

If you changed the daemon default but the container still shows old values, you have your answer. The container was not recreated.

Watch disk usage before and after

sudo du -sh /var/lib/docker/containers
sudo df -h /

Check for log spam at the source

Sometimes rotation is working, but the application is still shouting into the void. That is not a rotation problem. That is an application-behaving-like-an-application problem.

Use:

docker logs --tail 200 <container_name>

If the service is producing the same warning every second, fix the app, not just the bucket it is filling.

A practical baseline I recommend for most homelabs

If you want one copy-paste-safe default and do not want to overthink it, here is the baseline I would use for a small Docker host that runs normal self-hosted services.

Conservative json-file baseline

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

Fresh-host baseline with local

{
  "log-driver": "local"
}

Then add per-service overrides only when a workload genuinely needs them.

That is the part people skip. They either over-tune every container immediately, or they tune nothing. Both approaches waste time. Start with a sane host default and only customize the loud exceptions.

When rotation is not enough

Rotation protects the host. It does not replace observability.

If you actually need to retain logs for troubleshooting, search, or historical context, you should move beyond local files and centralize what matters. Otherwise you are using your Docker host as a reluctant log archive, which is a bit like using a step stool as a data center rack. Technically possible. Spiritually wrong.

For that next step, see Centralized Logging for Homelabs with Grafana Loki: The Stack I Actually Trust.

And while you are hardening the host, Docker Security Hardening for Homelabs: 10 Fixes I Recommend Before You Expose Anything is a good follow-up. The containers that fill disks are often the same ones that were launched with far too much trust and far too little supervision.

Recommended gear for a less fragile Docker host

You do not need expensive hardware for log rotation. You do need hardware that is not already losing an argument with basic disk pressure.

  • Fanless N100 mini PC for a dedicated Docker host - plenty for Compose stacks, quiet enough to forget it exists, which is exactly the point: Recommended N100 mini PCs on Amazon
  • 1TB NVMe SSD for container data and retained logs - a cheap way to stop pretending a tiny boot disk will age gracefully: 1TB NVMe SSD options
  • APC Back-UPS 1500VA - because repairing corrupted services after a power cut is a terrible way to spend a Saturday: APC Back-UPS 1500VA options

Lessons learned from doing this the lazy way first

The first time I fixed Docker log rotation, I focused on the config file and ignored the workflow around it. That was the wrong priority.

The actual durable fix was not just daemon.json. It was documenting a repeatable maintenance pattern: change the host default, recreate old containers, verify with docker inspect, then add a simple disk-usage check to my normal review routine.

That sounds boring because it is boring. Infrastructure that survives is often just a collection of boring habits you finally stopped arguing with.

My simple operating rule

Here is the rule I actually follow.

  • Set a host-wide logging default.
  • Recreate old containers after changing it.
  • Override only the noisy services.
  • Monitor disk usage.
  • Centralize important logs somewhere else.

That is enough for most homelabs. You do not need a grand observability manifesto before you stop a chatty container from chewing through local storage.

Common mistakes

1) Assuming Docker rotates logs by default

It does not for json-file. This is the root of the whole problem.

2) Editing daemon.json and forgetting to recreate containers

The daemon changed. Your old containers did not. That is the trap.

3) Setting enormous retention values "just in case"

If you need unlimited local logs on a small homelab server, what you actually need is a different architecture.

4) Ignoring the application itself

Rotation controls damage. It does not cure a broken app that emits the same error forever.

5) Treating local rotation like a full logging strategy

It is a safety control, not an observability platform.

FAQ

Should I use json-file or local for Docker log rotation?

For many self-hosted Docker boxes, I would start with local unless you need json-file for a compatibility reason. If you already use json-file, keeping it is fine as long as you explicitly set max-size and max-file.

Why did my Docker log rotation config not apply to an existing container?

Because Docker applies daemon-level logging changes to newly created containers, not existing ones. Recreate the container and verify its LogConfig with docker inspect.

What are good starter values for max-size and max-file?

For a typical homelab service, max-size: "10m" and max-file: "3" is a sensible starting point. Increase it only for containers that genuinely need more local retention.

Is Docker Compose log rotation different from daemon-level rotation?

It uses the same underlying Docker logging concepts, but you define them per service under logging: in your Compose file. I like daemon defaults for consistency and Compose overrides for the loud edge cases.

Do I still need centralized logging if I enable rotation?

Yes, if you care about searching, retention, or cross-service troubleshooting. Rotation protects the host. Centralized logging helps you investigate real problems later.

Final recommendation

If you want the short version, here it is: stop leaving Docker logging at the defaults.

For an existing homelab, set a safe host-wide policy today, recreate the containers that matter, and verify the result with docker inspect. For a fresh Docker host, I would seriously consider defaulting to local and moving on with my life.

That is not the most glamorous Docker improvement you will make this year. It might be the one that saves your next weekend.