A Docker host can run reliably for months and still fill its disk overnight or expose a port to your firewall that should have been blocked. These 12 practical tips use docker system prune, daemon.json, restart policies, and health checks to help prevent those problems.

Imagine receiving a disk-space alert from a server that has been running the same containers for months. Nothing new was deployed, yet /var/lib/docker has steadily grown because unused images, containers, volumes, and container logs were never cleaned up or rotated.

The fixes are straightforward. The tips below show how to manage Docker resources with the CLI and configure the Docker daemon through /etc/docker/daemon.json, the configuration file commonly used to control daemon-level settings such as logging.

TecMint Weekly Newsletter

Get the Learn Linux 7 Days Crash Course free when you join 34,000+ Linux professionals reading every Thursday.

Check your email for a magic link to get started.

Something went wrong. Please try again.

How These Docker Tips Were Chosen

Each tip addresses a common problem on real Docker hosts, progressing from host-wide settings to per-container policies and finally to troubleshooting commands. They apply across Ubuntu, Debian, Rocky Linux, AlmaLinux, and RHEL when using Docker Engine from Docker’s official repository. Only the editor commands may differ between distributions.

1. Run Docker Commands Without sudo Using the docker Group

The first tip avoids typing sudo before every Docker command, since users added to the docker group can communicate directly with the Docker daemon through /var/run/docker.sock:

sudo usermod -aG docker $USER
newgrp docker

The newgrp command applies the new group membership without requiring you to log out. However, membership in the docker group effectively grants root-level access to the host.


A user with Docker access can, for example, mount the host’s / filesystem inside a container. So only add trusted administrators to this group.

2. Free Disk Space with docker system df and docker system prune

Now let’s address the disk-space problem from the introduction. Start by checking how much space Docker is using and what can be reclaimed:

docker system df

Output:

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          14        3         6.42GB    5.1GB (79%)
Containers      5         3         12.3MB    1.2MB (9%)
Local Volumes   8         2         2.31GB    1.9GB (82%)
Build Cache     41        0         1.75GB    1.75GB

The RECLAIMABLE column shows how much space Docker can potentially recover. To remove stopped containers, unused networks, unused build cache, and dangling images, run:

docker system prune

For a more aggressive cleanup, add -a to remove all images not currently used by a container:

docker system prune -a

This can free significantly more space, but unused images will need to be pulled again when required. Volumes are not removed by default. Add --volumes only when you are certain that unused volumes can be deleted.

3. Rotate Container Logs with max-size in daemon.json

Pruning recovers disk space once, but container logs can continue growing. With Docker’s json-file logging driver, logs can grow indefinitely unless you configure rotation.

On Ubuntu and Debian, open the Docker daemon configuration with nano:

sudo nano /etc/docker/daemon.json

On Rocky Linux, AlmaLinux, and RHEL, minimal installations may include vi instead of nano:

sudo vi /etc/docker/daemon.json

Then add the following configuration, without a path comment, because Docker refuses to start if this JSON file contains one:

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

Here:

  • "log-driver": "json-file" keeps the default driver, so docker logs still works.
  • "max-size": "10m" starts a new log file once the current one reaches 10 MB.
  • "max-file": "3" keeps three files per container, capping its logs at about 30 MB.

Save the file and exit the editor, then restart Docker, which also restarts running containers:

sudo systemctl restart docker

This configuration applies to newly created containers. Existing containers retain their current logging configuration, so recreate them after changing the daemon settings if you want the new limits to apply.

If this stopped Docker from filling your root partition, share it with someone who still deletes container logs by hand.

4. Keep Containers Running After Reboots with –restart unless-stopped

With the host-wide settings in place, the next tips deal with individual containers, starting with reboots. By default, a container stays stopped after a reboot or crash, so a kernel update can take services offline:

docker run -d --name web --restart unless-stopped nginx

The unless-stopped policy restarts the container after crashes and reboots, but leaves it stopped if you ran docker stop yourself, and docker update –restart changes the policy on existing containers.

5. Limit Container Memory and CPU with –memory and –cpus

A restart policy keeps a container alive, but a leaking container can still use all the host’s memory and trigger the kernel’s out-of-memory (OOM) killer against unrelated processes:

docker run -d --name app --memory 512m --cpus 1.5 --restart unless-stopped nginx
  • --memory 512m sets a hard 512 MB limit, so when the container exceeds it, the kernel kills a process inside that container only.
  • --cpus 1.5 caps the container at one and a half CPU cores’ worth of time.

6. Bind Published Ports to localhost with -p 127.0.0.1

Published Docker ports can expose a service beyond the host itself. Docker installs its own firewall rules for published ports, and depending on the host’s firewall configuration, a port published with -p 5432:5432 may remain reachable even when a firewall such as UFW appears to deny it.

When a port is needed only by a local reverse proxy, monitoring tool, or other service on the same host, bind it explicitly to the loopback address:

docker run -d --name db -e POSTGRES_PASSWORD=changeme -p 127.0.0.1:5432:5432 postgres:16

This publishes PostgreSQL only on the host’s loopback interface rather than all host interfaces.

Check the listening address with:

sudo ss -tlnp | grep 5432

You should see 127.0.0.1:5432 rather than 0.0.0.0:5432 when the port is bound only to localhost.

If your database port turned out to be open to the network, share this with your team so they can check their hosts.

7. Detect Unresponsive Services with –health-cmd

A container can show as Up even when the application inside it is no longer responding correctly. Docker health checks let you periodically run a command inside the container and record whether the application is healthy.

Let’s recreate the db container from the previous tip using pg_isready, which is included in the official PostgreSQL image:

docker rm -f db
docker run -d --name db -e POSTGRES_PASSWORD=changeme \
  -p 127.0.0.1:5432:5432 \
  --health-cmd "pg_isready -U postgres" \
  --health-interval 30s --health-retries 3 \
  postgres:16

The health-check options control how Docker evaluates the service:

  • --health-cmd is the command Docker runs inside the container, where exit code 0 means healthy.
  • --health-interval 30s runs the check every 30 seconds.
  • --health-retries 3 marks the container unhealthy only after three failures in a row, which avoids false alarms.

After the health check runs, docker ps displays (healthy) in the container status when the check succeeds. Keep in mind that Docker’s built-in health check only records the container’s health state and it does not automatically restart an unhealthy container.

Combine health checks with an appropriate monitoring or orchestration mechanism when an unhealthy service needs automatic recovery.

8. Read Recent Logs with docker logs –since and –tail

When a health check fails, container logs are usually the first place to investigate; limit the output to the period and number of lines you need:

docker logs --since 30m --tail 100 -f db

This displays up to the last 100 log lines from the past 30 minutes and then follows new log entries as they appear. Press Ctrl+C to stop following the logs.

9. Watch Live Resource Usage with docker stats

Logs show what an application is doing, while docker stats shows how much CPU, memory, network, and disk I/O a running container is using. This makes it useful for checking the resource limits from Tip 5:

docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

Output:

NAME      CPU %     MEM USAGE / LIMIT
db        0.02%     38.4MiB / 7.75GiB
app       0.00%     9.1MiB / 512MiB
web       0.00%     8.9MiB / 7.75GiB

Here, app reports the 512 MB memory limit configured in Tip 5, and the other containers show the host’s available memory because they do not have an explicit memory limit.

10. Extract Container Details with docker inspect –format

The --format option uses Go templates to extract specific values from docker inspect without displaying its full JSON output.

For example, after an unexpected container restart, check whether Docker recorded an OOM kill:

docker inspect --format '{{.State.OOMKilled}}' app

A result of true means the container was killed because of an out-of-memory condition. It is a useful diagnostic signal, but it does not by itself prove that the container exceeded its configured Docker memory limit.

You can also inspect the configured memory limit with:

docker inspect --format '{{.HostConfig.Memory}}' app

The value is returned in bytes, with 0 meaning no container memory limit is configured.

11. Copy Files Into and Out of Containers with docker cp

Before changing a container’s configuration, use docker cp to make a backup on the host and it works even on stopped containers:

docker cp web:/etc/nginx/nginx.conf ./nginx.conf.bak

This copies the file from the web container to the current directory, giving you a local backup that you can inspect or restore later.

12. Debug Minimal Containers with nicolaka/netshoot

The final tip helps when you need to troubleshoot networking from inside a container whose image does not include tools such as ping, curl, or ss. The nicolaka/netshoot image provides a collection of network troubleshooting utilities and can join the target container’s network namespace:

docker run --rm -it --network container:web nicolaka/netshoot

From the resulting shell, curl localhost:80 tests the service from the same network namespace as the web container. The --rm option automatically removes the temporary debugging container when you exit.

If netshoot saved you from rebuilding an image just to add curl, share it with a colleague who still debugs containers that way.

Where to Start on an Existing Docker Host

If you’re hardening an existing production Docker host, start with the changes that address the biggest operational risks: configure log rotation to prevent unbounded container logs and bind internal services to localhost when they do not need external access.

Next, add restart policies, CPU and memory limits, and health checks as you recreate or update containers. Use docker logs, docker stats, and docker inspect when something goes wrong, and keep netshoot available for network troubleshooting.

Docker Compose provides equivalent settings for most of these controls, including restart, resource limits, and healthcheck, making the same practices easier to maintain across multi-container applications.

Conclusion

These 12 tips cover common Docker host problems, from disk usage and unbounded logs to exposed ports, resource limits, and hard-to-debug containers. Most require just a single Docker flag or a few lines in daemon.json.

Which Docker defaults do you change first when setting up a new server? Do you still use the docker group, or have you moved to rootless Docker? Share your daemon.json settings or any Docker errors you’re troubleshooting in the comments below.

If this article helped, with someone on your team.

TecMint Weekly Newsletter

Get the Learn Linux 7 Days Crash Course free when you join 34,000+ Linux professionals reading every Thursday.

Check your email for a magic link to get started.

Something went wrong. Please try again.

Share.
Leave A Reply