DEV Notes

Docker security notes

I had a small collection of links related to Docker security best practices and other security tips but unfortunately some of them disappeared forever. These notes are based on the Docker Security Cheat Sheet manual which rules were included in many other articles and I decided to archive some of them here as well.

Do not expose the Docker daemon socket (even to the containers)

Docker socket /var/run/docker.sock is the UNIX socket that Docker is listening to. This is the primary entry point for the Docker API. The owner of this socket is root. Giving someone access to it is equivalent to giving unrestricted root access to your host.

Do not enable tcp Docker daemon socket

If you are running docker daemon with -H tcp://0.0.0.0:XXX you are exposing un-encrypted and unauthenticated direct access to the Docker daemon.

Do not expose /var/run/docker.sock to other containers

By using volumes -v /var/run/docker.sock://var/run/docker.sock the socket can be exposed to other containers.

Set a user

Configuring the container to use an unprivileged user is the best way to prevent privilege escalation attacks. This can be accomplished in different ways as follows:

During runtime using -u option of docker run command

docker run -u 4000 alpine

During build time. Simple add user in Dockerfile and use it.

FROM alpine
RUN groupadd -r myuser && useradd -r -g myuser myuser

<ROOT USER ACTIONS>

USER myuser

Limit capabilities

Grant only specific capabilities needed by a container. Docker, by default, runs with only a subset of capabilities. You can change it and drop some capabilities (using --cap-drop) to harden your docker containers, or add some capabilities (using --cap-add) if needed. Remember not to run containers with the --privileged flag - this will add ALL Linux kernel capabilities to the container.
The most secure setup is to drop all capabilities

docker run --cap-drop all --cap-add CHOWN alpine

Add –no-new-privileges flag

Always run your docker images with --security-opt=no-new-privileges in order to prevent escalate privileges using setuid or setgid binaries.

Disable inter-container communication

By default, inter-container communication (ICC) is enabled - it means that all containers can talk with each other (using docker0 bridged network). This can be disabled by running docker daemon with --icc=false flag. If ICC is disabled it is required to tell which containers can communicate using --link=CONTAINER_NAME_or_ID:ALIAS option.

Limit resources

The best way to avoid DoS attacks is by limiting resources. You can limit memory, CPU, maximum number of restarts --restart=on-failure:<number_of_restarts>, maximum number of file descriptors --ulimit nofile=<number> and maximum number of processes --ulimit nproc=<number>.

Set filesystem and volumes to read-only

Run containers with a read-only filesystem using --read-only flag.

docker run --read-only alpine sh

If an application inside a container has to save something temporarily, combine --read-only flag with --tmpfs

docker run --read-only --tmpfs /tmp alpine sh -c 'echo "whatever" > /tmp/file'

In addition volumes also can be mounted as read-only

docker run -v volume-name:/path/in/container:ro alpine

Mount secrets

--mount=type=secret is a Docker build feature that allows you to securely pass sensitive data (like API keys, credentials or private tokens) into Dockerfile without embedding them in the image layers or build history.

  • Temporary access only - The secret is available only during the specific build step where it’s mounted, then discarded.
  • Not stored in the image - The secret never gets baked into the Docker image, so it won’t be exposed if someone inspects the image.
  • BuildKit required - This feature requires Docker BuildKit to be enabled (DOCKER_BUILDKIT=1).
RUN --mount=type=secret,id=my_secret cat /run/secrets/my_secret

Pass the secret to docker build

DOCKER_BUILDKIT=1 docker build --secret id=my_secret,src=/path/to/secret/file .

Or inline

DOCKER_BUILDKIT=1 docker build --secret id=my_secret,src=/dev/stdin . <<< "my-secret-value"

Access it in the Dockerfile

FROM python:3.12
RUN --mount=type=secret,id=my_api_key \
    API_KEY=$(cat /run/secrets/my_api_key) && \
    pip install some-package --with-api-key=$API_KEY

Mount SSH

--mount=type=ssh is a Docker build feature that allows you to securely use SSH keys during the build process without embedding them in the image.

  • SSH agent forwarding - It mounts your SSH key(s) from the host into the container during the build, making them available to SSH commands.
  • Temporary access - The SSH key is only available during that specific RUN step, then removed.
  • Not stored in the image - Your SSH keys are never baked into the Docker image layers.
  • BuildKit required - Requires Docker BuildKit (DOCKER_BUILDKIT=1).
RUN --mount=type=ssh <command that uses SSH>

SSH forwarding

DOCKER_BUILDKIT=1 docker build --ssh default=$SSH_AUTH_SOCK .

Or explicitly specify the key

DOCKER_BUILDKIT=1 docker build --ssh default=~/.ssh/id_rsa .

Use in Dockerfile

FROM ubuntu:22.04

RUN apt-get update && apt-get install -y git openssh-client

RUN --mount=type=ssh \
    git clone git@github.com:user/private-repo.git /app 

RUN --mount=type=ssh \
    pip install git+ssh://git@github.com/user/private-repo.git