Docker 49 🐳 Docker Daemon Configuration: Customizing /etc/docker/daemon.json
The Docker daemon is the background process that builds, runs, and manages every container on the host. It listens on the socket, pulls the images, starts the containers, and writes the logs. By default, it works well enough for the basic use, but the production environment demands the control over the logging, the storage, the networking, and the security. The primary way to configure the daemon is the single JSON file, the /etc/docker/daemon.json, which is the cleaner and the easier to version-control than the long list of the startup flags. The file is read when the daemon starts, and the options apply to the containers created after the restart.
The configuration file is not the only way to configure the daemon, but it is the recommended. The command-line flags are the alternative, and the two can be combined. The catch is that the same option must not appear in both the file and the flags, because the daemon detects the conflict and refuses to start. The error message names the conflicting option, and the fix is to remove one of the two. The JSON file is the declarative, and the flags are the imperative, and the modern Docker prefers the declarative.
The options in the file cover the wide range. The data-root moves the entire Docker data directory to a different disk. The log-driver and the log-opts control the logging and the rotation. The registry-mirrors and the insecure-registries control the image pulls. The default-ulimits and the host-gateway-ip control the resource limits and the host networking. Each option is the key in the JSON object, and the values are the strings, the numbers, or the nested objects.
This chapter covers three areas. First, why the daemon configuration exists — the default-versus-production gap and the declarative approach. Second, the file location and the key options — the daemon.json path, the storage, the logging, the registry, and the security. Third, the application and the verification — the systemctl reload, the docker info, the docker inspect, and the the common pitfalls. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the configuration flow.
Key point: The /etc/docker/daemon.json file is the central configuration for the Docker daemon. The data-root moves the data directory, the log-driver and the log-opts control the logging, the registry-mirrors and the insecure-registries control the pulls, and the default-ulimits controls the resource limits. The same option must not be set in both the file and the command-line flags. The changes apply only after the daemon restart, and only to the containers created after the restart.
Why the daemon configuration exists
The default-versus-production gap. The Docker defaults are tuned for the convenience, not the production. The json-file logging driver has no rotation, and a busy host fills the disk within days. The default data root is the /var/lib/docker on the root partition, which may be the small SSD. The default registry is the Docker Hub, which may be the slow or the blocked. The production requires the different choices, and the daemon.json is where they are made.
The declarative approach. The daemon can be configured with the command-line flags, but the flags are the long, the error-prone, and the hard to version-control. The daemon.json is the single file, and it can be committed to the configuration management, the Ansible, the Puppet, or the Git. The declarative file is the auditable, and the flags are the ephemeral.
The apply-on-restart problem. The daemon reads the daemon.json at the startup. The change to the file does not affect the running daemon until the restart, and the restart does not affect the existing containers. The existing containers keep the driver, the options, and the data root they were created with. The container must be recreated to pick up the new daemon defaults. The distinction is the source of the “why did my change not apply” confusion.
The conflict problem. The daemon merges the daemon.json and the command-line flags. If the same option is set in both, the daemon cannot decide which one wins, and it refuses to start. The error message names the option, and the fix is to remove one. The conflict is the common mistake, and the journalctl -u docker shows the error.
The live-reload problem. A subset of the options can be reloaded without the restart, and the SIGHUP triggers the reload. The live-restore, the debug, the labels, the max-concurrent-downloads, the max-concurrent-uploads, the default-runtime, the runtimes, the authorization-plugins, the insecure-registries, the registry-mirrors, the shutdown-timeout, and the features are the reloadable. The other options require the full restart.
The trade-off. The daemon.json is the single file, and the single file means the single point of the failure. A malformed JSON breaks the daemon start. The typo in the key is the silent ignore, and the option is not applied. The validation is the manual, and the docker info is the verification. The trade-off is between the simplicity of the single file and the need for the careful editing.
a. The file location and the structure
The file location depends on the operating system and the way the Docker runs.
| The environment | The file location |
|---|---|
| Linux (the regular) | /etc/docker/daemon.json |
| Linux (the rootless) | ~/.config/docker/daemon.json |
| Linux (the snap) | /var/snap/docker/current/config/daemon.json |
| Windows | C:\ProgramData\docker\config\daemon.json |
The file is the single JSON object, and the keys are the options.
{
"data-root": "/mnt/docker-data",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"compress": "true"
},
"registry-mirrors": ["https://mirror.example.com"],
"insecure-registries": ["myregistry.local:5000"],
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
}
}
The file does not exist by default on the fresh installation, and the daemon uses the built-in defaults. The creation of the file with the {} is the starting point, and the options are the keys.
The merge with the flags. The daemon reads the daemon.json and the dockerd flags. The flags are the --log-driver, the --data-root, and the others. The two are merged, and the conflict is the error.
The JSON validation. The file must be the valid JSON. The trailing comma, the unquoted key, and the wrong bracket are the errors. The python -m json.tool /etc/docker/daemon.json validates the file, and the jq is the alternative.
b. The key options
The storage. The data-root moves the entire Docker data directory:
{
"data-root": "/mnt/docker-data"
}
The move requires the stop of the daemon, the copy of the data, the update of the file, and the start of the daemon. The containerd image store keeps the image data in the /var/lib/containerd, and the data-root does not move it.
The logging. The log-driver and the log-opts control the logging:
{
"log-driver": "local",
"log-opts": {
"max-size": "10m"
}
}
The local is the recommended production default because it rotates by default. The json-file requires the explicit max-size and the max-file. The values inside the log-opts must be the strings.
The registry. The registry-mirrors and the insecure-registries control the pulls:
{
"registry-mirrors": ["https://mirror.example.com"],
"insecure-registries": ["myregistry.local:5000"]
}
The mirror is the cache for the Docker Hub, and the insecure registry is the private registry without the TLS.
The networking. The host-gateway-ip sets the IP that the host-gateway resolves to:
{
"host-gateway-ip": "192.168.1.1"
}
The --add-host=host.docker.internal:host-gateway uses the value, and the container reaches the host.
The resource limits. The default-ulimits sets the default limits:
{
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
}
}
The nofile is the open file descriptor limit, and the nproc is the process limit.
The live-restore. The live-restore keeps the containers running when the daemon restarts:
{
"live-restore": true
}
The option allows the daemon upgrade without the container downtime, and it is the important for the production.
The debug. The debug enables the debug output:
{
"debug": true
}
The debug is the verbose, and it is the temporary for the troubleshooting.
c. The application and the verification
The reload. The systemctl reload docker sends the SIGHUP and reloads the reloadable options. The systemctl restart docker restarts the daemon and applies all the options.
sudo systemctl daemon-reload
sudo systemctl restart docker
The daemon-reload reloads the systemd units, and the restart docker restarts the daemon. The two are the sequence after the daemon.json edit.
The verification. The docker info shows the applied options:
docker info --format '{{.LoggingDriver}}'
# local
docker info --format '{{.DockerRootDir}}'
# /mnt/docker-data
docker info --format '{{json .RegistryConfig.Mirrors}}'
# ["https://mirror.example.com"]
The docker info is the primary verification, and the docker inspect shows the per-container configuration.
The conflict detection. The journalctl -u docker shows the startup errors:
sudo journalctl -u docker -n 50
The conflict error names the option, and the fix is to remove one of the two.
The live-reload test. The kill -SIGHUP $(pidof dockerd) sends the signal, and the journalctl shows the reload messages.
Complete Example Session
// ============================================
// PART 1: THE BASIC FILE
// ============================================
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
// ============================================
// PART 2: THE DATA ROOT
// ============================================
{
"data-root": "/mnt/docker-data"
}
// The move requires the stop, the copy, and the start.
// ============================================
// PART 3: THE LOGGING
// ============================================
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
// The local driver rotates by default.
// ============================================
// PART 4: THE REGISTRY
// ============================================
{
"registry-mirrors": ["https://mirror.example.com"],
"insecure-registries": ["myregistry.local:5000"]
}
// ============================================
// PART 5: THE RESOURCE LIMITS
// ============================================
{
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
},
"nproc": {
"Name": "nproc",
"Hard": 4096,
"Soft": 4096
}
}
}
// ============================================
// PART 6: THE LIVE RESTORE
// ============================================
{
"live-restore": true
}
// The containers keep running when the daemon restarts.
// ============================================
// PART 7: THE HOST GATEWAY
// ============================================
{
"host-gateway-ip": "192.168.1.1"
}
// The --add-host=host.docker.internal:host-gateway uses it.
// ============================================
// PART 8: THE COMPLETE FILE
// ============================================
{
"data-root": "/mnt/docker-data",
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "5",
"compress": "true"
},
"registry-mirrors": ["https://mirror.example.com"],
"insecure-registries": ["myregistry.local:5000"],
"live-restore": true,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
}
}
// ============================================
// PART 9: THE APPLICATION
// ============================================
# The validate
python -m json.tool /etc/docker/daemon.json
# The reload
sudo systemctl daemon-reload
sudo systemctl restart docker
# The verify
docker info --format '{{.LoggingDriver}}'
docker info --format '{{.DockerRootDir}}'
// ============================================
// PART 10: THE LFCS PIPELINE
// ============================================
# The edit
sudo vi /etc/docker/daemon.json
# The validate
sudo python -m json.tool /etc/docker/daemon.json
# The apply
sudo systemctl daemon-reload
sudo systemctl restart docker
# The verify
docker info --format '{{json .}}' | jq '.LoggingDriver, .DockerRootDir'
# The conflict check
sudo journalctl -u docker -n 20 | grep -i conflict
# The live reload
sudo systemctl reload docker
The ten parts show the basic file, the data root, the logging, the registry, the resource limits, the live restore, the host gateway, the complete file, the application, and the LFCS pipeline.
Quick Reference
The File Locations
| The environment | The path |
|---|---|
| Linux (the regular) | /etc/docker/daemon.json |
| Linux (the rootless) | ~/.config/docker/daemon.json |
| Linux (the snap) | /var/snap/docker/current/config/daemon.json |
| Windows | C:\ProgramData\docker\config\daemon.json |
The Common Options
| The option | The purpose |
|---|---|
data-root | The data directory |
log-driver | The logging driver |
log-opts | The driver options |
registry-mirrors | The pull mirrors |
insecure-registries | The non-TLS registries |
default-ulimits | The resource limits |
live-restore | The daemon restart |
host-gateway-ip | The host gateway |
debug | The debug output |
default-runtime | The runtime |
The Reloadable Options
| The option | The reload |
|---|---|
debug | Yes |
labels | Yes |
live-restore | Yes |
max-concurrent-downloads | Yes |
max-concurrent-uploads | Yes |
registry-mirrors | Yes |
insecure-registries | Yes |
shutdown-timeout | Yes |
data-root | No |
log-driver | No |
The Commands
| The command | The purpose |
|---|---|
python -m json.tool | The validate |
systemctl daemon-reload | The systemd reload |
systemctl restart docker | The full restart |
systemctl reload docker | The SIGHUP reload |
docker info | The verification |
journalctl -u docker | The logs |
Best Practices
✅ Do This:
// Use the local driver for the production
{
"log-driver": "local",
"log-opts": { "max-size": "10m" }
} // ✅
// Use the live-restore for the production
{
"live-restore": true
} // ✅
// Use the mirror for the speed
{
"registry-mirrors": ["https://mirror.example.com"]
} // ✅
// Validate the JSON before the restart
// python -m json.tool /etc/docker/daemon.json // ✅
// Use the strings in the log-opts
"max-file": "3" // ✅
❌ Don’t Do This:
// Don't set the option in both the file and the flags
{"log-driver": "json-file"} // and the --log-driver flag // ❌
// Don't use the numeric value in the log-opts
{"max-file": 3} // the string is required // ❌
// Don't forget the restart
// The change is not applied without the restart // ❌
// Don't expect the existing containers to change
// The recreate is required // ❌
// Don't use the trailing comma
{"log-driver": "json-file",} // the invalid JSON // ❌
// Don't move the data root with the running daemon
// The stop and the copy are required // ❌
Common Pitfalls
| The pitfall | The why | The fix |
|---|---|---|
| The daemon does not start | The invalid JSON | Validate with the python -m json.tool |
| The option is ignored | The typo in the key | Check the docker info |
| The conflict error | The file and the flags | Remove one of the two |
| The change is not applied | The no restart | Restart the daemon |
| The existing containers unchanged | The container-scoped | Recreate the container |
| The log rotation not working | The json-file without the opts | Use the local or the max-size |
| The data root not moved | The containerd store | Check the /var/lib/containerd |
Real-World Examples
1. The Log Rotation
{"log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}}
2. The Local Driver
{"log-driver": "local", "log-opts": {"max-size": "10m"}}
3. The Data Root
{"data-root": "/mnt/docker-data"}
4. The Registry Mirror
{"registry-mirrors": ["https://mirror.example.com"]}
5. The Insecure Registry
{"insecure-registries": ["myregistry.local:5000"]}
6. The Ulimits
{"default-ulimits": {"nofile": {"Name": "nofile", "Hard": 65536, "Soft": 65536}}}
7. The Live Restore
{"live-restore": true}
8. The Host Gateway
{"host-gateway-ip": "192.168.1.1"}
9. The Validate
python -m json.tool /etc/docker/daemon.json
10. The Apply
sudo systemctl daemon-reload && sudo systemctl restart docker
Visual
The Configuration Flow
┌──────────────────────────────────────────────────────────────┐
│ THE CONFIGURATION FLOW │
│ │
│ /etc/docker/daemon.json │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "log-driver": "local", │ │
│ │ "data-root": "/mnt/docker-data" │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ sudo systemctl daemon-reload │
│ │ sudo systemctl restart docker │
│ ▼ │
│ The Docker daemon: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Reads the file at the startup. │ │
│ │ Merges with the flags. │ │
│ │ Applies the options. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ The new containers: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The local driver, the new data root. │ │
│ │ │ │
│ │ The existing containers keep the old settings. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Default vs the Production
┌──────────────────────────────────────────────────────────────┐
│ THE DEFAULT vs THE PRODUCTION │
│ │
│ THE DEFAULT: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The log-driver: the json-file (no rotation) │ │
│ │ The data-root: the /var/lib/docker │ │
│ │ The registry: the Docker Hub │ │
│ │ The live-restore: the false │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ THE PRODUCTION: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The log-driver: the local (the rotation) │ │
│ │ The data-root: the /mnt/docker-data │ │
│ │ The registry: the mirror │ │
│ │ The live-restore: the true │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The daemon.json is where the defaults become the │
│ production choices. │
│ │
└──────────────────────────────────────────────────────────────┘
The Live Reload vs the Restart
┌──────────────────────────────────────────────────────────────┐
│ THE LIVE RELOAD vs THE RESTART │
│ │
│ THE LIVE RELOAD (the SIGHUP): │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ systemctl reload docker │ │
│ │ │ │
│ │ The reloadable options: │ │
│ │ the debug, the labels, the live-restore, │ │
│ │ the registry-mirrors, the insecure-registries │ │
│ │ │ │
│ │ The containers keep running. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ THE RESTART: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ systemctl restart docker │ │
│ │ │ │
│ │ All the options are applied. │ │
│ │ │ │
│ │ The containers stop and start (unless the │ │
│ │ live-restore is enabled). │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Conflict Detection
┌──────────────────────────────────────────────────────────────┐
│ THE CONFLICT DETECTION │
│ │
│ The daemon.json: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ { "log-driver": "json-file" } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The command line: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ dockerd --log-driver=local │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ The error: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ unable to configure the Docker daemon with file │ │
│ │ /etc/docker/daemon.json: the following directives │ │
│ │ are both in the file and on the command line: │ │
│ │ log-driver │ │
│ │ │ │
│ │ The fix: remove one of the two. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
/etc/docker/daemon.json | The daemon configuration |
data-root | The data directory |
log-driver | The logging driver |
log-opts | The driver options |
registry-mirrors | The pull mirrors |
insecure-registries | The non-TLS registries |
default-ulimits | The resource limits |
live-restore | The daemon restart |
host-gateway-ip | The host gateway |
| The reload | systemctl reload docker |
| The restart | systemctl restart docker |
Key takeaways:
- The
/etc/docker/daemon.jsonfile is the central configuration for the Docker daemon. The file is the declarative, and the command-line flags are the imperative. The two can be combined, but the same option must not appear in both . - The
data-rootmoves the entire Docker data directory. The move requires the stop, the copy, and the restart. The containerd image store keeps the image data separately in the/var/lib/containerd. - The
log-driverand thelog-optscontrol the logging and the rotation. Thelocaldriver rotates by default, and thejson-filerequires themax-sizeand themax-file. The values inside thelog-optsmust be the strings . - The
registry-mirrorsand theinsecure-registriescontrol the pulls. The mirror speeds up the Docker Hub, and the insecure registry allows the private registries without the TLS . - The
default-ulimitssets the default resource limits. Thenofileis the open file descriptor limit, and thenprocis the process limit . - The
live-restorekeeps the containers running when the daemon restarts. The option allows the daemon upgrade without the container downtime, and it is the important for the production . - The changes apply only after the daemon restart. The
systemctl restart dockerapplies all the options, and thesystemctl reload dockerapplies only the reloadable ones. The existing containers keep the settings they were created with . - The conflict between the file and the flags prevents the daemon start. The
journalctl -u dockershows the conflict error, and the fix is to remove one of the two .
Remember: The /etc/docker/daemon.json file is the central control panel for the Docker host. The data-root moves the data, the log-driver and the log-opts control the logging, the registry-mirrors and the insecure-registries control the pulls, and the default-ulimits controls the resource limits. The changes apply only after the restart, and the existing containers must be recreated. The result is the controlled, the observable, and the production-ready Docker host.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!