The original mission of the atomic application to install, manage, and run container images using container labels has slowly been growing. We have been concentrating on ease-of-use and value-add functions for containers and images, and to accomplish this have recently added the atomic diff command. This command allows you to differentiate between two container images, which is critically important if you are like me and you have hundreds of container images on your system. I also use it when I pull an updated image from a repository and want to see what has changed.

Note: While docker itself has a subcommand called diff, it is only useful when trying to determine the difference between a container and its image. It does not compare two different docker objects looking for differences amongst them.

The atomic diff command is fairly simple to run. By default it will provide a file-level diff between the two docker-based objects grouped by:

  • Files only found in docker object A
  • Files only found in docker object B
  • Files found in both docker objects but differ

It can optionally also evaluate the differences between those same objects if they are RPM-based. Looking at a real-world example will probably illuminate its usefulness more readily.

For this example, I have created two simple images that use the CentOS base image as a seed image. In one image, I have added the package wget and in the other I have added the package less. The two Dockerfiles are, respectively:

FROM docker.io/centos:latest
RUN yum -y install less && yum clean all
FROM docker.io/centos:latest
RUN yum -y install wget && yum clean all

And my docker images look like:

REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
centos_with_less    latest              29ef3785d2d7        About a minute ago   217.2 MB
centos_with_wget    latest              3b32e38b6eb0        18 minutes ago       214.3 MB
docker.io/centos    latest              c8a648134623        2 weeks ago          196.6 MB

To perform an atomic diff of these two images, we simply issue the atomic diff command followed by the two image IDs or names. Because we are not passing any additional switches to the atomic command, the output will look like:

$ sudo atomic diff centos_with_less centos_with_wget
Files only in centos_with_less:
     /bin/gtroff
     /bin/troff
     /bin/lessecho
     /bin/lesspipe.sh
     /bin/lesskey
     /bin/gtbl
     /bin/zsoelim
     /bin/gnroff
     # content omitted for space

Files only in centos_with_wget:
     /bin/wget
     /run/secrets
     /etc/wgetrc
     /usr/bin/wget
     /usr/share/locale/en_GB/LC_MESSAGES/wget.mo
     # content omitted for space

Common files that are different:
     /var/lib/yum/history/history-2015-12-23.sqlite
     /var/lib/yum/history/history-2015-12-23.sqlite-journal
     /var/lib/rpm/Packages
     /var/lib/rpm/Group
     /var/lib/rpm/Name
     /var/lib/rpm/Sha1header
     # content omitted for space

In these results, we can observe all three potential groups or categories. In the output of Files only in centos_with_less, we can see evidence of the less RPM by the groff-related RPM dependency. In the Files only in centos_with_wget output, we see the wget binary itself. And in the Common files that are different category, we can see that both docker objects have a file called /var/lib/Packages but they differ, which makes good sense since that file is altered by yum/rpm when a package is added or removed.

I also mentioned earlier that atomic diff can differentiate between two RPM-based docker objects. This can be done by adding the -r or --rpms switch to the atomic diff command. In the next example, I will perform an atomic diff passing -r to evaluate the RPMs and -n, which tells atomic diff to not evaluate the files (as we did above).

$ sudo atomic diff -r -n centos_with_less centos_with_wget

centos_with_less                  | centos_with_wget
--------------------------------- | ---------------------------------
CentOS Linux release 7.2.1511 (   | CentOS Linux release 7.2.1511 (  
Core)                             | Core)
--------------------------------- | ---------------------------------
groff-base-0-1.22.2               |
less-0-458                        |
                                  | wget-0-1.14

By default, we only show the RPMs that differ. If you pass the -v (verbose) flag, it will show all the common RPMs as well.

One useful switch is the --json switch, which outputs the results in json format. This can be handy if you have a tool that perhaps analyzes the results, because you can pipe the output into your own tooling. An example of the JSON-based output is as follows:

{
    "centos_with_less": {
        "release": "CentOS Linux release 7.2.1511 (Core) \n",
        "all_rpms": [
            "acl-0-2.2.51",
            "audit-libs-0-2.4.1",
            "basesystem-0-10.0",
            # content omitted for space
        ],
        "exclusive_rpms": [
            "groff-base-0-1.22.2",
            "less-0-458"
        ],
        "common_rpms": [
            "acl-0-2.2.51",
            "audit-libs-0-2.4.1",
            "basesystem-0-10.0",
            # content omitted for space
        ]
    },
    "centos_with_wget": {
        "release": "CentOS Linux release 7.2.1511 (Core) \n",
        "all_rpms": [
            "acl-0-2.2.51",
            "audit-libs-0-2.4.1",
            "basesystem-0-10.0",
            # content omitted for space
        ],
        "exclusive_rpms": [
            "wget-0-1.14"
        ],
        "common_rpms": [
            "acl-0-2.2.51",
            "audit-libs-0-2.4.1",
            "basesystem-0-10.0",
            # content omitted for space
        ]
    }
}

The next time you need to perform some analysis between two docker objects, remember that atomic diff exists and can do some of the heavy lifting for you. It works on comparing images to images, images to containers, and containers to containers.