Docker Daemon Configuration
daemon.json changes every container on the host, and a malformed file stops Docker starting at all. Validate before you reload - and see the trap where reload applies a setting but removing it does not take it away.
Operations and Troubleshooting Guide 29 of 46 Advanced
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- Docker Compose5.4.0
- Architectureamd64
- TimeAbout 14 min
- Reviewed21 August 2026
Tested on the versions above. Every change here was applied to a real host and then reverted; the reverted state was verified. Your daemon may already have a daemon.json - back it up before editing.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- sudo on the host, and an understanding that this affects every container on it.
- Per-container logging options - guide 31 in this path.
-
See the settings before you change them
docker inforeports the daemon's effective configuration, which is what actually applies rather than what a file says. Record these before editing so you can tell what your change did and revert precisely.bash docker info --format "logging={{.LoggingDriver}} live-restore={{.LiveRestoreEnabled}} data-root={{.DockerRootDir}}"logging=json-file live-restore=false data-root=/var/lib/dockersudo test -f /etc/docker/daemon.json && sudo cp /etc/docker/daemon.json /tmp/daemon.bak || echo "no existing /etc/docker/daemon.json"Expected resultThe current effective settings, and a backup if a config file already existed.
Success conditionYou have a baseline and a backup. On many hosts there is no daemon.json at all and every setting is a default.
-
A malformed file stops Docker starting
This is why the next step matters.
dockerd --validateparses a config file and reports on it WITHOUT touching the running daemon - so you find out about a trailing comma now rather than when the service refuses to come back up.bash printf '%s' '{"log-driver": "json-file",}' | sudo tee /tmp/bad.json >/dev/nullsudo dockerd --validate --config-file /tmp/bad.jsonunable to configure the Docker daemon with file /tmp/bad.json: invalid JSON: invalid character '}' looking for beginning of object key stringExpected resultA clear parse error, exit status 1, and a daemon that is still running normally.
Success conditionYou have seen it fail safely. Had that file been installed and the daemon restarted, Docker would not have come back.
-
Validate the real configuration, then install it
Same command against the file you actually intend to use.
configuration OKis what you want before the file goes anywhere near/etc/docker/.bash printf '%s' '{"log-driver":"json-file","log-opts":{"max-size":"10m","max-file":"3"},"live-restore":true}' | sudo tee /tmp/good.json >/dev/nullsudo dockerd --validate --config-file /tmp/good.jsonconfiguration OKsudo mkdir -p /etc/docker && sudo cp /tmp/good.json /etc/docker/daemon.jsonExpected result
configuration OK, then the file in place.Success conditionValidation passed before installation. Make this the habit - it costs one command and prevents the worst outcome.
-
Reload rather than restart where you can
systemctl reload dockersends SIGHUP, which re-reads the config without stopping containers. It handles a useful subset - logging defaults, live-restore, insecure registries, registry mirrors. Settings it cannot apply live, such asdata-root, need a full restart.bash sudo systemctl reload dockerdocker info --format "logging={{.LoggingDriver}} live-restore={{.LiveRestoreEnabled}}"logging=json-file live-restore=trueExpected resultlive-restore now true, with containers untouched.
Success conditionThe setting applied without a restart.
data-rootand similar structural options are the exceptions - those require stopping the daemon. -
The trap: reload applies changes but does not un-apply them
Now remove the file entirely and reload again. The setting does NOT revert - live-restore is still enabled with no configuration file present at all. SIGHUP merges what it finds; it does not reset options back to their defaults. Only a full restart clears them.
bash sudo rm -f /etc/docker/daemon.json && sudo systemctl reload dockersudo test -f /etc/docker/daemon.json && echo present || echo "daemon.json absent"daemon.json absentdocker info --format "live-restore={{.LiveRestoreEnabled}}"live-restore=trueExpected resultThe file gone, and the setting still in effect.
Verify it worked
bash Example session sudo systemctl restart dockerdocker info --format "live-restore={{.LiveRestoreEnabled}}"live-restore=falseSuccess conditionYou have reproduced the trap. This is why a host can behave in ways its config file does not explain - somebody reloaded after removing a setting and assumed it had gone.
-
The settings worth knowing
A small set covers most real needs.
data-rootmoves Docker's storage to a bigger disk and is the usual answer to a full root filesystem. Proxy settings let the daemon pull images through a corporate proxy - note these configure the DAEMON, not your containers. Log defaults apply rotation to every new container so nobody has to remember it.bash cat /etc/docker/daemon.json{ "data-root": "/mnt/docker", "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "live-restore": true, "default-address-pools": [{"base":"172.30.0.0/16","size":24}]}docker info --format "proxy: http={{.HTTPProxy}} https={{.HTTPSProxy}} no={{.NoProxy}}"proxy: http= https= no=Expected resultA representative configuration, and the daemon's current proxy settings.
Success conditionYou can name what each does.
default-address-poolsis the fix when Docker's default bridge ranges collide with your corporate network. -
Daemon defaults are not stamped onto containers
A subtlety worth knowing when debugging. With log rotation set as a daemon default, a new container's own
LogConfigstill shows empty options - the default is applied by the daemon at runtime rather than recorded on the container. So an empty map here does not mean rotation is off.bash docker run -d --name cg-cfg alpine:3.22 sleep 60docker inspect cg-cfg --format "{{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}"json-file map[]docker rm -f cg-cfgExpected resultAn empty options map on a container covered by a daemon-level default.
Success conditionYou will not misread this as rotation being disabled. Check
docker infofor the daemon default, and the container only for per-container overrides.
Troubleshooting
Docker will not start after editing daemon.json
Why: Malformed JSON or an unrecognised key. The daemon refuses to start rather than run with a configuration it cannot parse.
Fix:Validate the file, and read the service log for the specific complaint. Move the file aside to get the daemon back up while you fix it.
bash sudo dockerd --validate --config-file /etc/docker/daemon.jsonsudo journalctl -u docker -n 30 --no-pagerA setting was removed but is still in effect
Why: Reload merges configuration; it does not reset removed options to their defaults.
Fix:Restart the daemon rather than reloading when you have removed a setting.
bash sudo systemctl restart dockerChanging data-root had no effect
Why: It cannot be applied by a reload - the daemon has to restart to use a different storage location.
Fix:Stop the daemon, move the existing data if you need to keep it, then start. Copying /var/lib/docker takes time proportional to its size.
bash # stop the daemon before moving its data directorysudo systemctl stop docker && sudo rsync -a /var/lib/docker/ /mnt/docker/Containers cannot reach the internet behind a corporate proxy
Why: Daemon proxy settings cover image pulls by the daemon. Containers need their own proxy environment.
Fix:Set the daemon proxy for pulls, and pass proxy variables into containers separately - the two are configured independently.
bash docker info --format "{{.HTTPProxy}}"docker run --rm -e HTTPS_PROXY=http://proxy:3128 alpine:3.22 wget -qO- https://example.com