Misconfigured Docker containers can be slow, heavy and difficult to maintain. This leads to sluggish releases, high storage costs and frustrated developers. Moreover, inadequate Docker containers can be a major security liability. A recent report by Aqua Security found that 50% of new Docker instances are attacked within 56 minutes of being deployed. Docker problems can be devastating for any business and software teams need to put in the time and energy to alleviate these risks.
Introduction to hadolint
Hadolint comes with a robust and easy to use CLI. You can install it on a variety of platforms, including macOS using
brew install hadolint.
Confirm the installation was successful with the following command:
We’ll use the following
Dockerfile as an example, which can be used to run a Python Django web server. On the surface, it looks fine but we’ll see it has a lot of problems.
Let’s run it through Hadolint:
Every violation takes on the following structure:
Let’s dive into these parameters in more detail.
A rule code is prefixed with either
DL prefix means the rule comes from Hadolint directly. The
SC prefix means the rule comes from SpellCheck which is a static analysis tool for shell scripts that comes with Hadolint out of the box. You can find the combined list of rules here.
Every rule has a dedicated documentation page that lists code examples, rationale and other important details. See the dedicated page for
You can ignore one or more rules using the
--ignore RULECODE option:
You can also ignore rules within the
Dockerfile inline. I prefer this approach because you can exclude rule codes on a per-line basis and it’s more clear where the violation is actually happening.
Hadolint has an active open-source community. New rule codes are added on a regular basis so be sure to check you’re running the latest version of Hadolint every so often.
The severity level indicates how critical a violation is. There are six levels: error, warning, info, style, ignore, and none.
The CLI includes a
--failure-threshold (abbreviated as
-t) to exclude certain severity levels from causing a failure. For example, if you only want Hadolint to fail on
Note, violations from other severity levels will still be reported but they won’t cause a failure.
If you don’t agree with a rule code’s severity level, you can easily change it using the
--<SEVERITY_LEVEL> RULECODE option. For example, the following command upgrades
error and downgrades
info (both codes are
warning by default):
Dockerfile labels are an excellent tool for annotating your Docker images. Hadolint comes with some validation options for ensuring your labels are set correctly.
--require-label LABELSCHEMA option verifies that your labels follow a specific format. You can see all acceptable format values here.
--strict-labels option verifies there are no extra labels outside of the ones defined in your schema.
Manually passing options into every Hadolint run can be annoying and error-prone. Hadolint conveniently comes with configuration file support for storing all of your options in a single place. This file can live in a variety of locations but I generally just put it in the repository’s root as
Fix the dockerfile
Working through each error one-by-one is a fantastic exercise for learning about Dockerfile best practices. As mentioned above, every rule has a very clear and detailed documentation page. Give it a shot and revisit this post when you’re done.
At this point, Hadolint should report no errors. Your file should look similar to this:
A few changes that need further explanation:
- We’re tagging the
pythonbase image with the latest available Python minor version, which is currently
3.10. We’re not including the patch version (
3.10.2) because Python patch versions are backwards compatible and generally include useful bug fixes.
- I generally like to use the
/appworking directory to keep my Docker images consistent but you can use any new or existing directory you want.
- We’re ignoring
DL3013because we want to download the latest version of
pip. There’s no need to pin it to a specific version.
Hadolint includes many convenient integrations for automatically running the linter throughout the development process. My favorites are:
- VS Code: run Hadolint directly in your editor
- pre-commit: run Hadolint on every git commit
- GitHub Actions: run Hadolint in GitHub CI/CD
Integrations are crucial, especially in larger teams because some developers will forget to run the linter manually. I set them up immediately when I start a new Docker project.
Hadolint is a terrific tool for building best practice Docker images. It gives you the peace of mind that your containers running in the cloud are small, fast and free of any major security vulnerabilities. Hook it into your development workflow and see what improvements you can make to your Dockerfiles.
If you're looking for an easier way to run your Docker-image-based tasks, consider using Airplane. With Airplane, you can build and run any image and run Docker commands against that image. This makes it easy to take your existing Docker images and deploy them onto Airplane's serverless platform that anyone on your team can use.