Container SecurityApril 7, 2023Docker Hardening Best Practices

Docker has gained immense popularity in recent times due to its containerization capabilities. However, as with any widely used platform, there is an increased risk of security threats. Therefore, taking the necessary measures to secure your Docker environment is imperative. In this regard, here are some Docker hardening best practices you should consider implementing.

Run Docker as an Unprivileged User: Enabling Rootless Mode

Running Docker in root-less mode has several advantages. It helps to improve the security of your system by limiting the privileges of the Docker daemon. It also makes managing Docker containers and images easier, as you do not need to run Docker commands as the root user. Overall, running Docker in root-less mode is a great way to ensure your system is secure and easy to manage.

Here is an example of running Docker in root-less mode:

1) Install Docker and its dependencies:

sudo apt-get update
sudo apt-get install -y docker.io

2) Create a new group called “docker”:

sudo groupadd docker

3) Add your user account to the “docker” group:

sudo usermod -aG docker $USER

4) Log out and log back in for the changes to take effect.

5) Test that Docker is running in root-less mode by running a container:

docker run hello-world

This should output “Hello from Docker!” if Docker runs correctly in root-less mode. You no longer need to run the “docker” command with sudo, as your user account now has the necessary permissions to interact with the Docker daemon.

Setting the container’s user

Setting the container’s user is important to secure your Docker environment. By default, Docker containers run as the root user, which can pose a security risk. To set the container’s user, you can use the “USER” directive in your Dockerfile.

There are several ways to set the user for a container in Docker, including:

1) Using the “-u” flag when running containers: This sets the user for the container to a specified user or UID. For example:

docker run -u 1001 nginx

This sets the user for the nginx container to user ID 1001.

2) When building the Docker image, the “USER” Dockerfile directive sets the user for the container. For example:

FROM nginx
RUN useradd -r -u 1001 myuser
USER myuser

This creates a new user, “myuser” with UID 1001 and sets it as the user for the container.

3) Enabling user namespace support in the Docker daemon allows you to map the user ID used inside the container to a different user ID outside the container. For example, you can map the user ID inside the container to a non-root user ID outside the container for improved security.

It is important to note that setting the user for a container as an unprivileged user can help prevent privilege escalation attacks. However, it does not guarantee security if there is a local privilege escalation within the container. Therefore, it is important also to implement additional security measures such as restricting container capabilities and monitoring container activity.

Do not expose the Docker daemon socket

Exposing the Docker daemon socket can be a serious security risk, as it can allow attackers to gain complete control over your Docker environment. Therefore, ensuring that the Docker daemon socket is not exposed, even to the containers, is important.

To prevent the exposure of the Docker daemon socket, you should take the following steps:

1) First, ensure that the Docker daemon is configured to use a Unix socket instead of a TCP socket. This can be done by modifying the Docker daemon configuration file (/etc/docker/daemon.json) to include the following lines:

{
  "hosts": ["unix:///var/run/docker.sock"]
}

This will configure the Docker daemon to use the Unix socket at /var/run/docker.sock.

2) Next, ensure the Docker daemon socket is not mounted into any containers. This can be done by inspecting the “docker run” command and ensuring that the “-v /var/run/docker.sock:/var/run/docker.sock” flag is not present.

Example:

volumes:
- "/var/run/docker.sock:/var/run/docker.sock"

3) Finally, ensure that your Docker environment is configured to use secure networking protocols, such as TLS, to prevent attackers from intercepting and manipulating network traffic.

You can verify if a container is already running with a particular configuration by using the following:

docker inspect --format=’{{.HostConfig.Binds}}’ [container id]

Following these best practices can help ensure your Docker environment is secure and protected against common threats.

Add –no-new-privileges Flag

The –no-new-privileges flag is an important security feature that can be used to prevent privilege escalation attacks. When this flag is set, it prevents the Docker container from gaining additional privileges beyond what was granted at the initialization time.

There are several ways to add the –no-new-privileges flag to a Docker container to prevent processes in the container from gaining additional privileges. Here are a few methods:

1) When running the container:

docker run --no-new-privileges nginx

2) In the Dockerfile:

FROM nginx
CMD ["nginx", "-g", "daemon off;"]
--no-new-privileges

3) In the Docker Compose file:

version: "3"
services:
  nginx:
    image: nginx
    command: ["nginx", "-g", "daemon off;"]
    no_new_privileges: true

Adding the –no-new-privileges flag is a simple but effective way to prevent privilege escalation attacks and improve the security of your Docker environment.

Set Filesystem and Volumes to Read-only

Setting the filesystem and volumes to read-only can help to prevent unauthorized modifications to your Docker environment. This is especially important if you run Docker in a production environment where security is a top priority.

To set the filesystem and volumes to read-only, you can follow these steps:

1) First, create a Docker container with a read-only filesystem by adding the “–read-only” flag to the “docker run” command. For example:

docker run --read-only -it alpine /bin/sh

This command will create a new container based on the Alpine Linux image with a read-only filesystem.

2) Next, set the volumes to read-only by adding the “–read-only” flag to the “docker run” command. For example:

docker run --read-only -v /data:/data:ro -it alpine /bin/sh

This command will create a new container based on the Alpine Linux image with a read-only filesystem and set the “/data” volume to read-only.

3) Finally, ensure your application can function correctly with a read-only filesystem and volumes.

Setting the filesystem and volumes to read-only is a simple but effective way to improve the security of your Docker environment and prevent unauthorized modifications to your containers and data.

Limit Capabilities

To prevent unauthorized access to your Docker environment, it is important to limit capabilities. Capabilities are special privileges that process running on the host system are granted, and by default, all Docker containers have access to all capabilities.

To limit capabilities, follow these steps:

1) Identify the capabilities required by your container. Run the container with the “–cap-add=ALL” flag and inspect the capabilities listed in the output of the “capsh –print” command.

2) Modify your Dockerfile to include only the required capabilities. For example, if your container requires only the “NET_ADMIN” and “SYS_TIME” capabilities, add the following lines to your Dockerfile:

FROM ubuntu
RUN apt-get update && apt-get install -y nginx
USER nginx
CMD ["nginx"]
--cap-add=NET_ADMIN
--cap-add=SYS_TIME
--security-opt=no-new-privileges

3) Build and run your Docker image as usual. Your container will now have access only to the specified capabilities, which can help prevent unauthorized access and improve the security of your Docker environment.

Limiting capabilities is a powerful but complex security measure, and it is important to thoroughly test your container to ensure that it has all the capabilities it needs to function correctly.

Add HEALTHCHECK to the Docker Image

Adding a HEALTHCHECK to your Docker image is important in ensuring your application runs correctly. A HEALTHCHECK is a command that Docker runs to check the status of your application. If the command returns a non-zero exit code, Docker will consider the container unhealthy.

To add a HEALTHCHECK to your Docker image, you can follow these steps:

1) First, choose a command that will check the health of your application. This can be a simple HTTP GET request or a more complex command that checks the status of your application’s components.

2) Next, add the HEALTHCHECK directive to your Dockerfile. For example, if you want to use an HTTP GET request to check the health of your application, you can add the following line to your Dockerfile:

HEALTHCHECK CMD curl --fail <http://localhost:8080> || exit 1

This command will make a GET request to http://localhost:8080 and return a non-zero exit code if the request fails.

3) Finally, build and run your Docker image. Docker will automatically run the HEALTHCHECK command and update the container’s status accordingly.

Adding a HEALTHCHECK to your Docker image is a simple but effective way to ensure your application runs correctly and detects any issues before they become critical.

Use Linux Security Modules (seccomp, AppArmor, or SELinux)

Linux Security Modules (LSMs) such as seccomp, AppArmor, or SELinux provide an additional layer of security for Docker environments by restricting the system calls that a container can make. This helps to prevent attackers from executing malicious code or compromising the host system.

To use an LSM with Docker, you can follow these steps:

1) Choose an LSM compatible with your Linux distribution and Docker version first. For example, Ubuntu supports AppArmor by default, while seccomp and SELinux may require additional configuration.

2) Next, configure the LSM to restrict the system calls that Docker containers can make. This can be done by creating a profile or policy that specifies the allowed and disallowed system calls. For example, an AppArmor profile may include the following lines:

  /usr/bin/dockerd flags=(complain) {
    # Allow read-only access to the host filesystem
    / r,
    /sys/ r,
    /sys/fs/cgroup/ r,
    # Allow network access only to specified ports
    network tcp,
    network udp,
    network inet dgram,
    network inet6 dgram,
    network inet stream,
    network inet6 stream,
    network raw,
    network unix,
    network packet,
    network netlink,
    # Deny all other system calls
    deny /**,
  }

Note: AppArmor controls access to system resources by enforcing rules set in a profile. Enforcing mode provides strong security, while complain mode logs violations for review. 

3) Finally, apply the profile or policy to your Docker containers. This can be done by adding the “–security-opt” flag to the “docker run” command and specifying the name of the profile or policy. For example:

docker run --security-opt apparmor=docker-profile -it ubuntu /bin/bash

Using an LSM with Docker can help to prevent attackers from exploiting vulnerabilities in your containers and compromising the host system.

This blog has covered some of the most essential Docker Hardening Best Practices.

By partnering with Redfox Security, you’ll get the best security and technical skills to execute a practical and thorough penetration test. Our offensive security experts have years of experience assisting organizations in protecting their digital assets through Penetration Testing Services. To schedule a call with one of our technical specialists, call 1-800-917-0850 now.

Redfox Security is a diverse network of expert security consultants with a global mindset and a collaborative culture. We proudly deliver robust security solutions with data-driven, research-based, and manual testing methodologies.

“Join us on our journey of growth and development by signing up for our comprehensive courses.”

Gaurav Patil

by Gaurav Patil

Associate Security Consultant | Redfox Security