Skip to content
Back to Blog
Linux & Server11 min read

Docker Container Hardening on a VPS: seccomp, AppArmor & Rootless Mode

Default Docker settings leave your VPS exposed. This guide covers practical container hardening: dropping Linux capabilities, applying seccomp profiles, enabling AppArmor, running rootless Docker, and a checklist to verify your setup before going to production.

Written by Abdul AbrorTechnical Hosting Support Engineer
On this page

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 -r

Subscribe 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> whoami

Step 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 \
  myimage

In Docker Compose:

services:
  web:
    image: myimage
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

Step 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 myimage

Download 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 AppArmor

For 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 \
  myimage

Mount 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 \
  myimage

Step 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.sock

Note: 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:latest

Integrate 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 --privileged flag 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.

FAQ

Does hardening affect container performance?

Minimal impact in practice. seccomp filtering adds a small syscall overhead that is negligible for most workloads. Resource limits only affect containers that would otherwise exceed them. The biggest performance consideration is rootless mode's use of slirp4netns for networking, which has lower throughput than the standard bridge network — benchmark your specific workload.

Should I disable Docker if I don't need it?

Yes. If Docker is installed but not actively used on a VPS, disable the daemon:systemctl disable docker. An inactive Docker socket is still an attack surface if the socket permissions are misconfigured.

What is the Docker socket and why is it dangerous to mount it?

The Docker socket (/var/run/docker.sock) is the Unix socket that the Docker daemon listens on. A container with access to this socket can control the Docker daemon — creating privileged containers, mounting host directories, or escaping to the host. Never mount the Docker socket into untrusted containers. Use a proxy like docker-socket-proxy if you need limited access.