Running Docker Containers Safely: The Non-Root Challenge

Published on

Running Docker Containers Safely: The Non-Root Challenge

In the realm of DevOps, the shift towards containerization has transformed how applications are developed, tested, and deployed. Docker, as one of the pioneers in container technology, has streamlined this process. However, one crucial aspect often overlooked is the necessity of running Docker containers with minimal privileges. In this post, we will explore the challenges associated with running Docker containers as a non-root user and how to implement best practices to enhance security.

Why Run Containers as Non-Root?

Running containers with root privileges can expose your system to various security vulnerabilities:

  1. Privilege Escalation: If a container is compromised, attackers could gain root access to the host system.
  2. Isolation Failures: Containers share the host's kernel. A compromised root container can potentially break this isolation, affecting other containers and services.
  3. Data Breaches: Sensitive information hosted within your containers can be at risk if attackers exploit root access.

By running Docker containers as non-root users, you minimize these risks. Consequently, your applications become more secure, aligning with industry best practices.

Setting Up a Non-Root Docker Container

To run containers as non-root users, follow these guidelines:

  1. Create a Dockerfile with a Non-Root User
  2. Build and Run the Container
  3. Set Proper File Permissions

Step 1: Create a Dockerfile with a Non-Root User

Starting with a Dockerfile is essential. Below is a basic example that creates a non-root user.

# Use an official base image
FROM ubuntu:20.04

# Create a non-root user 'appuser'
RUN useradd -ms /bin/bash appuser

# Set the user to 'appuser'
USER appuser

# Set the working directory
WORKDIR /home/appuser

# Copy application files
COPY . .

# Command to run the application
CMD ["./my_app"]

Why This Matters: In the Dockerfile above, the useradd command creates a non-root user called appuser. This user is then set as the default user for the container using USER appuser. This simple step ensures that any process running inside the container operates with limited permissions.

Step 2: Build and Run the Container

Once the Dockerfile has been created, you can build and run your Docker image using the following commands:

# Build the Docker image
docker build -t my_non_root_app .

# Run the Docker container
docker run -it my_non_root_app

Why This Matters: The docker build command creates an image from the Dockerfile. The image can then be instantiated as a container using docker run. By running the container with a non-root user, you establish a baseline for security.

Step 3: Set Proper File Permissions

Another critical aspect is ensuring that your container can access required files and directories without requiring root access. Suppose your application needs to write to a log file. You can modify your Dockerfile to adjust the permissions:

# Make the log directory and change ownership
RUN mkdir -p /home/appuser/logs && chown appuser:appuser /home/appuser/logs

Why This Matters: The chown command changes the ownership of the logs directory to appuser, allowing the non-root user to write logs safely without escalating to root privileges.

Additional Best Practices

It's not enough to run containers as a non-root user. To bolster security further, consider the following additional practices:

1. Use User Namespaces

User namespaces provide an additional layer of security by remapping user IDs (UIDs) and group IDs (GIDs) inside containers to those on the host system. This means that even if an attacker gains access to the container, they wouldn't have the same privileges on the host.

To enable user namespaces, you need to configure Docker daemon settings. Add the following to your /etc/docker/daemon.json:

{
    "userns-remap": "default"
}

After saving the file, restart the Docker service:

sudo systemctl restart docker

2. Use Read-Only File Systems

You might consider assigning read-only permissions to the container's filesystem, preventing any alterations unless explicitly required. This can be done by adding a flag during the docker run command:

docker run --read-only -it my_non_root_app

Why This Matters: A read-only filesystem minimizes the risk of exploitation, ensuring that even if the container becomes compromised, the attacker has limited capability to alter its environment or data.

3. Limit Capabilities

By default, containers run with a set of Linux capabilities. You can remove unnecessary capabilities to further restrict what your container can do. For instance:

docker run --cap-drop ALL -it my_non_root_app

This command drops all Linux capabilities from the running container, ensuring it can only perform very basic operations.

Wrapping Up

The trade-off between convenience and security is always present in DevOps practices. However, the necessity of running Docker containers as non-root users cannot be overstated. Implementing best practices such as creating non-root users, utilizing user namespaces, configuring read-only filesystems, and limiting capabilities can significantly reduce vulnerabilities.

As you've seen, running containers safely involves understanding the security implications and proactively addressing them. Start implementing these practices today, and you'll make significant strides in securing your containerized applications.

For additional information on Docker security practices, you can refer to the official Docker documentation and the CIS Docker Benchmark.


This article should serve as a stepping stone for your journey into secure Docker practices. As the landscape evolves, staying updated with the latest security trends is essential. Happy coding, and stay secure!