Docker Tutorial · Production Practices · Lesson 22 of 26
Non-Root Users and Read-Only Containers
Reduce what a compromised container can do: run as a non-root user, use a read-only filesystem and drop capabilities.
Defaults to change
By default the process in many images runs as root inside the container. Container root is restricted, but a container escape or a mounted host path makes it far more dangerous. Harden containers:
USER 10001in the Dockerfile (create the user in the image), or--userat run time.--read-onlyroot filesystem, with--tmpfs /tmpfor scratch space.--cap-drop ALL, adding back only what's needed (for exampleNET_BIND_SERVICE).--security-opt no-new-privilegesso setuid binaries can't escalate.- Never mount the Docker socket (
/var/run/docker.sock) into application containers: it is root on the host.
trap 'docker image rm -f rc-secure:1 > /dev/null 2>&1' EXIT
cat > Dockerfile <<'EOF'
FROM alpine:3.24
RUN adduser -D -u 10001 app
USER 10001
CMD ["sh", "-c", "id; touch /should-fail 2>/dev/null || echo 'cannot write to /'; echo ok > /tmp/scratch && echo 'can write to /tmp'"]
EOF
docker build -q -t rc-secure:1 . > /dev/null
docker run --rm --read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges rc-secure:1
echo "capabilities of the process: $(docker run --rm --cap-drop ALL rc-secure:1 sh -c 'grep CapEff /proc/self/status' | tr -s '\t' ' ')"
Output
uid=10001(app) gid=10001(app) groups=10001(app) cannot write to / can write to /tmp capabilities of the process: CapEff: 0000000000000000
Run this on your computer — see “Get Started” for setup.
Test yourself
Why is mounting /var/run/docker.sock into an app container dangerous?
It slows Docker downAccess to the Docker API is effectively root access to the hostIt breaks networkingIt's only a logging socket
Show answer
Answer: Access to the Docker API is effectively root access to the host
Anyone with the socket can start a privileged container that mounts the host filesystem.