Docker containers are ephemeral, and that means changes to a running container are not preserved. In general, it is advisable to continue using Dockerfiles to generate images, as we want to ensure a certain state exists for all new containers based on an image. However, what happens when you need to modify a running Docker container and preserve those changes?
There are a few scenarios where one would want to commit changes from a container to a new image. One example would be to debug an existing container, where you need to hotfix a bug or bad configurations.
Another scenario could be for investigating an issue, where you need to exact state of a running container. For example, an intrusion was detected and you need to preserve the running container to thoroughly examine it.
The container is now comprised and you need to analyze it, however, the ephemeral nature of containers means you should also replace the running container immediately. By committing the compromised container to a new image, it can be investigated in a safe sandbox environment, while a replacement container can be deployed immediately to remove any damage or payloads.
The Docker commit command was introduced to provide a mechanism to commit container changes.
As mentioned, Docker commit can create a mirror image of a container. It can modify instructions, such as CMD or ENV when hotfixes are needed in an image.
Docker Layers and How Commit Works
Docker images and, inherently, containers consist of multiple layers, with each layer being a separate filesystem stacked on top of each other.
Each Dockerfile instruction creates a new layer, which are given a unique hash value, and then stacked on top of the next instruction set. Layers actually exist as directories on the host server, with the their hash value used for their name.
When Docker commit is executed, the newly created image references all of the unaltered layers used by the original image, and then copies the ephemeral layers into permanent storage.
Committing an Existing Container
To commit changes from a container to a new image, you use the docker commit command and specify the source container ID.
The container you are committing from can be running or stopped, the command will work either way.
docker commit <container id> <image-name>:<version>
For example, we have a Python Flask API running on a Docker host. It has been running for 7 days, and a back is needed for audit reasons.
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 13c6e55583a5 serverlab/flask-api "python main.py" 7 days ago Up 7 days 0.0.0.0:5000->5000/tcp dreamy_grothendieck
To commit the container to an image, we would use the following command.
docker commit 13c6e55583a5 serverlab/flask-api-audit:20190612
A new hash will be outputted, which is assigned to the newly created image.
When we list Docker images with the docker image command, we can see our newly created image.
REPOSITORY TAG IMAGE ID CREATED SIZE
serverlab/flask-api-audit 20190612 dcbb3c4f74fa About a minute ago 464MB
Modifying ENV Instructions
To change Docker image instructions, such as ENV when adjustments to Environment Variables are needed, you use the –change flag with docker commit.
For example, if you wanted to change the ENV instruction for an environment variable for setting the containers environment, you would use the following command.
docker commit --change='ENV environment production' dcbb3c4 serverlab/flask-api:1.0.1
Modifying the CMD Instruction
Like the ENV instruction above, we use the –change flag to adjust the CMD instruction.
For example, if we mistakenly run a Python Flask API using Python directly rather than uwsgi server, we would run the following command.
docker commit --change='CMD ["
uwsgi", "--ini", "myproject.ini"] dcbb3c4 serverlab/flask-api:1.0.2
Modify Multiple Instructions
The commit command allows you to modify multiple instructions, if needed. Additional -c flags with instruction changes can be added to the command.
For example, to adjust the CMD instruction and EXPOSE instruction, you would use the following command.
docker commit --change='CMD ["uwsgi", "--ini", "myproject.ini"]' -c "EXPOSE 80" dcbb3c4 serverlab/flask-api:1.0.2