Infrastructure at your Service

Soufiane Benmalek

How uid mapping works in Docker containers?

It can be interesting to see how uids between the docker host and docker containers are mapped. For example, for security concerns.
As a reminder, docker containers are based on two linux kernel features: linux namespaces and cgroups.

Basically, linux namespaces provide isolation for running processes and cgroups allows you to isolate resource usage.

Let’s first run a docker container. Here, we will run a mariadb docker in background with -d option

[docker@docker1 ~]$ docker run -d -e MYSQL_ROOT_PASSWORD=test123 mariadb

Now the container mariadb is running. Let’s see what is happening on a host level.

[docker@docker1 ~]$ ps -ef
polkitd   1729  1718  0 08:14 ?        00:00:00 mysqld

On a container level:

root@5c4450939d71:~# ps -ef
mysql        1     0  0 06:14 ?        00:00:00 mysqld
root       174     0  0 06:22 pts/0    00:00:00 bash

On the host level the mysqld process is running by polkitd and on a container level the process is running by mysql. Any ideas?
This is because the user id (UID) of the mysql user created in mariadb container corresponds to the same UID of the polkitd user on the host.

Let’s see what is the userid of the mysql user in the mariadb container

root@5c4450939d71:~# id mysql
uid=999(mysql) gid=999(mysql) groups=999(mysql)

The UID of mysql is 999. On the host:

[docker@docker1 ~]$ cat /etc/passwd | grep 999
polkitd:x:999:997:User for polkitd:/:/sbin/nologin

We can see that 999 corresponds to the polkitd user id.

How to change this?

Well, this could be a problem because we don’t want to run docker containers with a system user that we don’t know.

One solution could be to create a mysql user with a certain UID on the host:

[root@docker1 ~]# useradd -g mysql -u 1099 -m -r mysql

Then, we modify the user id inside the docker image. To do so, we need to rebuild a new mariadb image :-)
Let’s first clone the docker mariadb project

[docker@docker1 ~]$ git clone
Cloning into 'mariadb'...
remote: Counting objects: 751, done.
remote: Compressing objects: 100% (15/15), done.
remote: Total 751 (delta 9), reused 18 (delta 8), pack-reused 728
Receiving objects: 100% (751/751), 152.38 KiB | 0 bytes/s, done.
Resolving deltas: 100% (338/338), done.

We enter the directory of the mariadb version 10.3

[docker@docker1 ~]$ cd mariadb/10.3/

We need to modify the Dockerfile where all instructions are described

[docker@docker1 10.3]$ vi Dockerfile

Change this line

# vim:set ft=dockerfile:
FROM debian:jessie

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mysql && useradd -r -g mysql mysql

To this line

# vim:set ft=dockerfile:
FROM debian:jessie

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -g 1099 -r mysql && useradd -u 1099 -r -g mysql mysql

We rebuild a new image, let’s call it mariadbcustom

[docker@docker1 10.3]$ docker build -t mariadbcustom:latest .
Sending build context to Docker daemon  13.31kB
Step 1/19 : FROM debian:jessie
 ---> 5dd74d62fab8
Step 2/19 : RUN groupadd -g 1099 -r mysql && useradd -u 1099 -r -g mysql mysql
 ---> Using cache
 ---> a285892faa45
Step 3/19 : ENV GOSU_VERSION 1.10
 ---> Using cache
 ---> 069252945f7a
Step 4/19 : RUN set -ex;                fetchDeps='             ca-certificates                 wget    ';      apt-get update;         apt-get install -y --no-install-recommends $fetchDeps;   rm -rf /var/lib/apt/lists/*;            dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')";      wget -O /usr/local/bin/gosu "$GOSU_VERSION/gosu-$dpkgArch";     wget -O /usr/local/bin/gosu.asc "$GOSU_VERSION/gosu-$dpkgArch.asc";             export GNUPGHOME="$(mktemp -d)";        gpg --keyserver --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4;         gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu;       rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc;             chmod +x /usr/local/bin/gosu;    gosu nobody true;               apt-get purge -y --auto-remove $fetchDeps
 ---> Using cache
 ---> c82d4738b781
Step 5/19 : RUN mkdir /docker-entrypoint-initdb.d
 ---> Using cache
 ---> 08acd0843256
Step 6/19 : RUN apt-get update && apt-get install -y --no-install-recommends            apt-transport-https ca-certificates             pwgen   && rm -rf /var/lib/apt/lists/*
 ---> Using cache
 ---> 3ed44a5e3cf5
Step 7/19 : ENV GPG_KEYS        199369E5404BD5FC7D2FE43BCBCB082A1BB943DB        430BDF5C56E7C94E848EE60C1C4CBDCDCD2EFD2A        4D1BB29D63D98E422B2113B19334A25F8507EFA5
 ---> Using cache
 ---> b30af869afbb
Step 8/19 : RUN set -ex;        export GNUPGHOME="$(mktemp -d)";        for key in $GPG_KEYS; do                gpg --keyserver --recv-keys "$key";   done;   gpg --export $GPG_KEYS > /etc/apt/trusted.gpg.d/mariadb.gpg;    rm -r "$GNUPGHOME";     apt-key list
 ---> Using cache
 ---> 7a6e03190271
Step 9/19 : RUN echo "deb jessie main" > /etc/apt/sources.list.d/percona.list      &> /etc/apt/preferences.d/percona
 ---> Using cache
 ---> e55705d326a2
Step 10/19 : ENV MARIADB_MAJOR 10.3
 ---> Using cache
 ---> bb3bc4adcf42
Step 11/19 : ENV MARIADB_VERSION 1:10.3.6+maria~jessie
 ---> Using cache
 ---> 05bb1dc686c8
Step 12/19 : RUN echo "deb$MARIADB_MAJOR/debian jessie main" > /etc/apt/sources.list.d/mariadb.list     &> /etc/apt/preferences.d/mariadb
 ---> Using cache
 ---> 3626c50c8d83
Step 13/19 : RUN {              echo "mariadb-server-$MARIADB_MAJOR" mysql-server/root_password password 'unused';              echo "mariadb-server-$MARIADB_MAJOR" mysql-server/root_password_again password 'unused';         } | debconf-set-selections      && apt-get update       && apt-get install -y           "mariadb-server=$MARIADB_VERSION"                percona-xtrabackup-24           socat   && rm -rf /var/lib/apt/lists/*  && sed -ri 's/^user\s/#&/' /etc/mysql/my.cnf /etc/mysql/conf.d/*        && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql /var/run/mysqld      && chown -R mysql:mysql /var/lib/mysql /var/run/mysqld  && chmod 777 /var/run/mysqld    && find /etc/mysql/ -name '*.cnf' -print0                | xargs -0 grep -lZE '^(bind-address|log)'              | xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/'      && echo '[mysqld]\nskip-host-cache\nskip-name-resolve' > /etc/mysql/conf.d/docker.cnf
 ---> Using cache
 ---> 7d3d52632798
Step 14/19 : VOLUME /var/lib/mysql
 ---> Using cache
 ---> 3880f6c65676
Step 15/19 : COPY /usr/local/bin/
 ---> Using cache
 ---> 98aa1e3161c4
Step 16/19 : RUN ln -s usr/local/bin/ / # backwards compat
 ---> Using cache
 ---> a5394275c2b2
Step 17/19 : ENTRYPOINT [""]
 ---> Using cache
 ---> c456c7b34697
Step 18/19 : EXPOSE 3306
 ---> Using cache
 ---> 05068b456523
Step 19/19 : CMD ["mysqld"]
 ---> Using cache
 ---> 5973a27bfd43
Successfully built 5973a27bfd43
Successfully tagged mariadbcustom:latest

Let’s check our image is here

[docker@docker1 10.3]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mariadbcustom       latest              5973a27bfd43        8 days ago          403MB

we run a docker container with our new customized image

[docker@docker1 10.3]$ docker run -d -e MYSQL_ROOT_PASSWORD=test123 mariadbcustom

Let’s check if the user id was properly initialized to the mysql user

[docker@docker1 10.3]$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
7e344d87c4bc        mariadbcustom       "docker-entrypoint.s…"   6 minutes ago       Up 6 minutes        3306/tcp            hungry_heisenberg
[docker@docker1 10.3]$ docker exec -it hungry_heisenberg /bin/bash
root@7e344d87c4bc:~# id mysql
uid=1099(mysql) gid=1099(mysql) groups=1099(mysql)

We check also that the mysqld process run on the host as mysql user

[docker@docker1 10.3]$ ps -ef
mysql     2727  2716  2 14:05 ?        00:00:00 mysqld

On the host, we can see that the mysqld process runs as mysql user. Why? Because now the user id of the mysql user existing on the docker container corresponds to the one existing on the host. In this case, the user id is 1099.


In some use cases you might want to use a specific user to run some process and not using root or whatever user.However, in order to do that you sometimes need to change the Dockerfile or add a specific user on the host.


Leave a Reply

Soufiane Benmalek
Soufiane Benmalek