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.