Ante Miličević
January 12, 2024

Docker Security: Simple and Easy Guide

Explore the world of Docker security with our straightforward guide. Learn essential practices to ensure your Docker containers are protected.

Docker is one of the best choices when it comes to containerization technology. When utilized efficiently, it has the potential to enhance security measures, particularly when compared to running applications directly on the host. However, certain misconfigurations may compromise security levels or even introduce new vulnerabilities.

The goal of this cheat sheet is to offer an accessible compilation of standard security blunders and best practices, equipping you with the knowledge to fortify the security of your Docker containers.

Rules

1. Keep Host and Docker up to Date

In order to shield against recognized container escape vulnerabilities, which often culminate in an elevation to root/administrator privileges, it's vital to patch the Docker Engine and Docker Machine.

Moreover, containers (as opposed to virtual machines) share the kernel with the host, implying that kernel exploits executed inside the container will have a direct impact on the host kernel. For instance, a kernel privilege elevation exploit (such as Dirty COW) executed within a well-protected container will lead to root access in a host.

2. Do Not Expose the Docker Daemon Socket

The Docker socket /var/run/docker.sock acts as the UNIX socket that Docker is listening to. Serving as the main access point for the Docker API, it is owned by root. Therefore, granting access to someone is akin to providing them with unrestrained root access to your host.

Do not activate the tcp Docker daemon socket. If the Docker daemon is being operated with -H tcp://0.0.0.0:XXX or similar, you risk providing unencrypted and unauthenticated direct access to the Docker daemon. Should the host be connected to the internet, anyone on the public internet could potentially use the Docker daemon on your computer. If absolutely necessary, ensure it is secured — consult the Docker official documentation for guidance on this.

Avoid exposing /var/run/docker.sock to other containers. Should your Docker image be run with -v /var/run/docker.sock://var/run/docker.sock or something similar, a change is advised. Note that merely mounting the socket as read-only does not solve the problem but makes exploitation slightly more challenging. An equivalent in the docker-compose file might look like this:

<pre class="codeWrap"><code>volumes:
- "/var/run/docker.sock:/var/run/docker.sock"</code></pre>

3. Set a User

The most effective method for avoiding privilege escalation attacks is to configure the container to utilize an unprivileged user. This can be accomplished in three distinct ways, as outlined below:

During runtime, use the -u option of the docker run command, for example:

<pre class="codeWrap"><code>docker run -u 4000 alpine</code></pre>

At build time, simply add a user in the Dockerfile and utilize it, as shown:

<pre class="codeWrap"><code>FROM alpine
RUN groupadd -r myuser && useradd -r -g myuser myuser
<HERE DO WHAT YOU HAVE TO DO AS A ROOT USER LIKE INSTALLING PACKAGES ETC.>USER myuser</code></pre>

Enable user namespace support (--userns-remap=default) in the Docker daemon. Additional information on this subject can be found in the Docker official documentation.

In Kubernetes, the configuration can be done with the Security Context feature, using the runAsNonRoot field, for example:

<pre class="codeWrap"><code>kind: ...
apiVersion: ...
metadata:
  name: ...
spec:
  ...
  containers:
  - name: ...
    image: ....
    securityContext:
          ...
          runAsNonRoot: true
          ...
</code></pre>

As a Kubernetes cluster administrator, this can be accomplished using Pod Security Policies.

3. Limit Capabilities

Linux kernel capabilities comprise a collection of privileges that can be leveraged by privileged entities. By default, Docker operates with a limited subset of capabilities. However, these capabilities can be amended - you can augment your Docker containers' security by dropping some capabilities (using --cap-drop), or adding a few capabilities (via --cap-add) as needed. Importantly, avoid running containers with the --privileged flag, as this will grant ALL Linux kernel capabilities to the container.

The most secure arrangement is to drop all capabilities with --cap-drop all and then selectively add only the necessary ones, as shown below:

<pre class="codeWrap"><code>docker run --cap-drop all --cap-add CHOWN alpine</code></pre>

Remember, do not run containers with the --privileged flag!

In Kubernetes, this can be configured under Security Context using the capabilities field, illustrated below:

As a Kubernetes cluster administrator, this configuration can be performed via Pod Security Policies.

<pre class="codeWrap"><code>kind: ...
apiVersion: ...
metadata:
  name: ...
spec:
  ...
  containers:
  - name: ...
    image: ....
    securityContext:

          ...
          capabilities:
            drop:
              - all
            add:
              - CHOWN
          ...
</code></pre>

4. Add–No-New-Privileges Flag

Consistently execute your Docker images with --security-opt=no-new-privileges to inhibit privilege escalation through the use of setuid or setgid binaries. In Kubernetes, this can be configured within the Security Context using the allowPrivilegeEscalation field, as exemplified below:

<pre class="codeWrap"><code>kind: ...
apiVersion: ...
metadata:
  name: ...
spec:
  ...
  containers:
  - name: ...
    image: ....
    securityContext:
          ...
          allowPrivilegeEscalation: false
          ...
</code></pre>

As a Kubernetes cluster administrator, consult the Kubernetes documentation to configure this setting through the use of Pod Security Policies.

5. Disable Inter-Container Communication (--icc=false)

By default, inter-container communication (icc) is enabled, meaning all containers are capable of interacting with each other (via the docker0 bridged network). However, a Docker daemon can disable this by running with the --icc=false flag. If icc is disabled (icc=false), it's necessary to define which containers can communicate using the --link=CONTAINER_NAME_or_ID:ALIAS option.

Consider checking the Docker documentation on container communication for more information. In Kubernetes, Network Policies are utilized for this purpose.

6. Use Linux Security Module (seccomp, AppArmor, or SELinux)

First and foremost, do not deactivate the default security profile! Consider employing security profiles such as seccomp or AppArmor. Guidance on implementing this within Kubernetes is available in the Security Context documentation and the Kubernetes API documentation.

7. Limit resources ( CPU, File Descriptors, Restarts, Memory)

The best method to protect against DoS attacks is by constraining resources. You can set limits on memory, CPU, maximum restarts (--restart=on-failure:<number_of_restarts>), maximum file descriptors (--ulimit nofile=<number>), and maximum processes (--ulimit nproc=<number>). Refer to the relevant documentation for further details about ulimits.

In a Kubernetes environment, this can also be accomplished: Assign Memory Resources to Containers and Pods, Assign CPU Resources to Containers and Pods, and Assign Extended Resources to a Container.

8. Filesystem and volumes Should Be Set to Read-Only

Execute containers with a read-only filesystem using the --read-only flag. For example:

<pre class="codeWrap"><code>docker run --read-only alpine sh -c 'echo "whatever" > /tmp'</code></pre>

If an application within a container needs to temporarily store something, combine the --read-only flag with --tmpfs, as follows:

<pre class="codeWrap"><code>docker run --read-only --tmpfs /tmp alpine sh -c 'echo "whatever" > /tmp/file'</code></pre>

The equivalent configuration in a docker-compose file is:

<pre class="codeWrap"><code>version: "3"
services:
  alpine:
    image: alpine
    read_only: true
</code></pre>

In Kubernetes, the equivalent configuration within the Security Context is:

<pre class="codeWrap"><code>kind: ...
apiVersion: ...
metadata:
  name: ...
spec:
  ...
  containers:
  - name: ...
    image: ....
    securityContext:
          ...
          readOnlyRootFilesystem: true
          ...
</code></pre>

Furthermore, if a volume is mounted solely for reading, mount it as read-only. This can be done by appending: ro to the -v, like so:

<pre class="codeWrap"><code>docker run -v volume-name:/path/in/container:ro alpine</code></pre>

Alternatively, use the --mount option:

<pre class="codeWrap"><code>docker run --mount source=volume-name,destination=/path/in/container,readonly alpine</code></pre>

9. Use Static Analysis Tools

To detect containers with known vulnerabilities, use static analysis tools to scan images.

Free:

Commercial

To detect secrets in images:

To detect misconfigurations in Kubernetes:

To detect misconfigurations in Docker:

10. Make Sure to Set the Logging Level to at Least INFO

By default, the Docker daemon's base log level is configured as 'info'. If this is not the case, ensure the Docker daemon log level is set to 'info'. The underlying reason is that setting a proper log level enables the Docker daemon to log events for future examination. A base log level of 'info' and above would document all logs excluding debug logs. Unless necessary, you should avoid running the Docker daemon at the 'debug' log level.

To configure the log level in docker-compose:

<pre class="codeWrap"><code>docker-compose --log-level info up</code></pre>

11. Lint the Dockerfile at Build Time

Numerous issues can be averted by adhering to certain best practices while composing the Dockerfile. Incorporating a security linter into the build pipeline can greatly assist in reducing potential issues.

Some crucial points to check include:

  • Guarantee a USER directive is specified
  • Make sure the base image version is pinned
  • Confirm the OS package versions are pinned
  • Prefer the use of COPY over ADD
  • Steer clear of curl bashing in RUN directives

12. Run Docker in Root-Less Mode

Rootless mode ensures that both the Docker daemon and containers function as non-privileged users, which signifies that even if an intruder escapes the container, they won't obtain root privileges on the host, thereby greatly reducing the attack surface.

As of Docker Engine v20.10, rootless mode has graduated from the experimental stage and should be considered for additional security, as long as the noted limitations are not a hindrance.

Rootless mode facilitates running the Docker daemon and containers as a non-root user to lessen potential vulnerabilities in the daemon and the container runtime. Rootless mode doesn't require root privileges even during the Docker daemon's installation, provided that the prerequisites are met. Rootless mode was initially presented as an experimental feature in Docker Engine v19.03, but achieved graduation from the experimental stage in Docker Engine v20.10.

For more information about rootless mode, its limitations, and installation and usage guidance, please consult the Docker documentation page.

Facing Challenges in Cloud, DevOps, or Security?
Let’s tackle them together!

get free consultation sessions

In case you prefer e-mail first:

Thank you! Your message has been received!
We will contact you shortly.
Oops! Something went wrong while submitting the form.
By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information. If you wish to disable storing cookies, click here.