How to Manage and Configure the Docker Daemon: Settings, Logging, and Runtime Options

How to Manage and Configure the Docker Daemon

The docker CLI is what most people interact with daily, but almost everything that actually matters — where images and containers are stored, how logging works, which network ranges are used, resource limits, security defaults — is controlled by the daemon, dockerd, and its configuration file. This guide walks through the daemon’s configuration surface end to end: where it lives, how to change it safely, and the options you’ll actually use in production.

Where Daemon Configuration Lives

On Linux, the daemon reads its configuration from /etc/docker/daemon.json, a plain JSON file. If it doesn’t exist yet:

sudo mkdir -p /etc/docker
sudo touch /etc/docker/daemon.json

Check what’s currently configured:

cat /etc/docker/daemon.json

If empty or missing, Docker runs with sane built-in defaults — you only need this file to override them.

You can also inspect the daemon’s live, effective configuration (merged defaults + file + any command-line flags):

docker info

Editing and Reloading Configuration

After editing daemon.json, validate the JSON syntax before restarting (a syntax error will prevent the daemon from starting at all):

python3 -m json.tool /etc/docker/daemon.json

Reload the daemon:

sudo systemctl reload docker

Some settings support a live reload via SIGHUP without a full restart (e.g., log level, registry mirrors, insecure registries, max concurrent downloads); others (like data-root or the storage driver) require a full restart:

sudo systemctl restart docker

Check the daemon actually came back up cleanly:

sudo systemctl status docker
journalctl -u docker --since "5 minutes ago"

Core Configuration Options

Changing the Data Root

By default, Docker stores images, containers, and volumes under /var/lib/docker. On many servers this fills the root partition — moving it to a larger disk is one of the most common daemon config changes:

{
  "data-root": "/mnt/docker-data"
}

Stop Docker, move the existing data, then restart:

sudo systemctl stop docker
sudo rsync -aP /var/lib/docker/ /mnt/docker-data/
sudo mv /var/lib/docker /var/lib/docker.old
sudo systemctl start docker

Verify:

docker info --format '{{.DockerRootDir}}'
/mnt/docker-data

Once confirmed working, remove the old directory to reclaim space.

Configuring Logging Defaults

By default, Docker uses the json-file log driver with no size limit — a chatty container can fill your disk over time. Set sane defaults globally:

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

This caps each container’s logs at 3 rotated files of 10MB each. Confirm per-container after restart:

docker inspect --format '{{.HostConfig.LogConfig}}' <container_id>
{json-file map[max-file:3 max-size:10m]}

For centralized logging, switch drivers entirely, e.g. to send logs to a syslog server:

{
  "log-driver": "syslog",
  "log-opts": {
    "syslog-address": "udp://logs.internal:514"
  }
}

Setting the Storage Driver

Modern Docker installs default to overlay2 on most Linux distributions, which is almost always the right choice. Confirm what’s active:

docker info --format '{{.Driver}}'
overlay2

Override only if you have a specific reason (e.g., certain older filesystems):

{
  "storage-driver": "overlay2"
}

Registry Mirrors and Insecure Registries

If you run a local pull-through cache or a private registry without valid TLS certs (lab environments only):

{
  "registry-mirrors": ["https://mirror.internal.example.com"],
  "insecure-registries": ["registry.internal:5000"]
}

Default Address Pools (Avoiding Network Overlaps)

If you run many Compose projects or Swarm networks, Docker’s default bridge subnet ranges can collide with your existing infrastructure. Define custom pools:

{
  "default-address-pools": [
    {"base": "172.30.0.0/16", "size": 24},
    {"base": "172.31.0.0/16", "size": 24}
  ]
}

Limiting Concurrent Downloads/Uploads

On constrained networks, tune registry throughput:

{
  "max-concurrent-downloads": 3,
  "max-concurrent-uploads": 3
}

Enabling Experimental Features

Some features (like certain BuildKit capabilities or checkpoint/restore) ship behind an experimental flag:

{
  "experimental": true
}

Setting Default Runtime and Adding Custom Runtimes

If you’re using gVisor, Kata Containers, or NVIDIA’s container runtime for GPU workloads:

{
  "default-runtime": "runc",
  "runtimes": {
    "nvidia": {
      "path": "/usr/bin/nvidia-container-runtime"
    }
  }
}

Use a non-default runtime per container:

docker run --runtime=nvidia --rm nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi

A Complete Production-Style daemon.json

Putting several of the above together into a realistic configuration:

{
  "data-root": "/mnt/docker-data",
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "default-address-pools": [
    {"base": "172.30.0.0/16", "size": 24}
  ],
  "max-concurrent-downloads": 5,
  "live-restore": true,
  "userland-proxy": false,
  "metrics-addr": "127.0.0.1:9323",
  "experimental": false
}

A few notable entries worth calling out:

  • live-restore: true — keeps containers running even if the daemon itself crashes or is upgraded, by relying on containerd‘s independent process supervision.
  • userland-proxy: false — disables Docker’s userspace TCP/UDP proxy for published ports, relying purely on iptables NAT rules instead, which is faster and reduces one moving part.
  • metrics-addr — exposes Prometheus-compatible daemon metrics on a local-only address.

Command-Line Flags vs daemon.json

Everything in daemon.json has an equivalent dockerd command-line flag, but mixing both is a common source of the “why won’t my config apply” problem — if systemd starts dockerd with explicit -H or other flags, they can conflict with or override daemon.json. Check what your systemd unit actually runs:

systemctl cat docker.service

If you see ExecStart=/usr/bin/dockerd -H fd:// with additional flags, and you want daemon.json to be the single source of truth, override it:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/override.conf <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
EOF
sudo systemctl daemon-reexec
sudo systemctl restart docker

Monitoring Daemon Health

Check resource usage and general daemon state at a glance:

docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          12        8         4.2GB     1.8GB (42%)
Containers      8         5         120MB     45MB (37%)
Local Volumes   6         4         2.1GB     0.5GB (23%)

Prune unused resources periodically (schedule via cron for hosts that accumulate cruft):

docker system prune -af --volumes

Stream daemon-level events for observability tooling:

docker events --filter type=container

Internal Working: How the Daemon Applies Configuration

At startup, dockerd merges configuration in this order of precedence: built-in defaults, then daemon.json, then command-line flags (flags win on conflict — which is exactly why leftover systemd flags silently override your JSON file). Once merged, the daemon initializes each subsystem — the storage driver, the network controller (libnetwork), the logging subsystem, and the runtime shim manager — using that final configuration. Some subsystems (logging level, registry config) support runtime reconfiguration via SIGHUP; others are wired in at process start and require a full restart to change, which is why data-root and storage-driver changes need systemctl restart rather than reload.

Best Practices

  • Keep daemon.json as the single source of truth; avoid duplicating settings via systemd override flags.
  • Always cap log file sizes (max-size/max-file) — an unbounded log driver is one of the most common causes of Docker hosts unexpectedly running out of disk.
  • Set live-restore: true in production so daemon upgrades or crashes don’t take down running workloads.
  • Version-control your daemon.json (e.g., via Ansible/Terraform/config management) rather than hand-editing production hosts.
  • Validate JSON syntax before every restart — a broken daemon.json will prevent Docker from starting, potentially taking down every container on the host if it also restarts.

Troubleshooting

SymptomCauseFix
Docker won’t start after config changeInvalid JSON syntaxValidate with python3 -m json.tool daemon.json before restarting
Config changes seem to have no effectSystemd unit overriding with its own flagsClear ExecStart via systemd override as shown above
Disk fills up despite max-size setSetting only applied going forward, not retroactivelyNew containers respect it; recreate long-running containers to pick up new log limits
data-root change loses existing imagesData not migrated before restartAlways rsync existing data to the new path before restarting with the new data-root

Summary

The Docker daemon’s behavior — where it stores data, how it logs, which networks it hands out, which runtimes it supports — is fully controllable through /etc/docker/daemon.json. Understanding the precedence between defaults, config file, and command-line flags (and clearing any systemd-injected flags that conflict with your file) is the key to configuration changes actually taking effect reliably.

References

  • Docker daemon configuration reference: https://docs.docker.com/engine/reference/commandline/dockerd/
  • dockerd JSON configuration file reference: https://docs.docker.com/engine/daemon/
  • Docker logging drivers: https://docs.docker.com/engine/logging/configure/
  • Docker storage drivers: https://docs.docker.com/engine/storage/drivers/
Total
0
Shares

Leave a Reply

Previous Post
How to Deploy flannel Overlay Between Docker Hosts

How to Deploy Flannel Overlay Between Docker Hosts: Cross-Host Container Networking Setup

Next Post
How to Replace Your Current Docker Binary with a New One

How to Replace Your Current Docker Binary with a New One: Upgrade and Installation Guide

Related Posts