bump version to 2.2.0
2 files changed
tree: 22faee5ced1daebd7158ae0944d9bc725991ab57
  1. .dockerignore
  2. .github/
  3. .gitignore
  4. .hindent.yaml
  5. .pre-commit-hooks.yaml
  6. .remarkrc.yaml
  8. README.md
  9. Setup.hs
  10. app/
  11. docker/
  12. docs/
  13. hadolint.cabal
  14. hie.yaml
  15. integration_test.sh
  16. package.yaml
  17. screenshot.png
  18. scripts/
  19. src/
  20. stack.yaml
  21. test/

Build Status GPL-3 licensed GitHub release Github downloads

Haskell Dockerfile Linter

A smarter Dockerfile linter that helps you build best practice Docker images. The linter is parsing the Dockerfile into an AST and performs rules on top of the AST. It is standing on the shoulders of ShellCheck to lint the Bash code inside RUN instructions.

:globe_with_meridians: Check the online version on hadolint.github.io/hadolint Screenshot

How to use

You can run hadolint locally to lint your Dockerfile.

hadolint <Dockerfile>
hadolint --ignore DL3003 --ignore DL3006 <Dockerfile> # exclude specific rules
hadolint --trusted-registry my-company.com:500 <Dockerfile> # Warn when using untrusted FROM images

Docker comes to the rescue to provide an easy way how to run hadolint on most platforms. Just pipe your Dockerfile to docker run:

$ docker run --rm -i hadolint/hadolint < Dockerfile
# or
$ docker run --rm -i ghcr.io/hadolint/hadolint < Dockerfile

or if you are using Windows PowerShell:

> cat .\Dockerfile | docker run --rm -i hadolint/hadolint


You can download prebuilt binaries for OSX, Windows and Linux from the latest release page. However, if it doesn't work for you, please fall back to Docker, brew or source installation.

If you are on OSX you can use brew to install hadolint.

brew install hadolint

On Windows you can use scoop to install hadolint.

scoop install hadolint

As shown before, hadolint is available as a Docker container:

$ docker pull hadolint/hadolint
# or
$ docker pull ghcr.io/hadolint/hadolint

If you need a Docker container with shell access, use the Debian or Alpine variants of the Docker image:

$ docker pull hadolint/hadolint:latest-debian
$ docker pull hadolint/hadolint:latest-alpine
# or
$ docker pull ghcr.io/hadolint/hadolint:latest-debian
$ docker pull ghcr.io/hadolint/hadolint:latest-alpine

You can also build hadolint locally. You need Haskell and the stack build tool to build the binary.

git clone https://github.com/hadolint/hadolint
cd hadolint
stack install


hadolint supports specifying the ignored rules using a configuration file. The configuration file should be in yaml format. This is one valid configuration file as an example:

  - DL3000
  - SC1010

Additionally, hadolint can warn you when images from untrusted repositories are being used in Dockerfiles, you can append the trustedRegistries keys to the configuration file as shown below:

  - DL3000
  - SC1010

  - docker.io
  - my-company.com:5000

If you want to override the severity of specific rules, you can do that too:

    - DL3001
    - DL3002
    - DL3042
    - DL3033
    - DL3032
    - DL3015

Configuration files can be used globally or per project. By default, hadolint will look for a configuration file in the current directory with the name .hadolint.yaml

The global configuration file should be placed in the folder specified by XDG_CONFIG_HOME, with the name hadolint.yaml. In summary, the following locations are valid for the configuration file, in order or preference:

  • $PWD/.hadolint.yaml
  • $XDG_CONFIG_HOME/hadolint.yaml
  • ~/.config/hadolint.yaml

In windows, the %LOCALAPPDATA% environment variable is used instead of XDG_CONFIG_HOME

Additionally, you can pass a custom configuration file in the command line with the --config option

hadolint --config /path/to/config.yaml Dockerfile

To pass a custom configuration file (using relative or absolute path) to a container, use the following command:

$ docker run --rm -i -v ./your/path/to/hadolint.yaml:/root/.config/hadolint.yaml hadolint/hadolint < Dockerfile
# or
$ docker run --rm -i -v ./your/path/to/hadolint.yaml:/root/.config/hadolint.yaml ghcr.io/hadolint/hadolint < Dockerfile

Inline ignores

It is also possible to ignore rules by using a special comment directly above the Dockerfile instruction you want to make an exception for. Ignore rule comments look like # hadolint ignore=DL3001,SC1081. For example:

# hadolint ignore=DL3006
FROM ubuntu

# hadolint ignore=DL3003,SC1035
RUN cd /tmp && echo "hello!"

Inline ignores will only work if place directly above the instruction.

Linting Labels

Hadolint has the ability to check that specific labels be present and conform to a predefined label schema. First a label schema must be defined either via commandline:

$ hadolint --require-label author:text --require-label version:semver Dockerfile

or via config file:

  author: text
  contact: email
  created: rfc3339
  version: semver
  documentation: url
  git-revision: hash
  license: spdx

The value of a label can be either of text, url, semver, hash or rfc3339: | Schema | Description | |:--------|:---------------------------------------------------| | text | Anything | | rfc3339 | A time, formatted according to RFC 3339 | | semver | A semantic version | | url | A URI as described in RFC 3986 | | hash | Either a short or a long Git hash | | spdx | An SPDX license identifier | | email | An email address conforming to RFC 5322 |

By default, Hadolint ignores any label not specified in the label schema. To warn on such additional labels, turn on strict labels:

$ hadolint --strict-labels --require-label version:semver Dockerfile

or in the config file:

strict-labels: true

When strict labels is enabled, but no label schema has been specified, Hadolint will warn if any label is present.

Note on dealing with variables in labels

It is a common pattern to fill the value of a label not statically, but rather dynamically at build time by using a variable:

FROM debian:buster
ARG VERSION="du-jour"
LABEL version="${VERSION}"

To allow this, the label schema must specify text as value for that label:

  version: text


To get most of hadolint it is useful to integrate it as a check to your CI or to your editor, or as a pre-commit hook, to lint your Dockerfile as you write it. See our Integration docs.


An incomplete list of implemented rules. Click on the error code to get more detailed information.

  • Rules with the prefix DL originate from hadolint. Take a look at Rules.hs to find the implementation of the rules.

  • Rules with the SC prefix originate from ShellCheck (Only the most common rules are listed, there are dozens more)

Please create an issue if you have an idea for a good rule.

DL3000Use absolute WORKDIR.
DL3001For some bash commands it makes no sense running them in a Docker container like ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig.
DL3002Last user should not be root.
DL3003Use WORKDIR to switch to a directory.
DL3004Do not use sudo as it leads to unpredictable behavior. Use a tool like gosu to enforce root.
DL3005Do not use apt-get dist-upgrade.
DL3006Always tag the version of an image explicitly.
DL3007Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag.
DL3008Pin versions in apt-get install.
DL3009Delete the apt-get lists after installing something.
DL3010Use ADD for extracting archives into an image.
DL3011Valid UNIX ports range from 0 to 65535.
DL3012Multiple HEALTHCHECK instructions.
DL3013Pin versions in pip.
DL3014Use the -y switch.
DL3015Avoid additional packages by specifying --no-install-recommends.
DL3016Pin versions in npm.
DL3017Do not use apk upgrade.
DL3018Pin versions in apk add. Instead of apk add <package> use apk add <package>=<version>.
DL3019Use the --no-cache switch to avoid the need to use --update and remove /var/cache/apk/* when done installing packages.
DL3020Use COPY instead of ADD for files and folders.
DL3021COPY with more than 2 arguments requires the last argument to end with /
DL3022COPY --from should reference a previously defined FROM alias
DL3023COPY --from cannot reference its own FROM alias
DL3024FROM aliases (stage names) must be unique
DL3025Use arguments JSON notation for CMD and ENTRYPOINT arguments
DL3026Use only an allowed registry in the FROM image
DL3027Do not use apt as it is meant to be a end-user tool, use apt-get or apt-cache instead
DL3028Pin versions in gem install. Instead of gem install <gem> use gem install <gem>:<version>
DL3029Do not use --platform flag with FROM.
DL3030Use the -y switch to avoid manual input yum install -y <package>
DL3031Do not use yum update
DL3032yum clean all missing after yum command.
DL3033Specify version with yum install -y <package>-<version>
DL3034Non-interactive switch missing from zypper command: zypper install -y
DL3035Do not use zypper update.
DL3036zypper clean missing after zypper use.
DL3037Specify version with zypper install -y <package>[=]<version>.
DL3038Use the -y switch to avoid manual input dnf install -y <package>
DL3039Do not use dnf update
DL3040dnf clean all missing after dnf command.
DL3041Specify version with dnf install -y <package>-<version>
DL3042Avoid cache directory with pip install --no-cache-dir <package>.
DL3043ONBUILD, FROM or MAINTAINER triggered from within ONBUILD instruction.
DL3044Do not refer to an environment variable within the same ENV statement where it is defined.
DL3045COPY to a relative destination without WORKDIR set.
DL3046useradd without flag -l and high UID will result in excessively large Image.
DL3047wget without flag --progress will result in excessively bloated build logs when downloading larger files.
DL3048Invalid Label Key
DL3049Label <label> is missing.
DL3050Superfluous label(s) present.
DL3051Label <label> is empty.
DL3052Label <label> is not a valid URL.
DL3053Label <label> is not a valid time format - must be conform to RFC3339.
DL3054Label <label> is not a valid SPDX license identifier.
DL3055Label <label> is not a valid git hash.
DL3056Label <label> does not conform to semantic versioning.
DL3057HEALTHCHECK instruction missing.
DL3058Label <label> is not a valid email format - must be conform to RFC5322.
DL3059Multiple consecutive RUN instructions. Consider consolidation.
DL3060yarn cache clean missing after yarn install was run.
DL4000MAINTAINER is deprecated.
DL4001Either use Wget or Curl but not both.
DL4003Multiple CMD instructions found.
DL4004Multiple ENTRYPOINT instructions found.
DL4005Use SHELL to change the default shell.
DL4006Set the SHELL option -o pipefail before RUN with a pipe in it
SC1000$ is not used specially and should therefore be escaped.
SC1001This \c will be a regular 'c' in this context.
SC1007Remove space after = if trying to assign a value (or for empty string, use var='' ...).
SC1010Use semicolon or linefeed before done (or quote to make it literal).
SC1018This is a unicode non-breaking space. Delete it and retype as space.
SC1035You need a space here
SC1045It's not foo &; bar, just foo & bar.
SC1065Trying to declare parameters? Don't. Use () and refer to params as $1, $2 etc.
SC1066Don't use $ on the left side of assignments.
SC1068Don't put spaces around the = in assignments.
SC1077For command expansion, the tick should slant left (` vs ยด).
SC1078Did you forget to close this double-quoted string?
SC1079This is actually an end quote, but due to next char, it looks suspect.
SC1081Scripts are case sensitive. Use if, not If.
SC1083This {/} is literal. Check expression (missing ;/\n?) or quote it.
SC1086Don't use $ on the iterator name in for loops.
SC1087Braces are required when expanding arrays, as in ${array[idx]}.
SC1095You need a space or linefeed between the function name and body.
SC1097Unexpected ==. For assignment, use =. For comparison, use [ .. ] or [[ .. ]].
SC1098Quote/escape special characters when using eval, e.g. eval "a=(b)".
SC1099You need a space before the #.
SC2002Useless cat. Consider cmd < file | .. or cmd file | .. instead.
SC2015Note that A && B || C is not if-then-else. C may run when A is true.
SC2026This word is outside of quotes. Did you intend to ‘nest ‘“‘single quotes’”’ instead’?
SC2028echo won't expand escape sequences. Consider printf.
SC2035Use ./*glob* or -- *glob* so names with dashes won't become options.
SC2039In POSIX sh, something is undefined.
SC2046Quote this to prevent word splitting
SC2086Double quote to prevent globbing and word splitting.
SC2140Word is in the form "A"B"C" (B indicated). Did you mean "ABC" or "A\"B\"C"?
SC2154var is referenced but not assigned.
SC2155Declare and assign separately to avoid masking return values.
SC2164Use cd ... || exit in case cd fails.


If you are an experienced Haskeller we would be really thankful if you would tear our code apart in a review.


  1. Clone repository

    git clone --recursive git@github.com:hadolint/hadolint.git
  2. Install the dependencies

    stack install


The easiest way to try out the parser is using the REPL.

# start the repl
stack repl
# overload strings to be able to use Text
:set -XOverloadedStrings
# import parser library
import Language.Docker
# parse instruction and look at AST representation
parseText "FROM debian:jessie"


Run unit tests.

stack test

Run integration tests.



Dockerfile syntax is fully described in the Dockerfile reference. Just take a look at Syntax.hs in the language-docker project to see the AST definition.