Docker Tips: Running a Container With a Non Root UserMethods and examplesLuc JuggeryBlockedUnblockFollowFollowingSep 20, 2018TL;DROne best practice when running a container is to launch the process with a non root user.
This is usually done through the usage of the USER instruction in the Dockerfile.
But, if this instruction is not present, it doesn’t necessarily mean the process is run as root.
The rationaleBy default, root in a container is the same root (uid 0) as on the host machine.
If a user manages to break out of an application running as root in a container, he may be able to gain access to the host with the same root user.
This access would be even easier to gain if the container was run with incorrect flags or with bind mounts of host folders in R/W.
Running a MongoDB containerIf you don’t know it yet, I highly recommend to give Play With Docker a try.
Also known as PWD, it’s an online playground where you can test all the latest Docker features without having to install anything locally.
Once in PWD, you can create an instance, and you’ll feel as if you’re in the shell of a Linux VM.
Note: Under the hood, you’ll have a shell but in an Alpine container in which the Docker daemon is installed.
That’s what is called DinD, for Docker in Docker, as the Docker daemon runs itself in a container.
Once in the terminal, let’s run a container based on the MongoDB image:[node1] (local) root@192.
168.
0.
13 ~$ docker container run -d -p 27017:27017 –name mongo mongo:4.
08cce38822a23bbacb5349c5af63c50f1d2e371029f5b6332b1144fcc4f8cb723And check, from the host machine, which user runs the mongod process:[node1] (local) root@192.
168.
0.
13 ~$ ps aux | grep mongo 1143 999 0:00 mongod –bind_ip_allFrom the output above, we can see that the user identified by the uid 999 is the one who owns the mongod process.
Let’s check the existing users on the host:$ cat /etc/passwdroot:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologinsync:x:5:0:sync:/sbin:/bin/syncshutdown:x:6:0:shutdown:/sbin:/sbin/shutdownhalt:x:7:0:halt:/sbin:/sbin/haltmail:x:8:12:mail:/var/spool/mail:/sbin/nologinnews:x:9:13:news:/usr/lib/news:/sbin/nologinuucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologinoperator:x:11:0:operator:/root:/bin/shman:x:13:15:man:/usr/man:/sbin/nologinpostmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologincron:x:16:16:cron:/var/spool/cron:/sbin/nologinftp:x:21:21::/var/lib/ftp:/sbin/nologinsshd:x:22:22:sshd:/dev/null:/sbin/nologinat:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologinsquid:x:31:31:Squid:/var/cache/squid:/sbin/nologinxfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologingames:x:35:35:games:/usr/games:/sbin/nologinpostgres:x:70:70::/var/lib/postgresql:/bin/shcyrus:x:85:12::/usr/cyrus:/sbin/nologinvpopmail:x:89:89::/var/vpopmail:/sbin/nologinntp:x:123:123:NTP:/var/empty:/sbin/nologinsmmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologinguest:x:405:100:guest:/dev/null:/sbin/nologinnobody:x:65534:65534:nobody:/:/sbin/nologindockremap:x:100:101:Linux User,,,:/home/dockremap:/bin/falseThere is no user with uid 999, that’s the reason why no user name can be mapped to this uid in the previous command.
DockerfileThis is the Dockerfile used to build the MongoDB 4.
0 image:docker-library/mongoDocker Official Image packaging for MongoDB.
Contribute to docker-library/mongo development by creating an account on…github.
comIn this file, there is no USER instruction, but we can see a new mongodb user is created in the image, and added to a mongodb group created at the same time.
This is what the following instruction is used for:RUN groupadd -r mongodb && useradd -r -g mongodb mongodbAs it’s not specified through a USER instruction in the Dockerfile, this user is not used during the image construction; everything is done with root.
But, if we have a closer look at the end of the Dockerfile, we can see both ENTRYPOINT and CMD instructions.
ENTRYPOINT ["docker-entrypoint.
sh"]CMD ["mongod"]As you probably know, the concatenation of those two instructions defines the command which is run when the container is started out of the mongo image.
The command is then the following one:$ docker-entrypoint.
sh mongodThe ENTRYPOINTLet’s now take a look into the code of the docker-entrypoint.
sh file :docker-library/mongoDocker Official Image packaging for MongoDB.
Contribute to docker-library/mongo development by creating an account on…github.
comThe following piece of code at the beginning of the file is very interesting.
This is the part where the user executing the process is changed from root to mongodb thanks to the gosu utility.
# allow the container to be started with ` — user# all mongo* commands should be dropped to the correct userif [[ “$originalArgOne” == mongo* ]] && [ “$(id -u)” = ‘0’ ]; then if [ “$originalArgOne” = ‘mongod’ ]; then chown -R mongodb /data/configdb /data/db fi # make sure we can write to stdout and stderr as “mongodb” # (for our “initdb” code later; see “ — logpath” below) chown –dereference mongodb “/proc/$$/fd/1” “/proc/$$/fd/2” || : exec gosu mongodb “$BASH_SOURCE” “$@”fiNote: we can see in the Dockerfile, that the gosu utility is among the packages installed when the image is created.
Image based on UbuntuThe first instruction in the Dockerfile indicates that ubuntu:xenial is the base image, the image from which the mongo image is created.
Let’s run an interactive container based on Ubuntu and list the existing users:$ docker container run -ti ubuntu:xenialroot@9e367c3d9ca1:/# cat /etc/passwdroot:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologinirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologingnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologinnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologinsystemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/falsesystemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/falsesystemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/falsesystemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false_apt:x:104:65534::/nonexistent:/bin/falseLet’s now create a dummy user and group:root@9e367c3d9ca1:/# groupadd -r mygrp && useradd -r -g mygrp myuserand list the users once again:root@9e367c3d9ca1:/# cat /etc/passwdroot:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologinirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologingnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologinnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologinsystemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/falsesystemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/falsesystemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/falsesystemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false_apt:x:104:65534::/nonexistent:/bin/falsemyuser:x:999:999::/home/myuser:We can see the new user created as the uid 999, which is the uid of the first user created out of a fresh ubuntu:xenial image.
This uid is the one used to run the mongod process as we saw before.
As a reminder:[node1] (local) root@192.
168.
0.
13 ~$ ps aux | grep mongo 1143 999 0:00 mongod –bind_ip_allImage based on AlpineApplication images are not necessarily based on ubuntu:xenial.
A lot of them are based on Alpine (tiny distribution focused on security).
Let’s add a new user out of a fresh alpine container and check its uid.
$ docker container run -ti alpine:3.
8/ # adduser -D myuser/ # cat /etc/passwdroot:x:0:0:root:/root:/bin/ashbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinadm:x:3:4:adm:/var/adm:/sbin/nologinlp:x:4:7:lp:/var/spool/lpd:/sbin/nologinsync:x:5:0:sync:/sbin:/bin/syncshutdown:x:6:0:shutdown:/sbin:/sbin/shutdownhalt:x:7:0:halt:/sbin:/sbin/haltmail:x:8:12:mail:/var/spool/mail:/sbin/nologinnews:x:9:13:news:/usr/lib/news:/sbin/nologinuucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologinoperator:x:11:0:operator:/root:/bin/shman:x:13:15:man:/usr/man:/sbin/nologinpostmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologincron:x:16:16:cron:/var/spool/cron:/sbin/nologinftp:x:21:21::/var/lib/ftp:/sbin/nologinsshd:x:22:22:sshd:/dev/null:/sbin/nologinat:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologinsquid:x:31:31:Squid:/var/cache/squid:/sbin/nologinxfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologingames:x:35:35:games:/usr/games:/sbin/nologinpostgres:x:70:70::/var/lib/postgresql:/bin/shcyrus:x:85:12::/usr/cyrus:/sbin/nologinvpopmail:x:89:89::/var/vpopmail:/sbin/nologinntp:x:123:123:NTP:/var/empty:/sbin/nologinsmmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologinguest:x:405:100:guest:/dev/null:/sbin/nologinnobody:x:65534:65534:nobody:/:/sbin/nologinmyuser:x:1000:1000:Linux User,,,:/home/myuser:As we can see here, the id of the first user in an alpine image is 1000, different from the uid 999 of an ubuntu image.
If we add a user in an alpine image and run a process with this user (using the USER instruction in the Dockerfile, for instance), we will see the uid 1000 as the owner of the process.
Let’s try it.
Let’s use a simple Dockerfile that adds a user to an Alpine image and defines a basic sleep 1000 command.
FROM alpine:3.
8RUN adduser -D myuserUSER myuserENTRYPOINT [“sleep”]CMD [“1000”]Let’s build an image from it:$ docker image build -t sleep:1.
0 .
Sending build context to Docker daemon 1.
775MBStep 1/5 : FROM alpine:3.
83.
8: Pulling from library/alpine4fe2ade4980c: Pull completeDigest: sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528Status: Downloaded newer image for alpine:3.
8 — -> 196d12cf6ab1Step 2/5 : RUN adduser -D myuser — -> Running in a7474167f27dRemoving intermediate container a7474167f27d — -> 7a17f0862780Step 3/5 : USER myuser — -> Running in b0a7eea711a4Removing intermediate container b0a7eea711a4 — -> d63533ce5be1Step 4/5 : ENTRYPOINT [“sleep”] — -> Running in f0dfc3ea4495Removing intermediate container f0dfc3ea4495 — -> 763dd8ac4f40Step 5/5 : CMD [“1000”] — -> Running in 14db1ea262f9Removing intermediate container 14db1ea262f9 — -> 978294e76184Successfully built 978294e76184Successfully tagged sleep:1.
0And then run a container from the newly created image:[node1] (local) root@192.
168.
0.
8 ~$ docker container run -d sleep:1.
0534e340780a89b3a86917aff2c20405dadbd7d50cfe5cb03e9cb6786a0517f21If we check the owner of the sleep process on the host, we can see it belongs to the user with uid 1000, the one that is created in the image.
[node1] (local) root@192.
168.
0.
8 ~$ ps aux | grep sleep 1181 1000 0:00 sleep 1000SummaryI hope those examples help you to understand some of the ways containers can be run with non root user, either through the image of the USER instruction in the Dockerfile or by changing the user at runtime (usually done within an entrypoint script).
Another way we did not cover here would be through the usage of the –user flag when running a container.
.