Running Docker containers on a VPS with default settings is like leaving your server room unlocked. A misconfigured container can become a foothold for privilege escalation to the host. This guide walks through practical Docker hardening steps — seccomp profiles, AppArmor rules, rootless mode, and kernel-level protections — that significantly reduce your attack surface without breaking your application.
Why Docker Security Matters on a VPS
Docker's default configuration prioritizes compatibility over security. Containers share the host kernel, so a kernel vulnerability or a misconfigured container capability can allow an attacker inside a container to escape to the host system. On a shared VPS or a server running multiple tenant workloads, this is a serious risk.
The good news: most container escapes exploit known misconfigurations, not zero-days. Systematic hardening blocks the majority of attack vectors.
Step 1: Keep the Docker Engine and Kernel Updated
Many container escape vulnerabilities are patched in Docker Engine updates and Linux kernel updates. On Ubuntu/Debian:
apt update && apt upgrade -y
# Check Docker Engine version
docker version
# Check kernel version
uname -rSubscribe to Docker Security Advisories at docs.docker.com/security/ and your Linux distribution's security mailing list to stay informed of critical patches.
Step 2: Never Run Containers as Root
By default, processes inside a container run as root (UID 0). If the container is compromised, the attacker has root privileges within the container namespace. Always set a non-root user in your Dockerfile:
FROM node:20-slim
# Create a non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --omit=dev
USER appuser
CMD ["node", "server.js"]Verify the running user:
docker exec <container_id> whoamiStep 3: Drop Linux Capabilities
Docker grants containers a set of Linux capabilities by default. Most applications don't need them. Drop all capabilities and add back only what's required:
docker run \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
myimageIn Docker Compose:
services:
web:
image: myimage
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICEStep 4: Apply a seccomp Profile
seccomp (Secure Computing Mode) filters which system calls a container can make. Docker applies a default seccomp profile that blocks ~44 dangerous syscalls. You can use the default or create a custom profile for stricter control.
# Use Docker's default seccomp profile explicitly
docker run --security-opt seccomp=/etc/docker/seccomp-default.json myimage
# Or use 'default' keyword
docker run --security-opt seccomp=default myimageDownload the official default profile from the Docker GitHub repository and customize it for your workload by removing syscalls your application doesn't need.
Step 5: Enable AppArmor
AppArmor is a Linux Security Module that restricts what programs can do using per-program profiles. Docker includes a default AppArmor profile (docker-default) that is automatically applied on systems where AppArmor is available.
# Verify AppArmor is active on your host
aa-status
# Check that a container uses the docker-default profile
docker inspect <container_id> | grep AppArmorFor high-security workloads, create a custom AppArmor profile that restricts file system access to only the paths your application needs.
Step 6: Use Read-Only Filesystems
If your container doesn't need to write to its own filesystem, run it read-only. This prevents an attacker from writing malicious files or modifying binaries:
docker run --read-only \
--tmpfs /tmp \
--tmpfs /run \
myimageMount writable directories explicitly with --tmpfs for ephemeral storage or named volumes for persistent data.
Step 7: Limit Resources
Without resource limits, a compromised container can exhaust host CPU and memory, causing a denial-of-service on all other services:
docker run \
--memory 512m \
--memory-swap 512m \
--cpus 0.5 \
--pids-limit 100 \
myimageStep 8: Enable Rootless Docker Mode
Rootless mode runs the Docker daemon itself as a non-root user. This is the most effective mitigation against daemon-level exploits. Set it up with:
# Install rootless prerequisites
apt install -y uidmap dbus-user-session
# Install rootless Docker for your user
dockerd-rootless-setuptool.sh install
# Set environment variables
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sockNote: rootless mode has some limitations with network ports below 1024 and certain storage drivers. Test thoroughly before migrating production workloads.
Step 9: Scan Images for Vulnerabilities
Before running any image in production, scan it for known CVEs:
# Using Docker Scout (built into Docker Desktop and CLI)
docker scout cves myimage:latest
# Using Trivy (open source, excellent coverage)
trivy image myimage:latest
# Using Grype
grype myimage:latestIntegrate image scanning into your CI/CD pipeline so vulnerabilities are caught before images are pushed to your registry.
Docker Hardening Checklist
- Docker Engine and host kernel are up to date
- Containers run as non-root user
- All capabilities dropped, only required ones added back
- seccomp profile applied (default or custom)
- AppArmor profile active
- Read-only filesystem where possible
- Memory, CPU, and PID limits set
- No
--privilegedflag unless absolutely necessary - Docker socket not mounted inside containers
- Images scanned for CVEs before deployment
- Rootless mode enabled (where compatible)
Conclusion
Docker security is not a single setting — it's a defense-in-depth approach. Start with the basics: run as non-root, drop capabilities, and keep everything updated. Then layer on seccomp, AppArmor, and rootless mode as your setup matures. For hardened base images that reduce your CVE surface further, see the guide on Docker distroless and hardened images.