Run stack setup first
1 file changed
tree: 27a4079453ae38fef1b1f743ceec79df6641e491
  1. .gitignore
  2. .travis.yml
  3. Dockerfile
  6. Setup.hs
  7. app/
  8. appveyor.yml
  9. hadolint.cabal
  11. screenshot.png
  12. src/
  13. stack.yaml
  14. test/

Haskell Dockerfile Linter Linux/OSX Build Status Windows Build status GPL-3 licensed

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


How to use

You can run hadolint locally to lint your Dockerfile.

hadolint <Dockerfile>
hadolint --ignore DL3003 --ignore DL3006 <Dockerfile> # exclude specific rules

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 lukasmartinelli/hadolint < Dockerfile


You can download prebuilt binaries for OSX, Windows and Linux from the latest release page. However they may not run on your system configuration since I am not able to provide completly staticly linked binaries. Fall back to brew, source installation or Docker if it doesn't work for you.

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

brew install hadolint

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

git clone
cd hadolint
stack build


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 into 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.
DL3002Do not switch to root USER.
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 upgrade or dist-upgrade.
DL3007Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag.
DL3006Always tag the version of an image explicitly.
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.
DL3012Provide an email adress or URL as maintainer.
DL3013Pin versions in pip.
DL3014Use the -y switch.
DL3015Avoid additional packages by specifying --no-install-recommends.
DL3020Use COPY instead of ADD for files and folders.
DL4000Specify a maintainer of the Dockerfile.
DL4001Either use Wget or Curl but not both.
DL4003Multiple CMD instructions found.
DL4004Multiple ENTRYPOINT instructions found.
DL4005Use SHELL to change the default shell.
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 [/[[.
SC1098Quote/escape special characters when using eval, e.g. eval "a=(b)".
SC1099You need a space before the #.
SC2002Useless cat. Consider `cmd < file
SC2015Note that `A && B
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.
SC2046Quote this to prevent word splitting
SC2086Double quote to prevent globbing and word splitting.
SC2140Word is on the form "A"B"C" (B indicated). Did you mean "ABC" or "A\"B\"C"?
SC2154var is referenced but not assigned.
SC2164Use `cd ...


This is my first Haskell program. If you are a experienced Haskeller I would be really thankful if you would tear my code apart in a review.


  1. Clone repository
    git clone --recursive
  2. Install the dependencies
    stack install


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

# start the repl
stack repl
# parse instruction and look at AST representation
parseString "FROM debian:jessie"


Run unit tests.

stack test

Run integration tests.



The Dockerfile is parsed using Parsec and is using the lexer Lexer.hs and parser Parser.hs.


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