I haven’t got time to learn Docker systematically so far, but I still gain a lot from daily tasks. This post is a brief summary for what I have done to upgrade the container from owned by root to noon-root for security reason required by our customers.
I will talk the development workflow instead of the detail about how to set and modify the configurations inside the container:
I have a base image at beginning, let’s say root_is_engine.tar.gz
, load it:
1 | docker load -i root_is_engine.tar.gz |
you can check the loaded image by running:
1 | docker images |
Now I am going to create the container from this image, but wait, I don’t want the container to run any script or program when it starts, what I need is it just hangs without doing anything.
That means I need to override the default entrypoint
(entrypoint sets the command and parameters that will be executed first when spin up a container) in docker run command:
1 | docker run --detach \ |
Note that place the arguments to your entrypoint at the end of your docker command
-c 'tail -f /dev/null'
Run docker ps
command, you can see under COMMAND
column the entrypoint is what I specified:
1 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
Then get into the container by running:
1 | docker exec -it <container id or container name> [bash|sh] |
Then if you check the process status, you can see the init process with PID #1 is running tail
command to track /dev/null
device file:
1 | ps aux |
OK, now I can make changes, for example, create and switch to ordinary user with specified user id and group id, grant them privileges, modify the owner and permission of some files, run and update startup script line by line to see if the applications setup correctly with non-root.
Note if you have mount path in host machine, you may need to
chown
correct uid and gid, otherwise ordinary user in container may get permission denied issue.
After running some tests and they succeed, I need to commit the changes into a new image.
-
First understand how does
docker commit
work? what will be committed into new image?The container has a writable layer that stacks on top of the image layers. This writable layer allows you to make changes to the container since the lower layers in the image are read-only.
From the docker documentation, it said: It can be useful to commit a container’s file changes or settings into a new image.
The commit operation will not include any data contained in volumes mounted inside the container.
By default, the container being committed and its processes will be paused while the image is committed. This reduces the likelihood of encountering data corruption during the process of creating the commit. If this behavior is undesired, set the
--pause
option to false.The
--change
option will apply Dockerfile instructions to the image that is created. Supported Dockerfile instructions:CMD|ENTRYPOINT|ENV|EXPOSE|LABEL|ONBUILD|USER|VOLUME|WORKDIR
-
How about processes in the running container?
When you start container from image then process will start here - processes exists only in executing container, when container stops there are no processes anymore - only files from container’s filesystem.
Note that before committing, you need to quiesce the services and remove the mount path content to unlink all broken symbolic links.
Also remember to put the old entrypoint back:
1 | docker commit \ |
Note that
podman
commit format is different.
Note that you may use
bin/sh
instead of/bin/bash
.
OK, now start to run the new image with non-root user:
1 | docker run --detach \ |
Let’s see the processes in the non-root container:
1 | USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND |
If all things are in good shape, save the image into tar.gz format, of course you can use a new tag before saving:
1 | docker save is-engine-image:1 | gzip > ~/nonroot_is_engine.tar.gz |
Note that there is a
gzip
to compress the image.