Lint your Dockerfile with Hadolint

A code base of high quality that makes an engineer proud cannot do without a proper CI/CD pipeline. Linting, analyzing your code for potential errors and bad coding practices, should be a standard part of your pipeline. Especially interpreted languages such as Python benefit greatly from linting because of the lack of compilation. In this blog post, I check out a Dockerfile linter called Hadolint.

Linting your Dockerfile

Just like programming languages, static files can also be linted. Why would you lint your Dockerfile? See and validate your own Dockerfile online at https://www.fromlatest.io for potential flaws. It checks for optimizations and likely failures, resulting in cleaner code, no errors and possibly a smaller Docker image. There are several Dockerfile linters available, the most popular being Hadolint, short for Haskell Dockerfile linter.

Hadolint is available as a Docker image and you can lint with:

docker run --rm -i hadolint/hadolint < Dockerfile

If your Dockerfile is immaculate, you won't see any output and receive an exit code 0. If your Dockerfile contains flaws such as this one:

1  # ===== Flawed Dockerfile, do not copy =====
2
3  FROM continuumio/miniconda:latest
4  RUN apt-get update \
5   && apt-get install -y \
6      gcc \
7      fortunes \
8      cowsay \
9   && pip install apache-airflow[crypto,postgres]
10
11 CMD /usr/games/fortune | /usr/games/cowsay

This Dockerfile might build and run fine:

$ docker build -t moo .
$ docker run moo

________________________________
/ No problem is insoluble in all \
\ conceivable circumstances.     /
--------------------------------
       \   ^__^
        \  (oo)\_______
           (__)\       )\/\
               ||----w |
               ||     ||

However Hadolint points out several flaws and potential issues which might break in the future, and returns an exit code 1:

/dev/stdin:3 DL3007 Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag
/dev/stdin:4 SC2102 Ranges can only match single chars (mentioned due to duplicates).
/dev/stdin:4 DL3008 Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
/dev/stdin:4 DL3009 Delete the apt-get lists after installing something
/dev/stdin:4 DL3013 Pin versions in pip. Instead of `pip install <package>` use `pip install <package>==<version>`
/dev/stdin:4 DL3015 Avoid additional packages by specifying `--no-install-recommends`
/dev/stdin:11 DL3025 Use arguments JSON notation for CMD and ENTRYPOINT arguments

Each error is represented by a key DL (Hadolint error) or SC (ShellCheck error). Documentation for the most common errors is given on the Hadolint wiki: https://github.com/hadolint/hadolint#rules.

Configuration

A nice feature of Hadolint is the ability pass along configuration. For example, the error SC2102 above is ShellCheck thinking the pip install command of Airflow with additional packages is a range, which in this case does not apply. So, we would like to ignore this error. We can do this is 3 ways:

  • Ignore in entire Dockerfile by passing --ignore flag with docker run:
docker run --rm -i hadolint/hadolint hadolint --ignore SC2102 - < Dockerfile
  • Ignore in entire Dockerfile by mounting configuration yaml file:

.hadolint.yml:

ignored:
  - SC2102
docker run --rm -i -v ${PWD}/.hadolint.yml:/.hadolint.yaml hadolint/hadolint < Dockerfile
  • Ignore specific instructions inline. The exception works for a Dockerfile full instruction, individual lines cannot be excluded.
# ===== Flawed Dockerfile, do not copy =====

FROM continuumio/miniconda:latest

# hadolint ignore=SC2102
RUN apt-get update \
 && apt-get install -y \
    gcc \
    fortunes \
    cowsay \
 && pip install apache-airflow[crypto,postgres]

CMD /usr/games/fortune | /usr/games/cowsay

Integrate in your CI pipeline

To continuously verify changes on your Dockerfiles while you're writing them, integrate the linter in your CI. Linter errors return exit code 1 and will fail your pipeline. E.g. for Bitbucket Pipelines you could define a custom "build-docker" pipeline:

custom:
  build-docker:
    - step:
        name: Dockerlint
        script:
          - docker run --rm -i -v ${PWD}/.hadolint.yml:/.hadolint.yaml hadolint/hadolint:v1.10.3 < Dockerfile
    - step:
        name: Build image
        script:
          - docker build -t myimage .

Other CI pipeline examples are given on the Hadolint integration docs.

That's it for Dockerfile linting, enjoy!

Author
Follow us for more of this
Recent posts
Recent tweets
Stay up to date on the latest insights and best-practices by registering for the GoDataDriven newsletter.
Follow us for more of this