Unit tests for Docker images

Unit testing Docker images with Google Container Structure Tests


When engineering a new Docker image, it can be difficult to ensure the Dockerfile instructions are accurate and working as we intended. Instead of discovering errors/bugs at runtime, writing tests will help you to catch these ones during the development phase.

To help maintainers write unit tests for their Docker images, the Google's Container Tools team has released a framework which provides a simple way to test the structure and content of a Docker image. Mostly written in Go, it is pretty mature since Google's teams have been using their framework for more than a year.

This tutorial has been written for the 1.3.0 version

Types of unit tests for Docker images

By providing 4 types of unit tests, this framework will help you to ensure that the required content/commands are available at runtime when shipping the Docker image:

  • command tests (run the specified command inside the container and verify the correct execution)
  • file existence tests (check the existence of a specified file inside the container)
  • file content tests (check the content of a specified file)
  • metadata tests (check the container configuration: environment variables, volumes, entrypoints, ports, etc.).

Running tests

You can either run the tests using the binary (Linux) or the Docker image (Windows).

The framework runs the specified tests from a given .yaml or .json file. The available tests types are listed in the next section.

Using the Google Container Structure test binary (Linux)

Simply download the latest binary here. The usage is straightforward:

./container-structure-test-linux-amd64 test --image sample-docker-image sample_test_config.yaml

Using a Docker image

As the Google Container Structure Tests binary is only compatible with Linux, I have developed a Docker image which make easy to run Docker tests in a Windows environment:

docker run --rm -v "<path-to-tests-config-file>:/test-config/tests_config.yaml" \
  -v /var/run/docker.sock:/var/run/docker.sock flopes/container-structure-test-docker "test --image <image-to-test> --config tests_config.yaml"

Testing Dockerfile instructions

As said before, the test configurations listed below have to be placed in a .yaml or .json file.

COPY, ADD

FROM alpine:3.7
ADD entrypoint.sh /entrypoint.sh

To test the Docker ADD/COPY instruction, you can use the following test configuration:

schemaVersion: '2.0.0'
fileExistenceTests:
- name: 'entrypoint'
  path: '/entrypoint.sh'
  shouldExist: true
  permissions: '-rwxr-xr-x'

This section will ensure that the specified file exists at the specified location (path) with the correct permissions. Note the possibility to test the nonexistence of a file.

To enhance this test, you can also add a fileContentTests section:

schemaVersion: '2.0.0'
fileContentTests:
- name: 'entrypoint'
  path: '/entrypoint.sh'
  expectedContents: ['echo']

Note that you can also use the excludedContents field to ensure that the specified file does NOT contain the given content.

RUN

To test the RUN instruction, you can either use a fileExistenceTests or fileContentTests, if you download or create a file using this instruction.
However, when using the RUN instruction to install packages or binaries, the commandTests will be relevant:

FROM alpine:3.7

RUN apk add --update curl

The corresponding test in the .yaml file:

schemaVersion: '2.0.0'
commandTests:
  - name: "curl package installation"
    setup: [["/entrypoint.sh"]]
    command: "which"
    args: ["curl"]
    expectedOutput: ["/usr/bin/curl"]

Testing Image metadatas

The metadataTest section will check the following container instructions:

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

To show its usage, let's test the following Dockerfile:

FROM alpine:3.7

LABEL MAINTAINER="Florian Lopes"

ENV PROFILE DEV

ADD entrypoint.sh /entrypoint.sh

VOLUME /volume

WORKDIR /

EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]

CMD ["--help"]
docker build -t metadata .

The test configuration:

schemaVersion: '2.0.0'
metadataTest:
  env:
  - key: 'PROFILE'
    value: 'DEV'
  labels:
  - key: 'MAINTAINER'
    value: 'Florian Lopes'
  volumes: ['/volume']
  workdir: ['/']
  exposedPorts: ['80']
  entrypoint: ['/entrypoint.sh']
  cmd: ['--help']

Run the tests:

 ./container-structure-test-linux-amd64 test --image test --config test_config.yaml
=========================================
====== Test file: tests_config.yml ======
=========================================

=== RUN: Metadata Test
--- PASS

=========================================
================ RESULTS ================
=========================================
Passes:      1
Failures:    0
Total tests: 1

PASS

Advanced usage

Setup/teardown commands

Setup command

Sometimes, an image needs an ENTRYPOINT instruction in order to properly initialize the container. As the Google Container Structure Tests framework works by overriding container entrypoint (see here), the defined ENTRYPOINT in the Dockerfile will not be honored.
To overcome this limitation, you can use the setup field to run an entrypoint script.

For example, let's say we want to install the curl package at the container startup:

FROM alpine:3.7

COPY entrypoint.sh /entrypoint.sh

RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
apk add --update curl

To ensure the /entrypoint.sh script is executed, we provide the setup field with the /entrypoint.sh script.

schemaVersion: '2.0.0'
commandTests:
  - name: "curl package installation"
    setup: [["/entrypoint.sh"]]
    command: "which"
    args: ["curl"]
    expectedOutput: ["/usr/bin/curl"]
Teardown command

As the setup field, the teardown one can be used to execute commands after the actual test command.

schemaVersion: '2.0.0'
commandTests:
  - name: "curl package installation"
    teardown: [["/entrypoint.sh"]]
    command: "which"
    args: ["curl"]
    expectedOutput: ["/usr/bin/curl"]

Samples

You can see a full sample for the spring-boot-docker image or the container-structure-test Docker image itself.

Automating Docker images tests

Automating Docker image tests is easy, simply provide your CI environment with the container-structure-test binary or the Docker image.

An example demonstrating Travis CI integration is available here.