Quick Links

Docker containers are generally ephemeral application instances which lack internal state. That's the best practice way to handle them that lets you stop or restart your containers at any time.

Sometimes modifications to a container's filesystem are unavoidable though. Perhaps you're trying out software and want a snapshot to return to later. Another use case could be situations where the software inside a container's stopped working and you want to save a replica you can debug in the future.

Here's how to create a new Docker image from an existing container. You'll then be able to start another container from that image which will be populated with the filesystem from the first one.

Committing Containers

The docker commit command is used to take a container and produce a new image from it. It works with either stopped or running containers.

The basic syntax is as follows:

docker commit example-container example-image:latest

This creates an image from the container named example-container. You can also identify the container by ID if you prefer. Both pieces of information are available from the output of docker ps which lists all the containers on your host.

Screenshot of docker ps output

The resulting image is assigned the tag given as the command's second parameter. This is example-image:latest in the example shown above. Just like a regular image tagging operation, the new image will replace the tag's reference if it already exists.

Screenshot of committing a Docker image

Now you can use your image to restore the filesystem from example-container into a new container instance:

docker run -d example-image:latest

The filesystem content will match the example-container container at the time the docker commit command was executed There is one important caveat: the content of mounted volumes will not be included, so their mount locations will be empty in the created container image. To run a new container with volume data intact, use the -v flag to reattach the volumes from the first container when you start the second instance with docker run.

Another noteworthy sticking point is how Docker handles commits of running containers. For the most part, this should work seamlessly but it defaults to pausing the target container before the commit is created. All the processes within the container will be suspended and then resumed after the image creation is complete. This improves data consistency in the new image but leaves the container momentarily inaccessible. You can disable this behavior by including --pause false with your docker commit command.

Adding Commit Messages

The docker commit command supports commit messages in a similar fashion to version control software like Git. Adding a message when you create an image from a container lets you document what's changed and the reason behind your commit.

Use the --message or -m flag to apply a commit message:

docker commit -m "Example commit" example-container example-image:latest

You can add authorship information with a dedicated flag too. Supply a string in the common First Name <email@example.com> format to the --author or -a flag. It'll be saved alongside the commit message.

docker commit -a "Example Author <example@example.com>" -m "Example commit" example-container example-image:latest

Commit messages are displayed when you use the docker history command to view the layers in an image. They'll show up in the COMMENT column on the far right.

Screenshot of viewing Docker image comments with &quot;docker history&quot;

Another way of accessing this information is to use docker inspect in tandem with grep to extract authorship and comment values from an image's JSON representation:

docker inspect <image-id> | grep 'Created|Author|Comment'

Screenshot of viewing Docker image commit data with &quot;docker inspect&quot;

This will show the data associated with the top-most layer in the image.

Changing Dockerfile Instructions

Committing an image gives you a chance to mutate some of its Dockerfile instructions. You can override the following values in your new image:

  • CMD
  • ENTRYPOINT
  • ENV
  • EXPOSE
  • LABEL
  • ONBUILD
  • USER
  • VOLUME
  • WORKDIR

To set an instruction, use the --change or -c flag:

docker commit --change 'ENTRYPOINT ["sh"]' example-container example-image:latest

You can repeat the flag as many times as necessary to apply all your intended changes.

Only instructions which impact the top-most filesystem layer are supported. You can't seamlessly extend a committed image with new layers via instructions such as RUN and COPY. However you could take the result of a commit and write a new Dockerfile that adds new content if required:

# Created via `docker commit`
    

FROM example-image:latest

RUN apt install example-package

If you do change Dockerfile instructions at commit time, it's worth adding a commit message that explains what you're modifying and why. This will help anyone else with access to the image understand any behavior differences compared to the container it was created from.

Summary

Docker images are usually built from Dockerfiles and used to start disposable containers. Changes to the state of a container's filesystem are made by rebuilding the image, destroying the existing container, and starting a new one. In an ideal world, containers don't have any internal state but this isn't always true in practice.

Committing a container gives you a way to restore its current filesystem in the future. Commits are useful for creating replicas of troublesome containers so you can debug in a separate environment while maintaining access to previously generated logs and temporary files.

Although container commits often feel similar to VM snapshots, they aren't quite the same thing. VMs control virtual hardware and the state of that hardware will be present within the snapshot. Docker containers are just a set of processes running on the host; a commit is a new Docker image that represents the container's filesystem but necessarily lacks any data about the state of processes, the kernel, and your hardware.