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.
Hadolint is a Dockerfile linter that helps you build best practice Docker images. I use it in all of my projects to ensure I’m creating small, secure, efficient and maintainable images.
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.
Rule code
A rule code is prefixed with either DL
or SC
. The 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 DL3006
here.
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.
Severity level
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 error
violations.
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 DL3006
to error
and downgrades DL3045
to info
(both codes are warning
by default):
Label linting
Dockerfile labels are an excellent tool for annotating your Docker images. Hadolint comes with some validation options for ensuring your labels are set correctly.
The --require-label LABELSCHEMA
option verifies that your labels follow a specific format. You can see all acceptable format values here.
The --strict-labels
option verifies there are no extra labels outside of the ones defined in your schema.
Configuration file
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 .hadolint.yaml
.
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
python
base image with the latest available Python minor version, which is currently3.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
/app
working directory to keep my Docker images consistent but you can use any new or existing directory you want. - We’re ignoring
DL3013
because we want to download the latest version ofpip
. There’s no need to pin it to a specific version.
Integrations
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.
Final thoughts
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.
To try this out yourself, sign up for a free account or book a demo.