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 own 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:
docker load -i root_is_engine.tar.gz
you can check the loaded image by running:
docker images
Now I am going to create the container from this template, but wait, I don't want it to run any script or program when I initialize it, what I need is it just hangs there like a fresh new white paper and I will draw something step by step on it.
That means I need to override the default entry point
(entry point sets the command and parameters that will be executed first when a container is run) in docker run command:
docker run --detach \
--cap-add=SYS_ADMIN \
--privileged=false --name=${ENGINE_HOST} --hostname=${ENGINE_HOST} \
--restart=always --add-host="${SERVICES_HOST} ${DB2_XMETA_HOST} ${ENGINE_HOST}":${ENGINE_HOST_IP} \
-p 8449:8449 \
-v ${DEDICATED_ENGINE_VOLPATH}/${ENGINE_HOST}/EngineClients/db2_client/dsadm:/home/dsadm \
--entrypoint=/bin/sh \
${DOCKER_IMAGE_TAG_ENGINE}:${DOCKER_IMAGE_VERSION} \
-c 'tail -f /dev/null'
Note that place the arguments to your entrypoint at the end of your docker command , here is
-c 'tail -f /dev/null'
Run docker ps
command, you can see under COMMAND
column the entry point is what we specified:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b462f6123684 is-engine-image:1 "/bin/sh -c 'tail -f..." 2 days ago Up 2 days 0.0.0.0:8449->8449/tcp is-en-conductor-0.en-cond
Then get into the container by running:
docker exec -it <container id or container name> bash
Also 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:
ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 5968 616 ? Ss 16:47 0:00 tail -f /dev/null
root 27 1.5 0.0 13420 1992 pts/0 Ss 16:50 0:00 bash
root 45 0.0 0.0 53340 1864 pts/0 R+ 16:50 0:00 ps aux
OK, now I can make changes inside container like 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 startup script line by line to see if the applications setup correctly with non-root.
Note if you have mount path with host machine, you may need to
chown
correct uid and gid in that path in host, otherwise ordinary user in container cannot access these directories.
After verification with simple tests, 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 inside the 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: Before committing, you need to quiesce the services and remove the mount path content, unlink any other broken symbolic links.
When commit, remember to restore the old entry point:
docker commit --change 'ENTRYPOINT ["/opt/xx/initScripts/startcontainer.sh"]' <container ID> is-engine-image:1
OK, now start to run the new image with non-root user:
docker run --detach \
--user 1000 \
--cap-add=SYS_ADMIN \
--privileged=false --name=${ENGINE_HOST} --hostname=${ENGINE_HOST} \
--restart=always --add-host="${SERVICES_HOST} ${DB2_XMETA_HOST} ${ENGINE_HOST}":${ENGINE_HOST_IP} \
-p 8449:8449 \
-v ${DEDICATED_ENGINE_VOLPATH}/${ENGINE_HOST}/EngineClients/db2_client/dsadm:/home/dsadm \
${DOCKER_IMAGE_TAG_ENGINE}:${DOCKER_IMAGE_VERSION}
Let's see the processes in the non-root container:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
dsadm 1 0.0 0.0 13288 1604 ? Ss 18:29 0:00 /bin/bash /opt/xx/initScripts/startcontainer.sh
dsadm 540 0.0 0.0 5968 620 ? S 18:29 0:00 tail -f /dev/null
dsadm 568 0.1 0.3 2309632 24792 ? Sl 18:29 0:00 /opt/xx/../../jdk
dsadm 589 2.5 0.0 13420 2012 pts/0 Ss 18:36 0:00 bash
dsadm 610 0.0 0.0 53340 1868 pts/0 R+ 18:36 0:00 ps aux
If all things work good, save the image into tar.gz format, of course you can use a new tag before saving:
docker save is-engine-image:1 > ~/nonroot_is_engine.tar.gz