Containers are convenient because they package applications and make recovery repeatable. They are not magical security bubbles.
The first containment improvement is network scope. An application should join only the networks it needs. A public web application normally needs to reach its database or cache, but it does not need to see every other application in the homelab. The tunnel connector also should not become a bridge to unrelated internal services.
The second improvement is privilege reduction:
- Do not mount the Docker socket unless there is a very strong reason.
- Avoid privileged containers.
- Drop unnecessary Linux capabilities.
- Use a non-root user where the application supports it.
- Add
no-new-privileges. - Make the filesystem read-only where practical.
- Mount only the persistent directories the application actually needs.
- Avoid unnecessary host ports when a private Docker network is enough.
These controls do not make a vulnerable application safe. They reduce what an attacker can do after an application-level failure. That distinction matters: patching prevents known exploitation, while containment limits blast radius when prevention fails.
The practical approach is incremental. First inspect the compose file and container mounts. Then remove one unnecessary permission at a time and test the application. Databases, upload directories, caches, and plugin systems often need different write permissions, so a blanket read-only setting may require careful exceptions.
The target is not an impressive compose file. It is a service that still works while having fewer paths to the host, fewer neighboring services to discover, and fewer privileges to abuse.
# Inspect mounts, networks, user, and privilege settings
docker inspect example-app --format '{{json .Mounts}}' | jq
docker inspect example-app --format '{{json .NetworkSettings.Networks}}' | jq
docker inspect example-app --format 'user={{.Config.User}} privileged={{.HostConfig.Privileged}}'
docker port example-app
# Validate a compose file before recreating services
docker compose config --quiet
These are read-only checks except for the compose validation; review the rendered configuration before applying it.
Comments