Four Docker Mistakes That Create Security Holes
Containers make it easy to run software quickly. They also make it easy to expose things you didn't mean to. Here are four common mistakes, and how I avoid them in my own lab.

Docker changed how I run software. In my home lab, an Ubuntu server runs Pi-hole for DNS filtering and WireGuard for VPN access, each in its own container. Deploying a new service takes minutes, rebuilding a broken one is simple and every configuration is written down in a Compose file.
That convenience has a catch. Containers make it very easy to run software you haven't examined, with settings you didn't fully think through. A few common mistakes can turn a tidy container setup into a real security risk.
This article covers four of the most common Docker mistakes I've seen and read about, why they matter and the habits I use to avoid them. It's aimed at anyone running containers, whether in a home lab or at work.
A quick reminder: containers aren't virtual machines
Before the mistakes, one important concept. A virtual machine runs its own complete operating system, including its own kernel. A container shares the host's kernel and is isolated using features of that kernel.
That makes containers lightweight and fast, but it also means the isolation between a container and its host is thinner than between a VM and its host. Containers are a useful security boundary, but not an impenetrable one. Many of the mistakes below come from treating containers as if they were fully separate machines.
Mistake 1: Exposing ports you didn't mean to
What happens
When you publish a port in Docker, for example with -p 8080:80 or a ports: entry in a Compose file, Docker makes that service reachable on the host's network interfaces. By default, that means all interfaces, not just localhost.
There's a further surprise for many Linux users: Docker manages its own firewall rules to make published ports work. On many setups, those rules take effect before host firewall tools such as UFW, so a port you thought you'd blocked may still be reachable.
Admin dashboards, databases and test services meant only for local use can end up exposed to the whole network, or to the internet if the host is directly reachable.
How to avoid it
- Publish only what must be reachable. If a service only needs to talk to other containers, put them on a shared Docker network and don't publish its port at all.
- Bind to localhost when appropriate, for example
127.0.0.1:8080:80, so the service is only reachable from the host itself. - Check what's actually listening with
sudo ss -tulpnon the host, and scan the host from another machine to see what's reachable from outside. - Understand how Docker interacts with your firewall, and apply restrictions where Docker will respect them, such as the dedicated rule chain Docker provides for user rules, or a network firewall in front of the host.
In my lab, only the WireGuard port is forwarded from the router. Everything else, including Pi-hole's web interface, is reachable only on the home network or over the VPN.
Mistake 2: Running containers with too much privilege
What happens
Many images run their processes as root inside the container by default. Some guides also suggest running containers with --privileged, which gives the container broad access to the host's devices and kernel features, just to make something work.
Other risky shortcuts include mounting the Docker socket (/var/run/docker.sock) into a container, which effectively gives that container control over Docker and therefore the host, and mounting sensitive host directories with write access.
If an attacker compromises a service in an over-privileged container, they may be able to escape to the host.
How to avoid it
- Never use
--privilegedunless you fully understand why it's needed. Usually a specific capability is enough. Grant only that with--cap-add, and consider dropping others with--cap-drop. - Run as a non-root user where the image supports it, using the
user:setting or aUSERinstruction in your own Dockerfiles. - Avoid mounting the Docker socket unless absolutely necessary, and treat any container that has it as having full host access.
- Mount host directories read-only (
:ro) whenever the container only needs to read them. - Apply least privilege exactly as you would for user accounts: give each container only what it needs.
Mistake 3: Trusting images blindly
What happens
It's easy to pull an image from a public registry because a tutorial mentioned it, without checking who published it or what's inside. Images can be outdated, contain known vulnerabilities, be abandoned by their maintainers or, occasionally, be deliberately malicious.
Using the latest tag adds another problem. You don't know exactly which version you're running, and an update can change behaviour without warning.
How to avoid it
- Prefer official images or images from the software's own publisher. Check the publisher, documentation, update history and source repository.
- Pin versions with specific tags, or even image digests for critical services, so you know exactly what's running.
- Scan images for known vulnerabilities. Several free scanners can check images for outdated packages and known issues.
- Use minimal base images for your own builds. Less software means fewer vulnerabilities.
- Rebuild and update regularly. Even a pinned image needs updating when security fixes are released. Pinning is about control, not standing still.
Mistake 4: Leaking secrets
What happens
Containers need configuration, and configuration often includes passwords, API keys and tokens. Common mistakes include writing secrets directly into Dockerfiles, which bakes them into image layers where they can be extracted later, committing .env files or Compose files containing secrets to public Git repositories, and passing secrets as environment variables that appear in logs or inspection output.
Leaked credentials are one of the most common causes of cloud and application breaches.
How to avoid it
- Never put secrets in Dockerfiles or images.
- Keep
.envfiles out of version control with a.gitignoreentry, and commit an example file with placeholder values instead. - Use Docker secrets or an external secrets manager where possible, so secrets are mounted as files with restricted access rather than exposed as environment variables.
- Restrict file permissions on configuration files on the host.
- Rotate any secret that might have leaked, immediately, even if you're not sure it was used.
Before and after: a safer Compose file
Here's a simplified example of a web dashboard defined in Docker Compose, first with common mistakes and then with safer settings.
Before:
services:
dashboard:
image: example/dashboard:latest
privileged: true
ports:
- "8080:80"
environment:
- ADMIN_PASSWORD=Summer2026!
volumes:
- /:/host
This runs an unpinned image with full privileges, exposes the port on every network interface, stores a password in plain text and mounts the entire host file system with write access.
After:
services:
dashboard:
image: example/dashboard:2.4.1
user: "1000:1000"
cap_drop: [ALL]
read_only: true
ports:
- "127.0.0.1:8080:80"
secrets:
- admin_password
volumes:
- ./config:/app/config:ro
secrets:
admin_password:
file: ./secrets/admin_password.txt
The image is pinned, the container runs as a regular user with no extra capabilities and a read-only file system, the port is reachable only from the host, the password comes from a protected secrets file and only one configuration folder is mounted, read-only. Not every image supports all of these settings, so test each change, but the direction is always the same: less access, less exposure.
Bonus: forgetting about updates and logs
Two more habits round out a secure container setup.
Keep the host updated. Because containers share the host's kernel, host updates protect every container. On my Ubuntu server, host security updates are applied regularly, and I restart after kernel updates.
Watch the logs. docker logs and centralized logging help you notice errors, crashes and unusual activity. In my lab, reviewing Pi-hole and WireGuard logs is part of my regular routine.
My container checklist
Before I consider a new container "done" in my lab, I check:
- Only necessary ports are published, bound to the right interfaces.
- The container doesn't run privileged, and capabilities are limited.
- It runs as a non-root user where possible.
- Host mounts are minimal and read-only where possible.
- The image comes from a trusted publisher and is pinned to a version.
- No secrets are stored in the image or in version control.
- The Compose file and any notes are documented.
- I've confirmed from another machine what's actually reachable.
The takeaway
Docker doesn't make software insecure. But it removes friction, and friction is sometimes what makes people stop and think. Exposed ports, excessive privilege, untrusted images and leaked secrets are all easy to avoid with a few deliberate habits. Treat containers like any other system: least privilege, minimal exposure, known software and protected secrets.
What's the most surprising thing you've found exposed by a container setup?
Yuvin Walker is a cybersecurity and network specialist in Toronto. He has supported a 1,200-user manufacturing site and an internet service provider’s customers, holds the Certified Ethical Hacker (CEH) credential and a BEng in Computer Network and Security, and completed postgraduate programs in Cybersecurity and Threat Management and IT Project Management at Seneca Polytechnic.
Connect: LinkedIn · yuvinwalker@gmail.com
Docker makes deploying software fast. It also makes it easy to open security holes without noticing. Here are four common container mistakes, why they matter and how I avoid them in my home lab. #Docker #Containers #DevSecOps #Linux #HomeLab
