Introducing container-diff, a tool for quickly comparing container images

NOV 21, 2017
Originally posted by Nick Kubala, Colette Torres, and Abby Tisdale from the Container Tools team, on the Google Open Source Blog

The Google Container Tools team originally built container-diff, a new project to help uncover differences between container images, to aid our own development with containers. We think it can be useful for anyone building containerized software, so we're excited to release it as open source to the development community.

Containers and the Dockerfile format help make customization of an application's runtime environment more approachable and easier to understand. While this is a great advantage of using containers in software development, a major drawback is that it can be hard to visualize what changes in a container image will result from a change in the respective Dockerfile. This can lead to bloated images and make tracking down issues difficult.

Imagine a scenario where a developer is working on an application, built on a runtime image maintained by a third-party. During development someone releases a new version of that base image with updated system packages. The developer rebuilds their application and picks up the latest version of the base image, and suddenly their application stops working; it depended on a previous version of one of the installed system packages, but which one? What version was it on before? With no currently existing tool to easily determine what changed between the two base image versions, this totally stalls development until the developer can track down the package version incompatibility.

Introducing container-diff

container-diff helps users investigate image changes by computing semantic diffs between images. What this means is that container-diff figures out on a low-level what data changed, and then combines this with an understanding of package manager information to output this information in a format that's actually readable to users. The tool can find differences in system packages, language-level packages, and files in a container image.

Users can specify images in several formats - from local Docker daemon (using the prefix `daemon://` on the image path), a remote registry (using the prefix `remote://`), or a file in the .tar in the format exported by "docker save" command. You can also combine these formats to compute the diff between a local version of an image and a remote version. This can be useful when experimenting with new builds of an image that you might not be quite ready to push yet. container-diff supports image tarballs and the registry protocol natively, enabling it to run in environments without a Docker daemon.

Examples and Use Cases

Here is a basic Dockerfile that installs Python inside our Debian base image. Running container-diff on the base image and the new one with Python, users can see all the apt packages that were installed as dependencies of Python.

➜ debian_with_python cat Dockerfile
      RUN apt-get update && apt-get install -qq --force-yes python
      ➜ debian_with_python docker build -q -t debian_with_python .
      ➜ debian_with_python container-diff diff

      Packages found only in None

      Packages found only in debian_with_python:latest:
      -file 1:5.22 15-2+deb8u3 76K
      -libexpat1 2.1.0-6 deb8u4 386K
      -libffi6 3.1-2 deb8u1 43K
      -libmagic1 1:5.22 15-2+deb8u3 3.1M
      -libpython-stdlib 2.7.9-1 54K
      -libpython2.7-minimal 2.7.9-2 deb8u1 2.6M
      -libpython2.7-stdlib 2.7.9-2 deb8u1 8.2M
      -libsqlite3-0 deb8u2 877K
      -mime-support 3.58 146K
      -python 2.7.9-1 680K
      -python-minimal 2.7.9-1 163K
      -python2.7 2.7.9-2 deb8u1 360K
      -python2.7-minimal 2.7.9-2 deb8u1 3.7M

      Version differences: None

And below is a Dockerfile that inherits from our Python base runtime image, and then installs the mock and six packages inside of it. Running container-diff with the pip differ, users can see all the Python packages that have either been installed or changed as a result of this:

➜ python_upgrade cat Dockerfile
      RUN pip install -U six
      ➜ python_upgrade docker build -q -t python_upgrade .
      ➜ python_upgrade container-diff diff
      daemon://python_upgrade:latest --types=pip


      Packages found only in None

      Packages found only in python_upgrade:latest:
      -funcsigs 1.0.2 51.4K
      -mock 2.0.0 531.2K
      -pbr 3.1.1 471.1K

      Version differences:
      PACKAGE IMAGE1 ( IMAGE2 (python_upgrade:latest)
      -six 1.8.0, 26.7K

This can be especially useful when it's unclear which packages might have been installed or changed incidentally as a result of dependency management of Python modules.

These are just a few examples. The tool currently has support for Python and Node.js packages installed via pip and npm, respectively, as well as comparison of image filesystems and Docker history. In the future, we'd like to see support added for additional runtime and language differs, including Java, Go, and Ruby. External contributions are welcome! For more information on contributing to container-diff, see this how-to guide.

Now that we've seen container-diff compare two images in action, it's easy to imagine how the tool may be integrated into larger workflows to aid in development:

container-diff's default output mode is "human-readable," but also supports output to JSON, allowing for easy automated parsing and processing by users.

Single Image Analysis

In addition to comparing two images, container-diff has the ability to analyze a single image on its own. This can enable users to get a quick glance at information about an image, such as its system and language-level package installations and filesystem contents.

Let's take a look at our Debian base image again. We can use the tool to easily view a list of all packages installed in the image, along with each one's installed version and size:

➜ Development container-diff analyze

      Packages found in
      -acl 2.2.52-2 258K
      -adduser 3.113 nmu3 1M
      -apt 3.1M
      -base-files 8 deb8u9 413K
      -base-passwd 3.5.37 185K
      -bash 4.3-11 deb8u1 4.9M
      -bsdutils 1:2.25.2-6 181K
      -ca-certificates 20141019 deb8u3 367K
      -coreutils 8.23-4 13.9M
      -dash 0.5.7-4 b1 191K
      -debconf 1.5.56 deb8u1 614K
      -debconf-i18n 1.5.56 deb8u1 1.1M
      -debian-archive-keyring 2017.5~deb8u1 137K

We could use this to verify compatibility with an application we're building, or maybe sort the packages by size in another one of our images and see which ones are taking up the most space.

For more information about this tool as well as a breakdown with examples, uses, and inner workings of the tool, please take a look at documentation on our GitHub page. Happy diffing!

Special thanks to Colette Torres and Abby Tisdale, our software engineering interns who helped build the tool from the ground up.