DEV Community

izackv
izackv

Posted on

Running a Docker Container with a Custom Non-Root User: Syncing Host and Container Permissions

Containers

Introduction

Docker has become a popular platform for developers to create, deploy, and run applications in a portable, consistent environment.

By default, Docker containers run as the root user, which can pose security risks if the container becomes compromised. 
Also, running as root can be an issue when sharing folders between the host and the docker container.

To reduce these risks, we'll discuss running a Docker container with a custom non-root user that matches your host Linux user's user ID (UID) and group ID (GID), ensuring seamless permission handling for mounted folders.

Running a docker build command that uses (mainly) a non-root user might force us to use sudo for some commands.
The same is valid for running the docker itself using unattended scripts.
You may need elevated privileges for specific tasks.
Granting password-less sudo permissions to a non-root user allows you to perform administrative tasks without the risk of running the entire container as the root user.

Here are the steps to create and run a Docker container with a non-root user and password-less sudo permissions:

Step 1: Adjust the Dockerfile to Accept UID and GID as Arguments

Modify your Dockerfile to accept the host's UID and GID as arguments. This way, you can create a user in the container with a matching UID and GID.
Add the following lines to your Dockerfile:

FROM ubuntu
ARG UID
ARG GID

# Update the package list, install sudo, create a non-root user, and grant password-less sudo permissions
RUN apt update && \
    apt install -y sudo && \
    addgroup --gid $GID nonroot && \
    adduser --uid $UID --gid $GID --disabled-password --gecos "" nonroot && \
    echo 'nonroot ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers

# Set the non-root user as the default user
USER nonroot
Enter fullscreen mode Exit fullscreen mode

Step 2: Set the Working Directory

Set the working directory where the non-root user can access it. Add the following line to your Dockerfile:

# Set the working directory
WORKDIR /home/nonroot/app
Enter fullscreen mode Exit fullscreen mode

This sets the working directory to '/home/nonroot/app', where the non-root user has read and write permissions.

Step 3: Copy Files and Set Permissions

Ensure the non-root user has the necessary permissions to access the copied files. Add the following lines to your Dockerfile:

# Copy files into the container and set the appropriate permissions
COPY --chown=nonroot:nonroot . /home/nonroot/app
RUN chmod -R 755 /home/nonroot/app
Enter fullscreen mode Exit fullscreen mode

Step 4: Build and Run the Docker Container with UID and GID Parameters

Now you can build the Docker image and run the container with the custom non-root user. Pass your host's UID and GID as build arguments to create a user with matching permissions.
Use the following commands to build and run your container:

# Get your host's UID and GID
export HOST_UID=$(id -u)
export HOST_GID=$(id -g)

# Build the Docker image
docker build --build-arg UID=$HOST_UID --build-arg GID=$HOST_GID -t your-image-name .

# Run the Docker container
docker run -it --rm --name your-container-name your-image-name id
Enter fullscreen mode Exit fullscreen mode

The docker output will be:
uid=1000(nonroot) gid=1000(nonroot) groups=1000(nonroot)

Optional - Adding Docker Compose for Running a Custom Non-Root User Container

Docker Compose is a tool for defining and running multi-container applications using a YAML file to configure the application's services, networks, and volumes.
It simplifies managing containers, especially when working with multiple services.
This section will discuss how to use Docker Compose to run a Docker container with a custom non-root user that matches your host's UID and GID.
Create a docker-compose.yml file in your project directory with the following content:

version: '3.8'

services:
  your_service_name:
    build:
      context: .
      args:
        UID: ${HOST_UID}
        GID: ${HOST_GID}
    image: your-image-name
    container_name: your-container-name
    volumes:
      - ./app:/home/nonroot/app
Enter fullscreen mode Exit fullscreen mode

This YAML file defines a service, your_service_name, using the Dockerfile in the current directory. The build section passes the UID and GID build arguments from the host environment variables HOST_UID and HOST_GID. The volumes section maps a local directory (./app) to the container's working directory (/home/nonroot/app), ensuring seamless permission handling for the mounted folder.

First, to run the container using Docker Compose set the HOST_UID and HOST_GID environment variables in your host system.
The following command will build the docker (if needed), start it, print the user ID, and remove the container:

HOST_UID=$(id -u) HOST_GID=$(id -g) docker compose run --rm your_service_name id
Enter fullscreen mode Exit fullscreen mode

Conclusion

Running a Docker container with a custom non-root user that matches your host's UID and GID ensures seamless permission handling for mounted folders while maintaining security.
Optimizing the Dockerfile and combining RUN commands can reduce the image size and improve performance. Following these steps will help you create and run a Docker container with a non-root user that aligns with your host's permissions, reducing the risk of potential security breaches and permission issues. Always prioritize security when deploying applications and containers to ensure a safe and stable environment.

Integrating Docker Compose into your workflow simplifies container management and improves the overall development experience, allowing you to focus on building your application.

Top comments (4)

Collapse
 
tbroyer profile image
Thomas Broyer

Step 3 is both useless/wasteful and a security risk:

  • you should chmod outside the image before you COPY to avoid duplicating all the files in a new layer (explore them with a tool like Dive to detect such waste; also note that while not documented you can --chmod during COPY with BuildKit enabled, but this applies to files and directories, and most if the time you don't want files to be executables)
  • apps shouldn't be given permission to modify themselves; while not as important as on a non-containerized system, a vulnerability in the app could lead to it modifying its own code and configuration files, which could allow RCEs. We've seen that for config files with log4j and logback a year ago. Only "data" files should be writeable.
Collapse
 
tbroyer profile image
Thomas Broyer

Also, rather than make the container user's UID/GID configurables, you can in many cases make the app executable as any arbitrary user (like most software on your machine) and the --user argument at runtime without having to rebuild the image.

Collapse
 
izackv profile image
izackv

Hi Thomas, thanks for your remarks.
During this build process, I'm building the docker using my uid. Not just running it after the build finished (as the --user imply)
Installing using my user ID lets me simulate the standard installation of OS packages, PIP packages, and any other installation step required by the installing app/code.

Once the process runs correctly inside the docker and passes all tests, I can decide whether to install it on my real machine. If yes, I already have a perfect install done with my user from step one of the building.

The user is a parameter because all my dockers might run using the CI/CD users or other users.
And the Idea is to share files between the image and the OS without any permissions issues.
Also, login into a Linux shell using one user and running the docker as another user (using the --user) is something I'm trying to avoid.

In my usual workflow, switching a user should rebuild the image because I want to verify that the build process works for any user that should install/run the image.
I don't want to build with one user, then run with another. this might be a security risk.
And I want the CI/CD to rebuild the image anyway.

Some apps/code should be restricted from being able to change file permissions, execute external code, and more.
But this is not true for all. There are a lot of apps/codes that need to be able to change file permissions and more.
This is the first reason I'm setting the container (from the very first building step) to use my user and not the default root user.

Collapse
 
marouanekhadiri profile image
marouane-khadiri

Hello,
i'm using an image docker from liquibase.
On step 1 i got this error:
addgroup --gid Value "non root" invalid for option gid (number expected)
do you have any idea please ?