diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 [![Build Status][github-actions-img]][github-actions]
 [![GPL-3 licensed][license-img]][license]
 [![GitHub release][release-img]][release]
-![Github downloads][downloads-img]
+![GitHub downloads][downloads-img]
 <img align="right" alt="pipecat" width="150"
 src="https://hadolint.github.io/hadolint/img/cat_container.png" />
 
@@ -16,6 +16,28 @@
 hadolint.github.io/hadolint**](https://hadolint.github.io/hadolint)
 [![Screenshot](screenshot.png)](https://hadolint.github.io/hadolint)
 
+## Table of Contents
+
+- [How to use](#how-to-use)
+- [Install](#install)
+- [CLI](#cli)
+- [Configure](#configure)
+- [Non-Posix Shells](#non-posix-shells)
+- [Ignoring Rules](#ignoring-rules)
+  - [Inline ignores](#inline-ignores)
+  - [Global ignores](#global-ignores)
+- [Linting Labels](#linting-labels)
+  - [Note on dealing with variables in labels](#note-on-dealing-with-variables-in-labels)
+- [Integrations](#integrations)
+- [Rules](#rules)
+- [Develop](#develop)
+  - [Setup](#setup)
+  - [REPL](#repl)
+  - [Tests](#tests)
+  - [AST](#ast)
+  - [Building against custom libraries](#building-against-custom-libraries)
+- [Alternatives](#alternatives)
+
 ## How to use
 
 You can run `hadolint` locally to lint your Dockerfile.
@@ -94,13 +116,15 @@
 docker pull ghcr.io/hadolint/hadolint:latest-alpine
 ```
 
-You can also build `hadolint` locally. You need [Haskell][] and the [stack][]
+You can also build `hadolint` locally. You need [Haskell][] and the [cabal][]
 build tool to build the binary.
 
 ```bash
 git clone https://github.com/hadolint/hadolint \
-&& cd hadolint \
-&& stack install
+  && cd hadolint \
+  && cabal configure \
+  && cabal build \
+  && cabal install
 ```
 
 If you want the
@@ -181,13 +205,14 @@
 Configuration files can be used globally or per project.
 Hadolint looks for configuration files in the following locations or their
 platform specific equivalents in this order and uses the first one exclusively:
+
 - `$PWD/.hadolint.yaml`
 - `$XDG_CONFIG_HOME/hadolint.yaml`
 - `$HOME/.config/hadolint.yaml`
 - `$HOME/.hadolint/hadolint.yaml or $HOME/hadolint/config.yaml`
 - `$HOME/.hadolint.yaml`
 
-In windows, the `%LOCALAPPDATA%` environment variable is used instead of 
+In windows, the `%LOCALAPPDATA%` environment variable is used instead of
 `XDG_CONFIG_HOME`. Config files can have either `yaml` or `yml` extensions.
 
 `hadolint` full `yaml` config file schema
@@ -262,7 +287,8 @@
 
 ```yaml
 failure-threshold: info
-warning:
+override:
+  warning:
     - DL3042
     - DL3033
   info:
@@ -287,6 +313,7 @@
 
 In addition to config files, Hadolint can be configured with environment
 variables.
+
 ```bash
 NO_COLOR=1                               # Set or unset. See https://no-color.org
 HADOLINT_NOFAIL=1                        # Truthy value e.g. 1, true or yes
@@ -316,8 +343,10 @@
 RUN Get-Process notepad | Stop-Process
 ```
 
-## Inline ignores
+## Ignoring Rules
 
+### Inline ignores
+
 It is also possible to ignore rules by adding a special comment directly
 above the Dockerfile statement for which you want to make an exception for.
 Such comments look like
@@ -333,10 +362,10 @@
 
 The comment "inline ignores" applies only to the statement following it.
 
-## Global ignores
+### Global ignores
 
 Rules can also be ignored on a per-file basis using the global ignore pragma.
-It works just like inline ignores, excep that it applies to the whole file
+It works just like inline ignores, except that it applies to the whole file
 instead of just the next line.
 
 ```dockerfile
@@ -371,6 +400,7 @@
 
 The value of a label can be either of `text`, `url`, `semver`, `hash` or
 `rfc3339`:
+
 | Schema  | Description                                        |
 |:--------|:---------------------------------------------------|
 | text    | Anything                                           |
@@ -431,10 +461,10 @@
 An incomplete list of implemented rules. Click on the error code to get more
 detailed information.
 
--   Rules with the prefix `DL` are from `hadolint`. Have a look at
+- Rules with the prefix `DL` are from `hadolint`. Have a look at
     `Rules.hs` to find the implementation of the rules.
 
--   Rules with the `SC` prefix are from **ShellCheck** (only the most
+- Rules with the `SC` prefix are 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.
@@ -449,7 +479,6 @@
 | [DL3002](https://github.com/hadolint/hadolint/wiki/DL3002)   | Warning          | Last user should not be root.                                                                                                                       |
 | [DL3003](https://github.com/hadolint/hadolint/wiki/DL3003)   | Warning          | Use WORKDIR to switch to a directory.                                                                                                               |
 | [DL3004](https://github.com/hadolint/hadolint/wiki/DL3004)   | Error            | Do not use sudo as it leads to unpredictable behavior. Use a tool like gosu to enforce root.                                                        |
-| [DL3005](https://github.com/hadolint/hadolint/wiki/DL3005)   | Error            | Do not use apt-get dist-upgrade.                                                                                                                    |
 | [DL3006](https://github.com/hadolint/hadolint/wiki/DL3006)   | Warning          | Always tag the version of an image explicitly.                                                                                                      |
 | [DL3007](https://github.com/hadolint/hadolint/wiki/DL3007)   | Warning          | Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag.                                         |
 | [DL3008](https://github.com/hadolint/hadolint/wiki/DL3008)   | Warning          | Pin versions in `apt-get install`.                                                                                                                  |
@@ -470,7 +499,7 @@
 | [DL3024](https://github.com/hadolint/hadolint/wiki/DL3024)   | Error            | `FROM` aliases (stage names) must be unique                                                                                                         |
 | [DL3025](https://github.com/hadolint/hadolint/wiki/DL3025)   | Warning          | Use arguments JSON notation for CMD and ENTRYPOINT arguments                                                                                        |
 | [DL3026](https://github.com/hadolint/hadolint/wiki/DL3026)   | Error            | Use only an allowed registry in the `FROM image`                                                                                                    |
-| [DL3027](https://github.com/hadolint/hadolint/wiki/DL3027)   | Warning          | Do not use `apt` as it is meant to be a end-user tool, use `apt-get` or `apt-cache` instead                                                         |
+| [DL3027](https://github.com/hadolint/hadolint/wiki/DL3027)   | Warning          | Do not use `apt` as it is meant to be an end-user tool, use `apt-get` or `apt-cache` instead                                                        |
 | [DL3028](https://github.com/hadolint/hadolint/wiki/DL3028)   | Warning          | Pin versions in gem install. Instead of `gem install <gem>` use `gem install <gem>:<version>`                                                       |
 | [DL3029](https://github.com/hadolint/hadolint/wiki/DL3029)   | Warning          | Do not use --platform flag with FROM.                                                                                                               |
 | [DL3030](https://github.com/hadolint/hadolint/wiki/DL3030)   | Warning          | Use the `-y` switch to avoid manual input `yum install -y <package>`                                                                                |
@@ -494,12 +523,12 @@
 | [DL3050](https://github.com/hadolint/hadolint/wiki/DL3050)   | Info             | Superfluous label(s) present.                                                                                                                       |
 | [DL3051](https://github.com/hadolint/hadolint/wiki/DL3051)   | Warning          | Label `<label>` is empty.                                                                                                                           |
 | [DL3052](https://github.com/hadolint/hadolint/wiki/DL3052)   | Warning          | Label `<label>` is not a valid URL.                                                                                                                 |
-| [DL3053](https://github.com/hadolint/hadolint/wiki/DL3053)   | Warning          | Label `<label>` is not a valid time format - must be conform to RFC3339.                                                                            |
+| [DL3053](https://github.com/hadolint/hadolint/wiki/DL3053)   | Warning          | Label `<label>` is not a valid time format - must conform to RFC3339.                                                                               |
 | [DL3054](https://github.com/hadolint/hadolint/wiki/DL3054)   | Warning          | Label `<label>` is not a valid SPDX license identifier.                                                                                             |
 | [DL3055](https://github.com/hadolint/hadolint/wiki/DL3055)   | Warning          | Label `<label>` is not a valid git hash.                                                                                                            |
 | [DL3056](https://github.com/hadolint/hadolint/wiki/DL3056)   | Warning          | Label `<label>` does not conform to semantic versioning.                                                                                            |
 | [DL3057](https://github.com/hadolint/hadolint/wiki/DL3057)   | Ignore           | `HEALTHCHECK` instruction missing.                                                                                                                  |
-| [DL3058](https://github.com/hadolint/hadolint/wiki/DL3058)   | Warning          | Label `<label>` is not a valid email format - must be conform to RFC5322.                                                                           |
+| [DL3058](https://github.com/hadolint/hadolint/wiki/DL3058)   | Warning          | Label `<label>` is not a valid email format - must conform to RFC5322.                                                                              |
 | [DL3059](https://github.com/hadolint/hadolint/wiki/DL3059)   | Info             | Multiple consecutive `RUN` instructions. Consider consolidation.                                                                                    |
 | [DL3060](https://github.com/hadolint/hadolint/wiki/DL3060)   | Info             | `yarn cache clean` missing after `yarn install` was run.                                                                                            |
 | [DL3061](https://github.com/hadolint/hadolint/wiki/DL3061)   | Error            | Invalid instruction order. Dockerfile must begin with `FROM`, `ARG` or comment.                                                                     |
@@ -526,15 +555,16 @@
 | [SC1083](https://github.com/koalaman/shellcheck/wiki/SC1083) |                  | This `{/}` is literal. Check expression (missing `;/\n`?) or quote it.                                                                              |
 | [SC1086](https://github.com/koalaman/shellcheck/wiki/SC1086) |                  | Don't use `$` on the iterator name in for loops.                                                                                                    |
 | [SC1087](https://github.com/koalaman/shellcheck/wiki/SC1087) |                  | Braces are required when expanding arrays, as in `${array[idx]}`.                                                                                   |
+| [SC1091](https://github.com/koalaman/shellcheck/wiki/SC1091) |                  | Not following: Reasons include: file not found, no permissions, not included on the command line, not allowing shellcheck to follow files with -x, etc.                                                                                    |
 | [SC1095](https://github.com/koalaman/shellcheck/wiki/SC1095) |                  | You need a space or linefeed between the function name and body.                                                                                    |
 | [SC1097](https://github.com/koalaman/shellcheck/wiki/SC1097) |                  | Unexpected `==`. For assignment, use `=`. For comparison, use `[ .. ]` or `[[ .. ]]`.                                                               |
 | [SC1098](https://github.com/koalaman/shellcheck/wiki/SC1098) |                  | Quote/escape special characters when using `eval`, e.g. `eval "a=(b)"`.                                                                             |
-| [SC1099](https://github.com/koalaman/shellcheck/wiki/SC1099) |                  | You need a space before the `#`.                                                                                                                    |
+| [SC1099](https://github.com/koalaman/shellcheck/wiki/SC1099) |                   | You need a space before the `#`.                                                                                                                    |
 | [SC2002](https://github.com/koalaman/shellcheck/wiki/SC2002) |                  | Useless cat. Consider <code>cmd < file &#124; ..</code> or <code>cmd file &#124; ..</code> instead.                                                 |
 | [SC2015](https://github.com/koalaman/shellcheck/wiki/SC2015) |                  | Note that <code>A && B &#124;&#124; C</code> is not if-then-else. C may run when A is true.                                                         |
 | [SC2026](https://github.com/koalaman/shellcheck/wiki/SC2026) |                  | This word is outside of quotes. Did you intend to 'nest '"'single quotes'"' instead'?                                                               |
 | [SC2028](https://github.com/koalaman/shellcheck/wiki/SC2028) |                  | `echo` won't expand escape sequences. Consider `printf`.                                                                                            |
-| [SC2035](https://github.com/koalaman/shellcheck/wiki/SC2035) |                  | Use `./*glob*` or `-- *glob*` so names with dashes won't become options.                                                                            |
+| [SC2035](https://github.com/koalaman/shellcheck/wiki/SC2035) |                  | Use `./*glob*` or `-- *glob*` so names with dashes won't become options.                                                                             |
 | [SC2039](https://github.com/koalaman/shellcheck/wiki/SC2039) |                  | In POSIX sh, something is undefined.                                                                                                                |
 | [SC2046](https://github.com/koalaman/shellcheck/wiki/SC2046) |                  | Quote this to prevent word splitting                                                                                                                |
 | [SC2086](https://github.com/koalaman/shellcheck/wiki/SC2086) |                  | Double quote to prevent globbing and word splitting.                                                                                                |
@@ -550,27 +580,36 @@
 If you are an experienced Haskeller, we would be very grateful if you would
 tear our code apart in a review.
 
+To compile, you will need a recent Haskell environment and `cabal-install`.
+
 ### Setup
 
-1.  Clone repository
+1. Clone repository
 
     ```bash
     git clone --recursive git@github.com:hadolint/hadolint.git
     ```
 
-1.  Install the dependencies
+1. Install dependencies and compile source
 
     ```bash
-    stack install
+    cabal configure
+    cabal build
     ```
 
+1. (Optional) Install Hadolint on your system
+
+    ```bash
+    cabal install
+    ```
+
 ### REPL
 
 The easiest way to try out the parser is using the REPL.
 
 ```bash
 # start the repl
-stack repl
+cabal repl
 # overload strings to be able to use Text
 :set -XOverloadedStrings
 # import parser library
@@ -581,10 +620,12 @@
 
 ### Tests
 
-Run unit tests:
+Compile with unit tests and run them:
 
 ```bash
-stack test
+cabal configure --enable-tests
+cabal build --enable-tests
+cabal test
 ```
 
 Run integration tests:
@@ -605,10 +646,11 @@
 language-docker is used to parse Dockerfiles and produce an AST which then can
 be analyzed. To build Hadolint against a custom version of such libraries, do
 the following. This example uses language-docker, but it would work with any
-other libaray as well.
+other library as well.
 
  1) In the same directory (e.g. `/home/user/repos`) clone Hadolint and
     language-docker git repositories
+
 ```bash
 cd /home/user/repos
 git clone https://github.com/hadolint/hadolint.git
@@ -617,34 +659,39 @@
 
  2) Make your modifications to language-docker
 
- 3) In the Hadolint repo, edit the `stack.yaml` file, such that the `extra-deps`
-    property points to the other repo
+ 3) In the Hadolint repo, edit the `cabal.project` file, such that the
+    `packages` property points to the other repo too
+
 ```yaml
 [...]
-extra-deps:
-  - ../language-docker
+packages:
+  .
+  ../language-docker
 [...]
 ```
 
  4) Recompile Hadolint and run the tests
+
 ```bash
 cd /home/user/repos/hadolint
-stack test
+cabal configure --enable-tests
+cabal build --enable-tests
+cabal test
 ```
 
 ## Alternatives
 
--   replicatedhq/[dockerfilelint](https://github.com/replicatedhq/dockerfilelint),
+- replicatedhq/[dockerfilelint](https://github.com/replicatedhq/dockerfilelint),
     the other linter used by the [super-linter](https://github.com/github/super-linter/blob/main/README.md#supported-linters)
 
--   RedCoolBeans/[dockerlint](https://github.com/RedCoolBeans/dockerlint/)
+- RedCoolBeans/[dockerlint](https://github.com/RedCoolBeans/dockerlint/)
 
--   projectatomic/[dockerfile_lint](https://github.com/projectatomic/dockerfile_lint/)
+- projectatomic/[dockerfile_lint](https://github.com/projectatomic/dockerfile_lint/)
 
 <!-- References -->
 
-[github-actions-img]: https://github.com/hadolint/hadolint/workflows/Haskell%20Tests/badge.svg?branch=master
-[github-actions]: https://travis-ci.org/hadolint/hadolint/actions
+[github-actions-img]: https://github.com/hadolint/hadolint/actions/workflows/haskell.yml/badge.svg?branch=master
+[github-actions]: https://github.com/hadolint/hadolint/actions/workflows/haskell.yml
 [license-img]: https://img.shields.io/badge/license-GPL--3-blue.svg
 [license]: https://tldrlegal.com/l/gpl-3.0
 [release-img]: https://img.shields.io/github/release/hadolint/hadolint.svg
@@ -654,7 +701,7 @@
 [shellcheck]: https://github.com/koalaman/shellcheck
 [release page]: https://github.com/hadolint/hadolint/releases/latest
 [haskell]: https://www.haskell.org/downloads/
-[stack]: http://docs.haskellstack.org/en/stable/install_and_upgrade.html
+[cabal]: https://www.haskell.org/cabal/
 [integration]: docs/INTEGRATION.md
 [code review platform integrations]: docs/INTEGRATION.md#code-review
 [continuous integrations]: docs/INTEGRATION.md#continuous-integration
@@ -662,7 +709,7 @@
 [version control integrations]: docs/INTEGRATION.md#version-control
 [create an issue]: https://github.com/hadolint/hadolint/issues/new
 [dockerfile reference]: http://docs.docker.com/engine/reference/builder/
-[syntax.hs]: https://www.stackage.org/haddock/nightly-2018-01-07/language-docker-2.0.1/Language-Docker-Syntax.html
+[syntax.hs]: https://www.stackage.org/haddock/nightly-2022-11-15/language-docker-12.0.0/Language-Docker-Syntax.html
 [rfc3339]: https://www.ietf.org/rfc/rfc3339.txt
 [semver]: https://semver.org/
 [rfc3986]: https://www.ietf.org/rfc/rfc3986.txt
diff --git a/hadolint.cabal b/hadolint.cabal
--- a/hadolint.cabal
+++ b/hadolint.cabal
@@ -1,332 +1,333 @@
-cabal-version: 2.0
-
--- This file has been generated from package.yaml by hpack version 0.34.7.
---
--- see: https://github.com/sol/hpack
+cabal-version:      2.0
+name:               hadolint
+version:            2.13.1
+synopsis:           Dockerfile Linter JavaScript API
+description:
+  A smarter Dockerfile linter that helps you build best practice Docker images.
 
-name:           hadolint
-version:        2.12.0
-synopsis:       Dockerfile Linter JavaScript API
-description:    A smarter Dockerfile linter that helps you build best practice Docker images.
-category:       Development
-homepage:       https://github.com/hadolint/hadolint
-author:         Lukas Martinelli
-maintainer:     me@lukasmartinelli.ch
-copyright:      Lukas Martinelli
-license:        GPL-3
-license-file:   LICENSE
-build-type:     Simple
-extra-source-files:
-    README.md
+category:           Development
+homepage:           https://github.com/hadolint/hadolint
+author:             Lukas Martinelli
+maintainer:         me@lukasmartinelli.ch
+copyright:          Lukas Martinelli
+license:            GPL-3
+license-file:       LICENSE
+build-type:         Simple
+extra-source-files: README.md
 
 source-repository head
-  type: git
+  type:     git
   location: git@github.com:hadolint/hadolint.git
 
-flag static
-  description: Use static linking for the hadolint executable
-  manual: True
-  default: True
-
 library
   exposed-modules:
-      Hadolint
-      Hadolint.Config
-      Hadolint.Config.Commandline
-      Hadolint.Config.Configfile
-      Hadolint.Config.Configuration
-      Hadolint.Config.Environment
-      Hadolint.Formatter
-      Hadolint.Formatter.Checkstyle
-      Hadolint.Formatter.Codacy
-      Hadolint.Formatter.Codeclimate
-      Hadolint.Formatter.Format
-      Hadolint.Formatter.Gnu
-      Hadolint.Formatter.Json
-      Hadolint.Formatter.Sarif
-      Hadolint.Formatter.SonarQube
-      Hadolint.Formatter.TTY
-      Hadolint.Lint
-      Hadolint.Meta
-      Hadolint.Pragma
-      Hadolint.Process
-      Hadolint.Rule
-      Hadolint.Rule.DL1001
-      Hadolint.Rule.DL3000
-      Hadolint.Rule.DL3001
-      Hadolint.Rule.DL3002
-      Hadolint.Rule.DL3003
-      Hadolint.Rule.DL3004
-      Hadolint.Rule.DL3005
-      Hadolint.Rule.DL3006
-      Hadolint.Rule.DL3007
-      Hadolint.Rule.DL3008
-      Hadolint.Rule.DL3009
-      Hadolint.Rule.DL3010
-      Hadolint.Rule.DL3011
-      Hadolint.Rule.DL3012
-      Hadolint.Rule.DL3013
-      Hadolint.Rule.DL3014
-      Hadolint.Rule.DL3015
-      Hadolint.Rule.DL3016
-      Hadolint.Rule.DL3018
-      Hadolint.Rule.DL3019
-      Hadolint.Rule.DL3020
-      Hadolint.Rule.DL3021
-      Hadolint.Rule.DL3022
-      Hadolint.Rule.DL3023
-      Hadolint.Rule.DL3024
-      Hadolint.Rule.DL3025
-      Hadolint.Rule.DL3026
-      Hadolint.Rule.DL3027
-      Hadolint.Rule.DL3028
-      Hadolint.Rule.DL3029
-      Hadolint.Rule.DL3030
-      Hadolint.Rule.DL3032
-      Hadolint.Rule.DL3033
-      Hadolint.Rule.DL3034
-      Hadolint.Rule.DL3035
-      Hadolint.Rule.DL3036
-      Hadolint.Rule.DL3037
-      Hadolint.Rule.DL3038
-      Hadolint.Rule.DL3040
-      Hadolint.Rule.DL3041
-      Hadolint.Rule.DL3042
-      Hadolint.Rule.DL3043
-      Hadolint.Rule.DL3044
-      Hadolint.Rule.DL3045
-      Hadolint.Rule.DL3046
-      Hadolint.Rule.DL3047
-      Hadolint.Rule.DL3048
-      Hadolint.Rule.DL3049
-      Hadolint.Rule.DL3050
-      Hadolint.Rule.DL3051
-      Hadolint.Rule.DL3052
-      Hadolint.Rule.DL3053
-      Hadolint.Rule.DL3054
-      Hadolint.Rule.DL3055
-      Hadolint.Rule.DL3056
-      Hadolint.Rule.DL3057
-      Hadolint.Rule.DL3058
-      Hadolint.Rule.DL3059
-      Hadolint.Rule.DL3060
-      Hadolint.Rule.DL3061
-      Hadolint.Rule.DL4000
-      Hadolint.Rule.DL4001
-      Hadolint.Rule.DL4003
-      Hadolint.Rule.DL4004
-      Hadolint.Rule.DL4005
-      Hadolint.Rule.DL4006
-      Hadolint.Rule.Shellcheck
-      Hadolint.Shell
-  other-modules:
-      Paths_hadolint
-  autogen-modules:
-      Paths_hadolint
-  hs-source-dirs:
-      src
+    Hadolint
+    Hadolint.Config
+    Hadolint.Config.Commandline
+    Hadolint.Config.Configfile
+    Hadolint.Config.Configuration
+    Hadolint.Config.Environment
+    Hadolint.Formatter
+    Hadolint.Formatter.Checkstyle
+    Hadolint.Formatter.Codacy
+    Hadolint.Formatter.Codeclimate
+    Hadolint.Formatter.Format
+    Hadolint.Formatter.Gnu
+    Hadolint.Formatter.Json
+    Hadolint.Formatter.Sarif
+    Hadolint.Formatter.SonarQube
+    Hadolint.Formatter.TTY
+    Hadolint.Lint
+    Hadolint.Meta
+    Hadolint.Pragma
+    Hadolint.Process
+    Hadolint.Rule
+    Hadolint.Rule.DL1001
+    Hadolint.Rule.DL3000
+    Hadolint.Rule.DL3001
+    Hadolint.Rule.DL3002
+    Hadolint.Rule.DL3003
+    Hadolint.Rule.DL3004
+    Hadolint.Rule.DL3006
+    Hadolint.Rule.DL3007
+    Hadolint.Rule.DL3008
+    Hadolint.Rule.DL3009
+    Hadolint.Rule.DL3010
+    Hadolint.Rule.DL3011
+    Hadolint.Rule.DL3012
+    Hadolint.Rule.DL3013
+    Hadolint.Rule.DL3014
+    Hadolint.Rule.DL3015
+    Hadolint.Rule.DL3016
+    Hadolint.Rule.DL3018
+    Hadolint.Rule.DL3019
+    Hadolint.Rule.DL3020
+    Hadolint.Rule.DL3021
+    Hadolint.Rule.DL3022
+    Hadolint.Rule.DL3023
+    Hadolint.Rule.DL3024
+    Hadolint.Rule.DL3025
+    Hadolint.Rule.DL3026
+    Hadolint.Rule.DL3027
+    Hadolint.Rule.DL3028
+    Hadolint.Rule.DL3029
+    Hadolint.Rule.DL3030
+    Hadolint.Rule.DL3032
+    Hadolint.Rule.DL3033
+    Hadolint.Rule.DL3034
+    Hadolint.Rule.DL3035
+    Hadolint.Rule.DL3036
+    Hadolint.Rule.DL3037
+    Hadolint.Rule.DL3038
+    Hadolint.Rule.DL3040
+    Hadolint.Rule.DL3041
+    Hadolint.Rule.DL3042
+    Hadolint.Rule.DL3043
+    Hadolint.Rule.DL3044
+    Hadolint.Rule.DL3045
+    Hadolint.Rule.DL3046
+    Hadolint.Rule.DL3047
+    Hadolint.Rule.DL3048
+    Hadolint.Rule.DL3049
+    Hadolint.Rule.DL3050
+    Hadolint.Rule.DL3051
+    Hadolint.Rule.DL3052
+    Hadolint.Rule.DL3053
+    Hadolint.Rule.DL3054
+    Hadolint.Rule.DL3055
+    Hadolint.Rule.DL3056
+    Hadolint.Rule.DL3057
+    Hadolint.Rule.DL3058
+    Hadolint.Rule.DL3059
+    Hadolint.Rule.DL3060
+    Hadolint.Rule.DL3061
+    Hadolint.Rule.DL4000
+    Hadolint.Rule.DL4001
+    Hadolint.Rule.DL4003
+    Hadolint.Rule.DL4004
+    Hadolint.Rule.DL4005
+    Hadolint.Rule.DL4006
+    Hadolint.Rule.Shellcheck
+    Hadolint.Shell
+
+  other-modules:      Paths_hadolint
+  autogen-modules:    Paths_hadolint
+  hs-source-dirs:     src
   default-extensions:
-      DeriveAnyClass
-      DeriveGeneric
-      NamedFieldPuns
-      OverloadedStrings
-      PatternSynonyms
-      RecordWildCards
-      ScopedTypeVariables
-      StrictData
-      TemplateHaskell
-      TupleSections
-  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path -flate-dmd-anal
+    DeriveAnyClass
+    DeriveGeneric
+    NamedFieldPuns
+    OverloadedStrings
+    PatternSynonyms
+    RecordWildCards
+    ScopedTypeVariables
+    StrictData
+    TemplateHaskell
+    TupleSections
+
+  ghc-options:
+    -Wall -Wcompat -Wincomplete-record-updates
+    -Wincomplete-uni-patterns -Wredundant-constraints
+    -optP-Wno-nonportable-include-path -flate-dmd-anal
+
   build-depends:
-      Cabal
-    , HsYAML
-    , ShellCheck >=0.8.0
-    , aeson
-    , base >=4.8 && <5
-    , bytestring
-    , colourista
-    , containers
-    , cryptonite
-    , data-default
-    , deepseq >=1.4.4 && <1.5
-    , directory >=1.3.0
-    , email-validate
-    , filepath
-    , foldl
-    , gitrev >=1.3.1
-    , ilist
-    , language-docker >=11.0.0 && <12
-    , megaparsec >=9.0.0
-    , mtl
-    , network-uri
-    , optparse-applicative >=0.14.0
-    , parallel
-    , parsec >=3.1.14
-    , prettyprinter >=1.7.0
-    , semver
-    , spdx
-    , split >=0.2
-    , text
-    , time
-    , timerep >=2.0
-    , void
-  default-language: Haskell2010
+      aeson                 >=2.2.3    && <2.3
+    , base                  >=4.20.1   && <5
+    , base16-bytestring     >=1.0.2    && <1.1
+    , bytestring            >=0.12.2   && <0.13
+    , Cabal                 >=3.12.1   && <3.13
+    , colourista            >=0.1.0    && <0.2
+    , containers            >=0.7      && <0.8
+    , cryptohash-sha1       >=0.11.101 && <0.12
+    , data-default          >=0.8.0    && <0.9
+    , deepseq               >=1.5.0    && <1.6
+    , directory             >=1.3.8    && <1.4
+    , email-validate        >=2.3.2    && <2.4
+    , filepath              >=1.5.4    && <1.6
+    , foldl                 >=1.4.18   && <1.5
+    , gitrev                >=1.3.1    && <1.4
+    , HsYAML                >=0.2.1    && <0.3
+    , ilist                 >=0.3.1    && <0.4
+    , language-docker       >=14.0.1   && <15
+    , megaparsec            >=9.0.0    && <9.8
+    , mtl                   >=2.3.1    && <2.4
+    , network-uri           >=2.6.4    && <2.7
+    , optparse-applicative  >=0.19.0   && <0.20
+    , parallel              >=3.2.2    && <3.3
+    , parsec                >=3.1.18   && <3.2
+    , prettyprinter         >=1.7.1    && <1.8
+    , semver                >=0.4.0    && <0.5
+    , ShellCheck            >=0.11.0   && <0.12
+    , spdx                  >=1.1      && <1.2
+    , split                 >=0.2.5    && <0.3
+    , text                  >=2.1.2    && <2.2
+    , time                  >=1.12.2   && <1.13
+    , timerep               >=2.1.0    && <2.2
+    , void                  >=0.7.3    && <0.8
+    , xml-conduit           >=1.10.0   && <1.11
 
+  default-language:   GHC2021
+
 executable hadolint
-  main-is: Main.hs
-  other-modules:
-      Paths_hadolint
-  autogen-modules:
-      Paths_hadolint
-  hs-source-dirs:
-      app
+  main-is:            Main.hs
+  other-modules:      Paths_hadolint
+  autogen-modules:    Paths_hadolint
+  hs-source-dirs:     app
   default-extensions:
-      DeriveAnyClass
-      DeriveGeneric
-      NamedFieldPuns
-      OverloadedStrings
-      PatternSynonyms
-      RecordWildCards
-      ScopedTypeVariables
-      StrictData
-      TemplateHaskell
-      TupleSections
-  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path -flate-dmd-anal -O2 -threaded -rtsopts "-with-rtsopts=-N5 -A4m"
+    DeriveAnyClass
+    DeriveGeneric
+    NamedFieldPuns
+    OverloadedStrings
+    PatternSynonyms
+    RecordWildCards
+    ScopedTypeVariables
+    StrictData
+    TemplateHaskell
+    TupleSections
+
+  ghc-options:
+    -Wall -Wcompat -Wincomplete-record-updates
+    -Wincomplete-uni-patterns -Wredundant-constraints
+    -optP-Wno-nonportable-include-path -flate-dmd-anal -threaded
+    -rtsopts "-with-rtsopts=-N5 -A4m"
+
   build-depends:
-      base >=4.8 && <5
-    , containers
-    , data-default
+      base                  >=4.20.1    && <5
+    , containers            >=0.7       && <0.8
+    , data-default          >=0.8.0     && <0.9
     , hadolint
-    , language-docker >=11.0.0 && <12
-    , megaparsec >=9.0.0
-    , optparse-applicative >=0.14.0
-    , prettyprinter >=1.7.0
-    , text
-  if flag(static) && !(os(osx))
-    ld-options: -static -pthread
-  default-language: Haskell2010
+    , language-docker       >=14.0.1    && <15
+    , megaparsec            >=9.7.0     && <9.8
+    , optparse-applicative  >=0.19.0    && <0.20
+    , prettyprinter         >=1.7.1     && <1.8
+    , text                  >=2.1.2     && <2.2
 
+  default-language:   GHC2021
+
 test-suite hadolint-unit-tests
-  type: exitcode-stdio-1.0
-  main-is: Spec.hs
+  type:               exitcode-stdio-1.0
+  main-is:            Spec.hs
   other-modules:
-      Hadolint.Config.CommandlineSpec
-      Hadolint.Config.ConfigfileSpec
-      Hadolint.Config.ConfigurationSpec
-      Hadolint.Config.EnvironmentSpec
-      Hadolint.Config.SpecHook
-      Hadolint.Formatter.GnuSpec
-      Hadolint.Formatter.ParseErrorSpec
-      Hadolint.Formatter.SarifSpec
-      Hadolint.Formatter.TTYSpec
-      Hadolint.PragmaSpec
-      Hadolint.Rule.DL1001Spec
-      Hadolint.Rule.DL3000Spec
-      Hadolint.Rule.DL3001Spec
-      Hadolint.Rule.DL3002Spec
-      Hadolint.Rule.DL3003Spec
-      Hadolint.Rule.DL3004Spec
-      Hadolint.Rule.DL3005Spec
-      Hadolint.Rule.DL3006Spec
-      Hadolint.Rule.DL3007Spec
-      Hadolint.Rule.DL3008Spec
-      Hadolint.Rule.DL3009Spec
-      Hadolint.Rule.DL3010Spec
-      Hadolint.Rule.DL3011Spec
-      Hadolint.Rule.DL3012Spec
-      Hadolint.Rule.DL3013Spec
-      Hadolint.Rule.DL3014Spec
-      Hadolint.Rule.DL3015Spec
-      Hadolint.Rule.DL3016Spec
-      Hadolint.Rule.DL3018Spec
-      Hadolint.Rule.DL3019Spec
-      Hadolint.Rule.DL3020Spec
-      Hadolint.Rule.DL3021Spec
-      Hadolint.Rule.DL3022Spec
-      Hadolint.Rule.DL3023Spec
-      Hadolint.Rule.DL3024Spec
-      Hadolint.Rule.DL3025Spec
-      Hadolint.Rule.DL3026Spec
-      Hadolint.Rule.DL3027Spec
-      Hadolint.Rule.DL3028Spec
-      Hadolint.Rule.DL3029Spec
-      Hadolint.Rule.DL3030Spec
-      Hadolint.Rule.DL3032Spec
-      Hadolint.Rule.DL3033Spec
-      Hadolint.Rule.DL3034Spec
-      Hadolint.Rule.DL3035Spec
-      Hadolint.Rule.DL3036Spec
-      Hadolint.Rule.DL3037Spec
-      Hadolint.Rule.DL3038Spec
-      Hadolint.Rule.DL3040Spec
-      Hadolint.Rule.DL3041Spec
-      Hadolint.Rule.DL3042Spec
-      Hadolint.Rule.DL3043Spec
-      Hadolint.Rule.DL3044Spec
-      Hadolint.Rule.DL3045Spec
-      Hadolint.Rule.DL3046Spec
-      Hadolint.Rule.DL3047Spec
-      Hadolint.Rule.DL3048Spec
-      Hadolint.Rule.DL3049Spec
-      Hadolint.Rule.DL3050Spec
-      Hadolint.Rule.DL3051Spec
-      Hadolint.Rule.DL3052Spec
-      Hadolint.Rule.DL3053Spec
-      Hadolint.Rule.DL3054Spec
-      Hadolint.Rule.DL3055Spec
-      Hadolint.Rule.DL3056Spec
-      Hadolint.Rule.DL3057Spec
-      Hadolint.Rule.DL3058Spec
-      Hadolint.Rule.DL3059Spec
-      Hadolint.Rule.DL3060Spec
-      Hadolint.Rule.DL3061Spec
-      Hadolint.Rule.DL4000Spec
-      Hadolint.Rule.DL4001Spec
-      Hadolint.Rule.DL4003Spec
-      Hadolint.Rule.DL4004Spec
-      Hadolint.Rule.DL4005Spec
-      Hadolint.Rule.DL4006Spec
-      Hadolint.Rule.ShellcheckSpec
-      Hadolint.ShellSpec
-      Helpers
-      RegressionSpec
-      Paths_hadolint
-  autogen-modules:
-      Paths_hadolint
-  hs-source-dirs:
-      test
+    Hadolint.Config.CommandlineSpec
+    Hadolint.Config.ConfigfileSpec
+    Hadolint.Config.ConfigurationSpec
+    Hadolint.Config.EnvironmentSpec
+    Hadolint.Config.SpecHook
+    Hadolint.Formatter.GnuSpec
+    Hadolint.Formatter.ParseErrorSpec
+    Hadolint.Formatter.SarifSpec
+    Hadolint.Formatter.TTYSpec
+    Hadolint.PragmaSpec
+    Hadolint.Rule.DL1001Spec
+    Hadolint.Rule.DL3000Spec
+    Hadolint.Rule.DL3001Spec
+    Hadolint.Rule.DL3002Spec
+    Hadolint.Rule.DL3003Spec
+    Hadolint.Rule.DL3004Spec
+    Hadolint.Rule.DL3006Spec
+    Hadolint.Rule.DL3007Spec
+    Hadolint.Rule.DL3008Spec
+    Hadolint.Rule.DL3009Spec
+    Hadolint.Rule.DL3010Spec
+    Hadolint.Rule.DL3011Spec
+    Hadolint.Rule.DL3012Spec
+    Hadolint.Rule.DL3013Spec
+    Hadolint.Rule.DL3014Spec
+    Hadolint.Rule.DL3015Spec
+    Hadolint.Rule.DL3016Spec
+    Hadolint.Rule.DL3018Spec
+    Hadolint.Rule.DL3019Spec
+    Hadolint.Rule.DL3020Spec
+    Hadolint.Rule.DL3021Spec
+    Hadolint.Rule.DL3022Spec
+    Hadolint.Rule.DL3023Spec
+    Hadolint.Rule.DL3024Spec
+    Hadolint.Rule.DL3025Spec
+    Hadolint.Rule.DL3026Spec
+    Hadolint.Rule.DL3027Spec
+    Hadolint.Rule.DL3028Spec
+    Hadolint.Rule.DL3029Spec
+    Hadolint.Rule.DL3030Spec
+    Hadolint.Rule.DL3032Spec
+    Hadolint.Rule.DL3033Spec
+    Hadolint.Rule.DL3034Spec
+    Hadolint.Rule.DL3035Spec
+    Hadolint.Rule.DL3036Spec
+    Hadolint.Rule.DL3037Spec
+    Hadolint.Rule.DL3038Spec
+    Hadolint.Rule.DL3040Spec
+    Hadolint.Rule.DL3041Spec
+    Hadolint.Rule.DL3042Spec
+    Hadolint.Rule.DL3043Spec
+    Hadolint.Rule.DL3044Spec
+    Hadolint.Rule.DL3045Spec
+    Hadolint.Rule.DL3046Spec
+    Hadolint.Rule.DL3047Spec
+    Hadolint.Rule.DL3048Spec
+    Hadolint.Rule.DL3049Spec
+    Hadolint.Rule.DL3050Spec
+    Hadolint.Rule.DL3051Spec
+    Hadolint.Rule.DL3052Spec
+    Hadolint.Rule.DL3053Spec
+    Hadolint.Rule.DL3054Spec
+    Hadolint.Rule.DL3055Spec
+    Hadolint.Rule.DL3056Spec
+    Hadolint.Rule.DL3057Spec
+    Hadolint.Rule.DL3058Spec
+    Hadolint.Rule.DL3059Spec
+    Hadolint.Rule.DL3060Spec
+    Hadolint.Rule.DL3061Spec
+    Hadolint.Rule.DL4000Spec
+    Hadolint.Rule.DL4001Spec
+    Hadolint.Rule.DL4003Spec
+    Hadolint.Rule.DL4004Spec
+    Hadolint.Rule.DL4005Spec
+    Hadolint.Rule.DL4006Spec
+    Hadolint.Rule.ShellcheckSpec
+    Hadolint.ShellSpec
+    Helpers
+    Paths_hadolint
+    RegressionSpec
+
+  autogen-modules:    Paths_hadolint
+  hs-source-dirs:     test
   default-extensions:
-      DeriveAnyClass
-      DeriveGeneric
-      NamedFieldPuns
-      OverloadedStrings
-      PatternSynonyms
-      RecordWildCards
-      ScopedTypeVariables
-      StrictData
-      TemplateHaskell
-      TupleSections
-      ImplicitParams
-      OverloadedLists
-  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path -flate-dmd-anal
-  build-tool-depends:
-      hspec-discover:hspec-discover ==2.*
+    DeriveAnyClass
+    DeriveGeneric
+    ImplicitParams
+    NamedFieldPuns
+    OverloadedLists
+    OverloadedStrings
+    PatternSynonyms
+    RecordWildCards
+    ScopedTypeVariables
+    StrictData
+    TemplateHaskell
+    TupleSections
+
+  ghc-options:
+    -Wall -Wcompat -Wincomplete-record-updates
+    -Wincomplete-uni-patterns -Wredundant-constraints
+    -optP-Wno-nonportable-include-path -flate-dmd-anal
+
+  build-tool-depends: hspec-discover:hspec-discover >=2 && <3
   build-depends:
-      HUnit >=1.2
-    , HsYAML
-    , ShellCheck >=0.8.0
-    , aeson
-    , base >=4.8 && <5
-    , bytestring >=0.10
+      aeson
+    , base
+    , bytestring
     , containers
     , data-default
     , foldl
     , hadolint
-    , hspec >=2.8.3
-    , language-docker >=11.0.0 && <12
-    , megaparsec >=9.0.0
-    , optparse-applicative >=0.14.0
+    , hspec                 >=2.8.3
+    , HsYAML
+    , HUnit                 >=1.2
+    , language-docker
+    , megaparsec
+    , optparse-applicative
+    , ShellCheck
     , silently
-    , split >=0.2
+    , split
     , text
-  default-language: Haskell2010
+
+  default-language:   GHC2021
diff --git a/src/Hadolint/Formatter.hs b/src/Hadolint/Formatter.hs
--- a/src/Hadolint/Formatter.hs
+++ b/src/Hadolint/Formatter.hs
@@ -31,7 +31,7 @@
     Checkstyle -> FormatCheckstyle.printResults allResults filePathInReport
     Codacy -> FormatCodacy.printResults allResults
     CodeclimateJson -> FormatCodeclimate.printResults allResults
-    GitlabCodeclimateJson -> FormatCodeclimate.printGitlabResults allResults
+    GitLabCodeclimateJson -> FormatCodeclimate.printGitLabResults allResults
     Gnu -> FormatGnu.printResults allResults
     Json -> FormatJson.printResults allResults
     Sarif -> FormatSarif.printResults allResults
diff --git a/src/Hadolint/Formatter/Checkstyle.hs b/src/Hadolint/Formatter/Checkstyle.hs
--- a/src/Hadolint/Formatter/Checkstyle.hs
+++ b/src/Hadolint/Formatter/Checkstyle.hs
@@ -1,15 +1,11 @@
-module Hadolint.Formatter.Checkstyle
-  ( printResults,
-    formatResult,
-  )
+module Hadolint.Formatter.Checkstyle ( printResults )
 where
 
-import qualified Control.Foldl as Foldl
-import qualified Data.ByteString.Builder as Builder
 import qualified Data.ByteString.Lazy.Char8 as B
-import Data.Char (isAsciiLower, isAsciiUpper, isDigit, ord)
+import Data.Foldable
+import qualified Data.Map as Map
+import qualified Data.Maybe as Maybe
 import qualified Data.Text as Text
-import Data.Text.Encoding (encodeUtf8Builder)
 import Hadolint.Formatter.Format
   ( Result (..),
     errorBundlePretty,
@@ -17,7 +13,6 @@
     severityText,
   )
 import Hadolint.Rule (CheckFailure (..), DLSeverity (..), RuleCode (..))
-import System.IO (stdout)
 import Text.Megaparsec (TraversableStream)
 import Text.Megaparsec.Error
   ( ParseErrorBundle,
@@ -25,95 +20,91 @@
   )
 import Text.Megaparsec.Pos (sourceColumn, sourceLine, unPos)
 import Text.Megaparsec.Stream (VisualStream)
-
-data CheckStyle = CheckStyle
-  { line :: Int,
-    column :: Int,
-    impact :: Text.Text,
-    msg :: Text.Text,
-    source :: Text.Text
-  }
+import qualified Text.XML as XML
 
-errorToCheckStyle :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> CheckStyle
-errorToCheckStyle err =
-  CheckStyle
-    { line = unPos (sourceLine pos),
-      column = unPos (sourceColumn pos),
-      impact = severityText DLErrorC,
-      msg = Text.pack (errorBundlePretty err),
-      source = "DL1000"
+errorToNode ::
+  (VisualStream s, TraversableStream s, ShowErrorComponent e) =>
+  ParseErrorBundle s e -> XML.Node
+errorToNode err =
+  XML.NodeElement XML.Element
+    { elementName = "error",
+      elementAttributes =
+        Map.fromList
+          [ ("line", Text.pack $ show $ unPos $ sourceLine $ errorPosition err),
+            ("column", Text.pack $ show $ unPos $ sourceColumn $ errorPosition err),
+            ("severity", severityText DLErrorC),
+            ("message", Text.pack $ errorBundlePretty err)
+          ],
+      elementNodes = []
     }
-  where
-    pos = errorPosition err
 
-ruleToCheckStyle :: CheckFailure -> CheckStyle
-ruleToCheckStyle CheckFailure {..} =
-  CheckStyle
-    { line = line,
-      column = 1,
-      impact = severityText severity,
-      msg = message,
-      source = unRuleCode code
+checkToNode :: CheckFailure -> XML.Node
+checkToNode CheckFailure {..} =
+  XML.NodeElement XML.Element
+    { elementName = "error",
+      elementAttributes =
+        Map.fromList
+          [ ("line", Text.pack $ show line),
+            ("column", "1"),
+            ("severity", severityText severity),
+            ("message", message),
+            ("source", unRuleCode code)
+          ],
+      elementNodes = []
     }
 
-toXml :: CheckStyle -> Builder.Builder
-toXml CheckStyle {..} =
-  "<error "
-    <> attr "line" (Builder.intDec line)
-    <> attr "column" (Builder.intDec column)
-    <> attr "severity" (encode impact)
-    <> attr "message" (encode msg)
-    <> attr "source" (encode source)
-    <> "/>"
-
-encode :: Text.Text -> Builder.Builder
-encode = encodeUtf8Builder . escape
-
-attr :: Text.Text -> Builder.Builder -> Builder.Builder
-attr name value = encodeUtf8Builder name <> "='" <> value <> "' "
-
-escape :: Text.Text -> Text.Text
-escape = Text.concatMap doEscape
+renderNodes ::
+  (VisualStream s, TraversableStream s, ShowErrorComponent e) =>
+  Result s e -> [XML.Node]
+renderNodes (Result _ errors checks) =
+  if isEmpty then [] else toList ( errorNodes <> checkNodes )
   where
-    doEscape c =
-      if isOk c
-        then Text.singleton c
-        else "&#" <> Text.pack (show (ord c)) <> ";"
-    isOk x = any (\check -> check x) [isAsciiUpper, isAsciiLower, isDigit, (`elem` [' ', '.', '/'])]
+    errorNodes = fmap errorToNode errors
+    checkNodes = fmap checkToNode checks
+    isEmpty = null checks && null errors
 
-formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Maybe FilePath -> Builder.Builder
-formatResult (Result filename errors checks) filePathInReport = header <> xmlBody <> footer
+toFile ::
+  (VisualStream s, TraversableStream s, ShowErrorComponent e) =>
+  Result s e -> Maybe FilePath -> XML.Node
+toFile results filePathInReport =
+  XML.NodeElement XML.Element
+    { elementName = "file",
+      elementAttributes = Map.fromList [("name", filepath)],
+      elementNodes = renderNodes results
+    }
   where
-    xmlBody = Foldl.fold (Foldl.premap toXml Foldl.mconcat) issues
-
-    issues = checkstyleErrors <> checkstyleChecks
-    checkstyleErrors = fmap errorToCheckStyle errors
-    checkstyleChecks = fmap ruleToCheckStyle checks
+    filepath = if null filePathInReport then filename results else getFilePath filePathInReport
+    filename Result {fileName=fn} = fn
 
-    isEmpty = null checks && null errors
-    name = if null filePathInReport then filename else getFilePath filePathInReport
-    header =
-      if isEmpty
-        then ""
-        else "<file " <> attr "name" (encode name) <> ">"
-    footer = if isEmpty then "" else "</file>"
+renderResults ::
+  (Foldable f, VisualStream s, TraversableStream s, ShowErrorComponent e) =>
+  f (Result s e) -> Maybe FilePath -> XML.Element
+renderResults results filePathInReport = XML.Element
+  { elementName = "checkstyle",
+    elementAttributes = Map.fromList [("version", "4.3")],
+    elementNodes = Maybe.mapMaybe maybeFile ( toList results )
+  }
+  where
+    maybeFile r = if isEmpty r then Nothing else Just $ toFile r filePathInReport
+    isEmpty Result {errors=e, checks=c} = null e && null c
 
 printResults ::
   (Foldable f, VisualStream s, TraversableStream s, ShowErrorComponent e) =>
-  f (Result s e) -> Maybe FilePath ->
-  IO ()
-printResults results filePathInReport = do
-  B.putStr header
-  mapM_ put results
-  B.putStr footer
+  f (Result s e) -> Maybe FilePath -> IO ()
+printResults results filePathInReport =
+  B.putStr $ XML.renderLBS settings document
   where
-    header = "<?xml version='1.0' encoding='UTF-8'?><checkstyle version='4.3'>"
-    footer = "</checkstyle>"
-    put result = Builder.hPutBuilder stdout (formatResult result filePathInReport)
+    settings = XML.def -- use default render settings
+    document =
+      XML.Document
+        { documentPrologue = XML.Prologue [] Nothing [],
+          documentRoot = renderResults results filePathInReport,
+          documentEpilogue = []
+        }
 
-getFilePath :: Maybe FilePath -> Text.Text 
+getFilePath :: Maybe FilePath -> Text.Text
 getFilePath Nothing = ""
 getFilePath (Just filePath) = toText [filePath]
 
-toText :: [FilePath] -> Text.Text 
+toText :: [FilePath] -> Text.Text
 toText = foldMap Text.pack
diff --git a/src/Hadolint/Formatter/Codeclimate.hs b/src/Hadolint/Formatter/Codeclimate.hs
--- a/src/Hadolint/Formatter/Codeclimate.hs
+++ b/src/Hadolint/Formatter/Codeclimate.hs
@@ -1,15 +1,17 @@
 module Hadolint.Formatter.Codeclimate
   ( printResults,
-    printGitlabResults,
+    printGitLabResults,
     formatResult,
-    formatGitlabResult,
+    formatGitLabResult,
   )
 where
 
 import qualified Control.Foldl as Foldl
-import Crypto.Hash (Digest, SHA1 (..), hash)
+import qualified Crypto.Hash.SHA1 as SHA1
 import Data.Aeson hiding (Result)
 import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString.Base16 as B16
+import Data.ByteString.Char8 as Char8
 import Data.Sequence (Seq)
 import qualified Data.Text as Text
 import GHC.Generics
@@ -29,7 +31,7 @@
 
 data FingerprintIssue = FingerprintIssue
   { issue :: Issue,
-    fingerprint :: Digest SHA1
+    fingerprint :: ByteString
   }
 
 data Location
@@ -68,7 +70,7 @@
   toJSON FingerprintIssue {..} =
     object
       [ "type" .= ("issue" :: Text.Text),
-        "fingerprint" .= show fingerprint,
+        "fingerprint" .= Char8.unpack fingerprint,
         "check_name" .= checkName issue,
         "description" .= description issue,
         "categories" .= (["Bug Risk"] :: [Text.Text]),
@@ -107,8 +109,8 @@
     DLStyleC -> "minor"
     _ -> ""
 
-generateFingerprint :: Issue -> Digest SHA1
-generateFingerprint = hash . B.toStrict . encode
+generateFingerprint :: Issue -> ByteString
+generateFingerprint = B16.encode . SHA1.hashlazy . encode
 
 issueToFingerprintIssue :: Issue -> FingerprintIssue
 issueToFingerprintIssue i =
@@ -120,8 +122,8 @@
 formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Seq Issue
 formatResult (Result filename errors checks) = (errorToIssue <$> errors) <> (checkToIssue filename <$> checks)
 
-formatGitlabResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Seq FingerprintIssue
-formatGitlabResult result = issueToFingerprintIssue <$> formatResult result
+formatGitLabResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Seq FingerprintIssue
+formatGitLabResult result = issueToFingerprintIssue <$> formatResult result
 
 printResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> IO ()
 printResult result = mapM_ output (formatResult result)
@@ -133,7 +135,7 @@
 printResults :: (VisualStream s, TraversableStream s, ShowErrorComponent e, Foldable f) => f (Result s e) -> IO ()
 printResults = mapM_ printResult
 
-printGitlabResults :: (Foldable f, VisualStream s, TraversableStream s, ShowErrorComponent e) => f (Result s e) -> IO ()
-printGitlabResults results = B.putStr . encode $ flattened
+printGitLabResults :: (Foldable f, VisualStream s, TraversableStream s, ShowErrorComponent e) => f (Result s e) -> IO ()
+printGitLabResults results = B.putStr . encode $ flattened
   where
-    flattened = Foldl.fold (Foldl.premap formatGitlabResult Foldl.mconcat) results
+    flattened = Foldl.fold (Foldl.premap formatGitLabResult Foldl.mconcat) results
diff --git a/src/Hadolint/Formatter/Format.hs b/src/Hadolint/Formatter/Format.hs
--- a/src/Hadolint/Formatter/Format.hs
+++ b/src/Hadolint/Formatter/Format.hs
@@ -32,7 +32,7 @@
   | SonarQube
   | TTY
   | CodeclimateJson
-  | GitlabCodeclimateJson
+  | GitLabCodeclimateJson
   | Gnu
   | Checkstyle
   | Codacy
@@ -44,7 +44,7 @@
   pretty SonarQube = "sonarqube"
   pretty TTY = "tty"
   pretty CodeclimateJson = "codeclimate"
-  pretty GitlabCodeclimateJson = "gitlab_codeclimate"
+  pretty GitLabCodeclimateJson = "gitlab_codeclimate"
   pretty Gnu = "gnu"
   pretty Checkstyle = "checkstyle"
   pretty Codacy = "codacy"
@@ -77,7 +77,7 @@
 readMaybeOutputFormat "sonarqube" = Just SonarQube
 readMaybeOutputFormat "tty" = Just TTY
 readMaybeOutputFormat "codeclimate" = Just CodeclimateJson
-readMaybeOutputFormat "gitlab_codeclimate" = Just GitlabCodeclimateJson
+readMaybeOutputFormat "gitlab_codeclimate" = Just GitLabCodeclimateJson
 readMaybeOutputFormat "gnu" = Just Gnu
 readMaybeOutputFormat "checkstyle" = Just Checkstyle
 readMaybeOutputFormat "codacy" = Just Codacy
diff --git a/src/Hadolint/Pragma.hs b/src/Hadolint/Pragma.hs
--- a/src/Hadolint/Pragma.hs
+++ b/src/Hadolint/Pragma.hs
@@ -46,8 +46,14 @@
 ignoreParser = hadolintPragma >> ignore
 
 globalIgnoreParser :: Megaparsec.Parsec Void Text [Text]
-globalIgnoreParser = hadolintPragma >> string "global" >> spaces1 >> ignore
+globalIgnoreParser = hadolintPragma >> global >> ignore
 
+hadolintPragma :: Megaparsec.Parsec Void Text Text
+hadolintPragma = spaces >> string "hadolint" >> spaces1
+
+global :: Megaparsec.Parsec Void Text Text
+global = string "global" >> spaces1
+
 ignore :: Megaparsec.Parsec Void Text [Text]
 ignore = string "ignore" >> spaces >> string "=" >> spaces >> ruleList
 
@@ -55,8 +61,9 @@
 ruleList = Megaparsec.sepBy1 ruleName ( spaces >> string "," >> spaces )
 
 ruleName :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
-ruleName = Megaparsec.takeWhile1P Nothing (\c -> c `elem` Set.fromList "DLSC0123456789")
-
+ruleName =
+  Megaparsec.takeWhile1P Nothing (\c -> c `elem` Set.fromList "DLSC0123456789")
+  <* inlineComment
 
 parseShell :: Text -> Maybe Text
 parseShell = Megaparsec.parseMaybe shellParser
@@ -65,12 +72,13 @@
 shellParser = hadolintPragma >> string "shell" >> spaces >> string "=" >> spaces >> shellName
 
 shellName :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
-shellName = Megaparsec.takeWhile1P Nothing (/= '\n')
-
-
-hadolintPragma :: Megaparsec.Parsec Void Text Text
-hadolintPragma = spaces >> string "hadolint" >> spaces1
+shellName =
+  Megaparsec.takeWhile1P Nothing (\c -> c `notElem` Set.fromList "\n\t ")
+  <* inlineComment
 
+inlineComment :: Megaparsec.Parsec Void Text (Maybe Text)
+inlineComment =
+  spaces >> Megaparsec.optional ( string "#" >> Megaparsec.takeWhileP Nothing (/= '\n') )
 
 string :: Megaparsec.Tokens Text
   -> Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)
diff --git a/src/Hadolint/Process.hs b/src/Hadolint/Process.hs
--- a/src/Hadolint/Process.hs
+++ b/src/Hadolint/Process.hs
@@ -15,7 +15,6 @@
 import qualified Hadolint.Rule.DL3002
 import qualified Hadolint.Rule.DL3003
 import qualified Hadolint.Rule.DL3004
-import qualified Hadolint.Rule.DL3005
 import qualified Hadolint.Rule.DL3006
 import qualified Hadolint.Rule.DL3007
 import qualified Hadolint.Rule.DL3008
@@ -120,7 +119,6 @@
     <> Hadolint.Rule.DL3002.rule
     <> Hadolint.Rule.DL3003.rule
     <> Hadolint.Rule.DL3004.rule
-    <> Hadolint.Rule.DL3005.rule
     <> Hadolint.Rule.DL3006.rule
     <> Hadolint.Rule.DL3007.rule
     <> Hadolint.Rule.DL3008.rule
diff --git a/src/Hadolint/Rule/DL1001.hs b/src/Hadolint/Rule/DL1001.hs
--- a/src/Hadolint/Rule/DL1001.hs
+++ b/src/Hadolint/Rule/DL1001.hs
@@ -11,7 +11,7 @@
   where
     code = "DL1001"
     severity = DLIgnoreC
-    message = "Please refrain from using inline igore pragmas \
+    message = "Please refrain from using inline ignore pragmas \
               \ `# hadolint ignore=DLxxxx`."
     check (Comment com) =
       case parseIgnorePragma com of
diff --git a/src/Hadolint/Rule/DL3005.hs b/src/Hadolint/Rule/DL3005.hs
deleted file mode 100644
--- a/src/Hadolint/Rule/DL3005.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Hadolint.Rule.DL3005 (rule) where
-
-import Hadolint.Rule
-import Hadolint.Shell (ParsedShell)
-import qualified Hadolint.Shell as Shell
-import Language.Docker.Syntax (Instruction (..), RunArgs (..))
-
-
-rule :: Rule ParsedShell
-rule = dl3005 <> onbuild dl3005
-{-# INLINEABLE rule #-}
-
-dl3005 :: Rule ParsedShell
-dl3005 = simpleRule code severity message check
-  where
-    code = "DL3005"
-    severity = DLErrorC
-    message = "Do not use apt-get dist-upgrade"
-    check (Run (RunArgs args _)) =
-      foldArguments (Shell.noCommands (Shell.cmdHasArgs "apt-get" ["dist-upgrade"])) args
-    check _ = True
-{-# INLINEABLE dl3005 #-}
diff --git a/src/Hadolint/Rule/DL3006.hs b/src/Hadolint/Rule/DL3006.hs
--- a/src/Hadolint/Rule/DL3006.hs
+++ b/src/Hadolint/Rule/DL3006.hs
@@ -1,6 +1,7 @@
 module Hadolint.Rule.DL3006 (rule) where
 
 import qualified Data.Set as Set
+import qualified Data.Text as Text
 import Data.Text (Text)
 import Hadolint.Rule
 import Language.Docker.Syntax
@@ -21,7 +22,7 @@
               -- When the image being used is a previously defined FROM alias,
               -- then we can safely ignore that the image is not tagged. Otherwise
               -- we marked it as a failure
-              if Set.member i (state st)
+              if Text.isPrefixOf "$" i || Set.member i (state st)
                 then newState
                 else newState |> addFail (CheckFailure {..})
             _ -> newState
diff --git a/src/Hadolint/Rule/DL3009.hs b/src/Hadolint/Rule/DL3009.hs
--- a/src/Hadolint/Rule/DL3009.hs
+++ b/src/Hadolint/Rule/DL3009.hs
@@ -28,7 +28,7 @@
   where
     code = "DL3009"
     severity = DLInfoC
-    message = "Delete the apt-get lists after installing something"
+    message = "Delete the apt lists (/var/lib/apt/lists) after installing something"
 
     check line st (From from) = st |> modify (rememberStage line from)
     check line st (Run (RunArgs args flags))
@@ -76,8 +76,12 @@
       any (Shell.cmdHasArgs "rm" ["-rf", "/var/lib/apt/lists/*"]) (Shell.presentCommands args)
 
 hasUpdate :: Shell.ParsedShell -> Bool
-hasUpdate args =
-  any (Shell.cmdHasArgs "apt-get" ["update"]) (Shell.presentCommands args)
+hasUpdate args = any isPackageUpdate (Shell.presentCommands args)
+  where
+    isPackageUpdate cmd =
+      Shell.cmdHasArgs "apt" ["update"] cmd ||
+      Shell.cmdHasArgs "apt-get" ["update"] cmd ||
+      Shell.cmdHasArgs "aptitude" ["update"] cmd
 
 disabledDockerClean :: Shell.ParsedShell -> Bool
 disabledDockerClean args
diff --git a/src/Hadolint/Rule/DL3010.hs b/src/Hadolint/Rule/DL3010.hs
--- a/src/Hadolint/Rule/DL3010.hs
+++ b/src/Hadolint/Rule/DL3010.hs
@@ -3,14 +3,13 @@
 import Data.Foldable (toList)
 import Data.Function ((&))
 import Data.List.NonEmpty (NonEmpty)
-import qualified Data.Set as Set
-import qualified Data.Sequence as Seq
-import qualified Data.Text as Text
+import Data.Sequence qualified as Seq
+import Data.Set qualified as Set
+import Data.Text qualified as Text
 import Hadolint.Rule
-import qualified Hadolint.Shell as Shell
+import Hadolint.Shell qualified as Shell
 import Language.Docker.Syntax
 
-
 data Acc
   = Acc
       { archives :: Set.Set (Linenumber, Text.Text),
@@ -26,7 +25,7 @@
     message = "Use `ADD` for extracting archives into an image"
 
     check _ _ (From _) = emptyState Empty
-    check line st (Copy (CopyArgs srcs tgt) (CopyFlags _ _ _ NoSource)) =
+    check line st (Copy (CopyArgs srcs tgt) (CopyFlags _ _ _ NoSource _)) =
       st |> modify (rememberArchives line srcs tgt)
     check _ st (Run (RunArgs args _))
       | Acc archives _ <- state st,
@@ -44,7 +43,6 @@
     makeFail (line, _) = CheckFailure {..}
 {-# INLINEABLE rule #-}
 
-
 extractsThisArchive :: (Linenumber, Text.Text) -> Shell.Command -> Bool
 extractsThisArchive (_, archive) cmd =
   (isTarExtractCommand cmd || isUnzipCommand cmd) && archive `elem` arguments
@@ -72,22 +70,22 @@
 
 isUnzipCommand :: Shell.Command -> Bool
 isUnzipCommand (Shell.Command name _ _) =
-  name `elem`
-    [ "unzip",
-      "gunzip",
-      "bunzip2",
-      "unlzma",
-      "unxz",
-      "zgz",
-      "uncompress",
-      "zcat",
-      "gzcat"
-    ]
+  name
+    `elem` [ "unzip",
+             "gunzip",
+             "bunzip2",
+             "unlzma",
+             "unxz",
+             "zgz",
+             "uncompress",
+             "zcat",
+             "gzcat"
+           ]
 
 markExtracted :: Set.Set (Linenumber, Text.Text) -> Acc -> Acc
 markExtracted _ Empty = Empty
 markExtracted exarcv Acc {archives, extracted} =
-  Acc { archives, extracted = Set.union exarcv extracted }
+  Acc {archives, extracted = Set.union exarcv extracted}
 
 rememberArchives ::
   Linenumber ->
@@ -97,38 +95,42 @@
   Acc
 rememberArchives line paths target Empty =
   if isArchive $ unTargetPath target
-    then Acc
-          { archives = Set.singleton (line, basename $ unTargetPath target),
-            extracted = Set.empty
-          }
-    else Acc
-          { archives =
-              paths
-                & toList
-                & map (basename . unSourcePath)
-                & Set.fromList
-                & Set.filter isArchive
-                & Set.map (line,),
-            extracted = Set.empty
-          }
+    then
+      Acc
+        { archives = Set.singleton (line, basename $ unTargetPath target),
+          extracted = Set.empty
+        }
+    else
+      Acc
+        { archives =
+            paths
+              & toList
+              & map (basename . unSourcePath)
+              & Set.fromList
+              & Set.filter isArchive
+              & Set.map (line,),
+          extracted = Set.empty
+        }
 rememberArchives line paths target Acc {archives, extracted} =
   if isArchive $ unTargetPath target
-    then Acc
-          { archives =
-              Set.insert (line, basename $ unTargetPath target) archives,
-            extracted
-          }
-    else Acc
-          { archives =
-              paths
-                & toList
-                & map (basename . unSourcePath)
-                & Set.fromList
-                & Set.filter isArchive
-                & Set.map (line,)
-                & Set.union archives,
-            extracted
-          }
+    then
+      Acc
+        { archives =
+            Set.insert (line, basename $ unTargetPath target) archives,
+          extracted
+        }
+    else
+      Acc
+        { archives =
+            paths
+              & toList
+              & map (basename . unSourcePath)
+              & Set.fromList
+              & Set.filter isArchive
+              & Set.map (line,)
+              & Set.union archives,
+          extracted
+        }
 
 basename :: Text.Text -> Text.Text
 basename = Text.takeWhileEnd (\c -> c /= '/' && c /= '\\') . dropQuotes
diff --git a/src/Hadolint/Rule/DL3011.hs b/src/Hadolint/Rule/DL3011.hs
--- a/src/Hadolint/Rule/DL3011.hs
+++ b/src/Hadolint/Rule/DL3011.hs
@@ -10,7 +10,7 @@
     severity = DLErrorC
     message = "Valid UNIX ports range from 0 to 65535"
     check (Expose (Ports ports)) =
-      and [p <= 65535 | Port p _ <- ports]
-        && and [l <= 65535 && m <= 65535 | PortRange l m _ <- ports]
+      and [p <= 65535 | PortSpec (Port p _) <- ports]
+        && and [l <= 65535 && m <= 65535 | PortRangeSpec (PortRange (Port l _) (Port m _)) <- ports]
     check _ = True
 {-# INLINEABLE rule #-}
diff --git a/src/Hadolint/Rule/DL3014.hs b/src/Hadolint/Rule/DL3014.hs
--- a/src/Hadolint/Rule/DL3014.hs
+++ b/src/Hadolint/Rule/DL3014.hs
@@ -22,5 +22,9 @@
 
     forgotAptYesOption cmd = isAptGetInstall cmd && not (hasYesOption cmd)
     isAptGetInstall = Shell.cmdHasArgs "apt-get" ["install"]
-    hasYesOption = Shell.hasAnyFlag ["y", "yes", "q", "assume-yes"]
+    hasYesOption cmd =
+      Shell.hasAnyFlag ["y", "yes", "qq", "assume-yes"] cmd
+        || ( Shell.countFlag "q" cmd == 2 )
+        || ( Shell.countFlag "quiet" cmd == 2 )
+        || "-q=2" `elem` Shell.getArgs cmd
 {-# INLINEABLE dl3014 #-}
diff --git a/src/Hadolint/Rule/DL3022.hs b/src/Hadolint/Rule/DL3022.hs
--- a/src/Hadolint/Rule/DL3022.hs
+++ b/src/Hadolint/Rule/DL3022.hs
@@ -1,21 +1,46 @@
 module Hadolint.Rule.DL3022 (rule) where
 
-import qualified Data.Set as Set
-import qualified Data.Text as Text
+import Data.Set qualified as Set
+import Data.Text qualified as Text
+import Data.Text.Read qualified as Read
 import Hadolint.Rule
 import Language.Docker.Syntax
 
+data Acc
+  = Acc {count :: Int, names :: Set.Set Text.Text}
+  | Empty
+  deriving (Eq)
+
 rule :: Rule args
-rule = customRule check (emptyState Set.empty)
+rule = customRule check (emptyState Empty)
   where
     code = "DL3022"
     severity = DLWarningC
     message = "`COPY --from` should reference a previously defined `FROM` alias"
 
-    check _ st (From BaseImage {alias = Just (ImageAlias als)}) = st |> modify (Set.insert als)
-    check line st (Copy (CopyArgs _ _) (CopyFlags _ _ _ (CopySource s)))
+    check _ st (From BaseImage {alias = Just (ImageAlias als)}) = st |> modify (incAndAddName als)
+    check _ st (From BaseImage {}) = st |> modify incCount
+    check line st (Copy (CopyArgs _ _) (CopyFlags _ _ _ (CopySource s) _))
       | ":" `Text.isInfixOf` dropQuotes s = st
-      | Set.member s (state st) = st
-      | otherwise = st |> addFail CheckFailure {..}
+      | isMember s (state st) = st
+      | otherwise = case Read.decimal s of
+          Right (v, _) | v < nameCount (state st) -> st
+          _ -> st |> addFail CheckFailure {..}
     check _ st _ = st
 {-# INLINEABLE rule #-}
+
+incAndAddName :: Text.Text -> Acc -> Acc
+incAndAddName s Empty = Acc {count = 1, names = Set.singleton s}
+incAndAddName s Acc {count, names} = Acc {count = count + 1, names = Set.insert s names}
+
+incCount :: Acc -> Acc
+incCount Empty = Acc {count = 1, names = Set.empty}
+incCount Acc {count, names} = Acc {count = count + 1, names = names}
+
+isMember :: Text.Text -> Acc -> Bool
+isMember _ Empty = False
+isMember s Acc {names} = Set.member s names
+
+nameCount :: Acc -> Int
+nameCount Empty = 0
+nameCount Acc {count} = count
diff --git a/src/Hadolint/Rule/DL3023.hs b/src/Hadolint/Rule/DL3023.hs
--- a/src/Hadolint/Rule/DL3023.hs
+++ b/src/Hadolint/Rule/DL3023.hs
@@ -11,7 +11,7 @@
     message = "`COPY --from` cannot reference its own `FROM` alias"
 
     check _ st f@(From _) = st |> replaceWith (Just f) -- Remember the last FROM instruction found
-    check line st@(State _ (Just fromInstr)) (Copy (CopyArgs _ _) (CopyFlags _ _ _ (CopySource stageName)))
+    check line st@(State _ (Just fromInstr)) (Copy (CopyArgs _ _) (CopyFlags _ _ _ (CopySource stageName) _))
       | aliasMustBe (/= stageName) fromInstr = st
       | otherwise = st |> addFail CheckFailure {..}
     -- cannot copy from the same stage!
diff --git a/src/Hadolint/Rule/DL3027.hs b/src/Hadolint/Rule/DL3027.hs
--- a/src/Hadolint/Rule/DL3027.hs
+++ b/src/Hadolint/Rule/DL3027.hs
@@ -14,7 +14,7 @@
   where
     code = "DL3027"
     severity = DLWarningC
-    message = "Do not use apt as it is meant to be a end-user tool, use apt-get\
+    message = "Do not use apt as it is meant to be an end-user tool, use apt-get\
               \ or apt-cache instead"
 
     check (Run (RunArgs args _)) = foldArguments (not . usingProgram "apt") args
diff --git a/src/Hadolint/Rule/DL3028.hs b/src/Hadolint/Rule/DL3028.hs
--- a/src/Hadolint/Rule/DL3028.hs
+++ b/src/Hadolint/Rule/DL3028.hs
@@ -34,8 +34,15 @@
       not (Shell.cmdHasArgs "gem" ["-v"] cmd),
       not (Shell.cmdHasArgs "gem" ["--version"] cmd),
       not (Shell.cmdHasPrefixArg "gem" "--version=" cmd),
-      arg <- Shell.getArgsNoFlags cmd,
+      let args = Shell.getArgs cmd,
+      let argsUntilDoubleDash = takeWhile (/= "--") args,
+      arg <- removeOptions argsUntilDoubleDash,
       arg /= "install",
-      arg /= "i",
-      arg /= "--"
+      arg /= "i"
   ]
+  where
+    removeOptions [] = []
+    removeOptions (x : xs)
+      | x == "--" = removeOptions xs
+      | "-" `Text.isPrefixOf` x = removeOptions (drop 1 xs)
+      | otherwise = x : removeOptions xs
diff --git a/src/Hadolint/Rule/DL3030.hs b/src/Hadolint/Rule/DL3030.hs
--- a/src/Hadolint/Rule/DL3030.hs
+++ b/src/Hadolint/Rule/DL3030.hs
@@ -14,7 +14,7 @@
   where
     code = "DL3030"
     severity = DLWarningC
-    message = "Use the -y switch to avoid manual input `yum install -y <package`"
+    message = "Use the -y switch to avoid manual input `yum install -y <package>`"
 
     check (Run (RunArgs args _)) = foldArguments (Shell.noCommands forgotYumYesOption) args
     check _ = True
diff --git a/src/Hadolint/Rule/DL3038.hs b/src/Hadolint/Rule/DL3038.hs
--- a/src/Hadolint/Rule/DL3038.hs
+++ b/src/Hadolint/Rule/DL3038.hs
@@ -14,7 +14,7 @@
   where
     code = "DL3038"
     severity = DLWarningC
-    message = "Use the -y switch to avoid manual input `dnf install -y <package`"
+    message = "Use the -y switch to avoid manual input `dnf install -y <package>`"
 
     check (Run (RunArgs args _)) = foldArguments (Shell.noCommands forgotDnfYesOption) args
     check _ = True
diff --git a/src/Hadolint/Rule/DL3041.hs b/src/Hadolint/Rule/DL3041.hs
--- a/src/Hadolint/Rule/DL3041.hs
+++ b/src/Hadolint/Rule/DL3041.hs
@@ -4,7 +4,7 @@
 import Hadolint.Rule
 import qualified Hadolint.Shell as Shell
 import Language.Docker.Syntax
-
+import Data.Char (isDigit, isAsciiUpper, isAsciiLower)
 
 rule :: Rule Shell.ParsedShell
 rule = dl3041 <> onbuild dl3041
@@ -35,8 +35,26 @@
     ]
 
 packageVersionFixed :: Text.Text -> Bool
-packageVersionFixed package =
-  "-" `Text.isInfixOf` package || ".rpm" `Text.isSuffixOf` package
+packageVersionFixed package
+  | length parts <= 1 = False  -- No dashes, definitively no version
+  | ".rpm" `Text.isSuffixOf` package = True  -- rpm files always have a version
+  | otherwise = isVersionLike $ drop 1 parts
+  where
+    parts = Text.splitOn "-" package
+
+isVersionLike :: [Text.Text] -> Bool
+isVersionLike parts =
+  case parts of
+    [] -> False  -- No parts after splitting by hyphen
+    _ -> all partIsValid parts && any partStartsWithDigit parts
+  where
+    partIsValid part = Text.all isVersionChar part
+    partStartsWithDigit part = case Text.uncons part of
+                                 Just (c, _) -> isDigit c
+                                 Nothing -> False -- Empty Text
+
+isVersionChar :: Char -> Bool
+isVersionChar c = isDigit c || isAsciiUpper c || isAsciiLower c || c `elem` ['.', '~', '^', '_']
 
 dnfModules :: Shell.ParsedShell -> [Text.Text]
 dnfModules args =
diff --git a/src/Hadolint/Rule/DL3048.hs b/src/Hadolint/Rule/DL3048.hs
--- a/src/Hadolint/Rule/DL3048.hs
+++ b/src/Hadolint/Rule/DL3048.hs
@@ -36,4 +36,4 @@
 hasConsecutiveSeparators l = ".." `Text.isInfixOf` l || "--" `Text.isInfixOf` l
 
 validChars :: String
-validChars = ['0'..'9'] ++ ['a' .. 'z'] ++ ['.', '-']
+validChars = ['0'..'9'] ++ ['a' .. 'z'] ++ ['.', '-', '_', '/']
diff --git a/src/Hadolint/Rule/DL3049.hs b/src/Hadolint/Rule/DL3049.hs
--- a/src/Hadolint/Rule/DL3049.hs
+++ b/src/Hadolint/Rule/DL3049.hs
@@ -1,29 +1,27 @@
 module Hadolint.Rule.DL3049 (rule) where
 
-import qualified Data.Map as Map
-import qualified Data.Set as Set
-import qualified Data.Sequence as Seq
-import qualified Data.Text as Text
+import Data.Map qualified as Map
+import Data.Sequence qualified as Seq
+import Data.Set qualified as Set
+import Data.Text qualified as Text
 import Hadolint.Rule
 import Language.Docker.Syntax
 
-
 rule :: LabelSchema -> Rule args
 rule labelschema = mconcat $ fmap missingLabelRule (Map.keys labelschema)
 {-# INLINEABLE rule #-}
 
-
 data StageID = StageID
   { name :: BaseImage,
     line :: Linenumber
-  } deriving (Eq, Ord, Show)
+  }
+  deriving (Eq, Ord, Show)
 
 data Acc
   = Acc StageID (Set.Set StageID) (Set.Set StageID) (Set.Set StageID)
   | Empty
   deriving (Show)
 
-
 -- missingLabelRule
 --
 -- triggers on a `FROM` instruction when label `label` is not defined within
@@ -36,7 +34,7 @@
     message = "Label `" <> label <> "` is missing."
     check line state (From img) =
       state |> modify (currentStage (StageID img line))
-    check _ state (Copy (CopyArgs _ _) (CopyFlags _ _ _ (CopySource src))) =
+    check _ state (Copy (CopyArgs _ _) (CopyFlags _ _ _ (CopySource src) _)) =
       state |> modify (markSilentByAlias src)
     check _ state (Label pairs)
       | label `elem` fmap fst pairs =
@@ -51,7 +49,6 @@
     markFailure st = failures st
 
     markFail (StageID _ line) = CheckFailure {..}
-
 
 currentStage :: StageID -> Acc -> Acc
 currentStage stageid Empty = Acc stageid Set.empty Set.empty (Set.singleton stageid)
diff --git a/src/Hadolint/Rule/DL3053.hs b/src/Hadolint/Rule/DL3053.hs
--- a/src/Hadolint/Rule/DL3053.hs
+++ b/src/Hadolint/Rule/DL3053.hs
@@ -18,7 +18,7 @@
   where
     code = "DL3053"
     severity = DLWarningC
-    message = "Label `" <> label <> "` is not a valid time format - must be conform to RFC3339."
+    message = "Label `" <> label <> "` is not a valid time format - must conform to RFC3339."
     check (Label ls) = null $ getBadTimeformatLabels label ls
     check _ = True
 
diff --git a/src/Hadolint/Rule/DL3058.hs b/src/Hadolint/Rule/DL3058.hs
--- a/src/Hadolint/Rule/DL3058.hs
+++ b/src/Hadolint/Rule/DL3058.hs
@@ -18,7 +18,7 @@
   where
     code = "DL3058"
     severity = DLWarningC
-    message = "Label `" <> label <> "` is not a valid email format - must be conform to RFC5322."
+    message = "Label `" <> label <> "` is not a valid email format - must conform to RFC5322."
     check (Label ls) = null $ getBadEmailformatLabels label ls
     check _ = True
 
diff --git a/src/Hadolint/Rule/DL3059.hs b/src/Hadolint/Rule/DL3059.hs
--- a/src/Hadolint/Rule/DL3059.hs
+++ b/src/Hadolint/Rule/DL3059.hs
@@ -13,7 +13,7 @@
 -- | This Rule catches multiple consecutive `RUN` instructions.
 -- It ignores the case where multiple commands are chained together (e.g. with
 -- `&&`) because in that case the programmer most likely has deliberately
--- chosen to use multiuple `RUN` instructions. Cases where --mount=xxx flags
+-- chosen to use multiple `RUN` instructions. Cases where --mount=xxx flags
 -- differ are excluded as well.
 rule :: Rule Shell.ParsedShell
 rule = customRule check (emptyState Empty)
diff --git a/src/Hadolint/Rule/DL3060.hs b/src/Hadolint/Rule/DL3060.hs
--- a/src/Hadolint/Rule/DL3060.hs
+++ b/src/Hadolint/Rule/DL3060.hs
@@ -5,7 +5,7 @@
 import Hadolint.Rule
 import qualified Hadolint.Shell as Shell
 import Language.Docker.Syntax
-
+import Data.Set
 
 data Acc
   = Acc
@@ -16,7 +16,22 @@
   | Empty
   deriving (Show)
 
+-- check if a RunMount is a CacheMount
+isCacheMount :: RunMount -> Bool
+isCacheMount (CacheMount _) = True
+isCacheMount _ = False
 
+-- Set of RunMounts must contain at least one CacheMount
+
+containsOneCacheMount :: Set RunMount -> Bool
+containsOneCacheMount = Data.Set.foldl foldable False
+  where 
+    foldable a b = a || isCacheMount b
+
+-- do not flag this as a problem if using a cacheMount
+cacheMount :: RunFlags -> Bool
+cacheMount RunFlags {mount} = containsOneCacheMount mount
+
 rule :: Rule Shell.ParsedShell
 rule = dl3060 <> onbuild dl3060
 {-# INLINEABLE rule #-}
@@ -30,9 +45,10 @@
 
     check line st (From from) =
       st |> modify (rememberStage line from)
-    check line st (Run (RunArgs args _))
+    check line st (Run (RunArgs args flags))
       | foldArguments (Shell.anyCommands yarnInstall) args
-          && foldArguments (Shell.noCommands yarnCacheClean) args =
+          && foldArguments (Shell.noCommands yarnCacheClean) args
+          && not (cacheMount flags) =
         st |> modify (rememberLine line)
       | otherwise = st
     check _ st _ = st
@@ -66,7 +82,7 @@
 yarnCacheClean = Shell.cmdHasArgs "yarn" ["cache", "clean"]
 
 -- | This is needed as placeholder when no FROM statement has yet been
--- envountered.
+-- encountered.
 scratch :: BaseImage
 scratch =
   ( BaseImage
diff --git a/src/Hadolint/Shell.hs b/src/Hadolint/Shell.hs
--- a/src/Hadolint/Shell.hs
+++ b/src/Hadolint/Shell.hs
@@ -69,7 +69,7 @@
     si = mockedSystemInterface [("", "")]
     spec =
       emptyCheckSpec
-        { csFilename = "", -- filename can be ommited because we only want the parse results back
+        { csFilename = "", -- filename can be omitted because we only want the parse results back
           csScript = script,
           csCheckSourced = False,
           csExcludedWarnings = exclusions,
@@ -79,7 +79,8 @@
     script = Text.unpack $ "#!" <> extractShell sh <> "\n" <> printVars <> txt
     exclusions =
       [ 2187, -- exclude the warning about the ash shell not being supported
-        1090 -- requires a directive (shell comment) that can't be expressed in a Dockerfile
+        1090, -- requires a directive (shell comment) that can't be expressed in a Dockerfile
+        1091 -- requires a directive (shell comment) that can't be expressed in a Dockerfile
       ]
 
     extractShell s = fromMaybe "" (listToMaybe . Text.words $ s)
@@ -205,6 +206,9 @@
 hasFlag :: Text.Text -> Command -> Bool
 hasFlag flag Command {flags} = not $ null [f | CmdPart f _ <- flags, f == flag]
 
+countFlag :: Text.Text -> Command -> Int
+countFlag flag Command {flags} = length [ f | CmdPart f _ <- flags, f == flag ]
+
 hasAnyFlag :: [Text.Text] -> Command -> Bool
 hasAnyFlag fs Command {flags} = not $ null [f | CmdPart f _ <- flags, f `elem` fs]
 
@@ -212,14 +216,14 @@
 hasArg arg Command {arguments} = not $ null [a | CmdPart a _ <- arguments, a == arg]
 
 dropFlagArg :: [Text.Text] -> Command -> Command
-dropFlagArg flagsToDrop Command {name, arguments, flags} = Command name filterdArgs flags
+dropFlagArg flagsToDrop Command {name, arguments, flags} = Command name filteredArgs flags
   where
     idsToDrop = Set.fromList [getValueId fId arguments | CmdPart f fId <- flags, f `elem` flagsToDrop]
-    filterdArgs = [arg | arg@(CmdPart _ aId) <- arguments, not (aId `Set.member` idsToDrop)]
+    filteredArgs = [arg | arg@(CmdPart _ aId) <- arguments, not (aId `Set.member` idsToDrop)]
 
--- | given a flag and a command, return list of arguments for that particulat
+-- | given a flag and a command, return list of arguments for that particular
 -- flag. E.g., if the command is `useradd -u 12345 luser` and this function is
--- called for the command `u`, it returns ["12345"].
+-- called for the flag `u`, it returns ["12345"].
 getFlagArg :: Text.Text -> Command -> [Text.Text]
 getFlagArg flag Command {arguments, flags} = extractArgs
   where
@@ -238,6 +242,7 @@
   where
     isStdPipInstall =
       "pip" `Text.isPrefixOf` name
+        && not ("pipenv" `Text.isPrefixOf` name)
         && ["install"] `isInfixOf` getArgs cmd
     isPythonPipInstall =
       "python" `Text.isPrefixOf` name
diff --git a/test/Hadolint/Config/EnvironmentSpec.hs b/test/Hadolint/Config/EnvironmentSpec.hs
--- a/test/Hadolint/Config/EnvironmentSpec.hs
+++ b/test/Hadolint/Config/EnvironmentSpec.hs
@@ -38,7 +38,7 @@
         conf `shouldBe` mempty { partialNoColor = Just True }
 
     -- The expected value in this test changes based on the OS, but in order to
-    -- repect the spec of https://no-color.org, an environment variable set to
+    -- respect the spec of https://no-color.org, an environment variable set to
     -- the empty string must still count as set, at least on platforms that
     -- support setting an environment variable to the empty string (POSIX).
     withJustEnv "NO_COLOR" "" $ do
diff --git a/test/Hadolint/PragmaSpec.hs b/test/Hadolint/PragmaSpec.hs
--- a/test/Hadolint/PragmaSpec.hs
+++ b/test/Hadolint/PragmaSpec.hs
@@ -82,3 +82,28 @@
        in do
             ruleCatchesNot "DL3023" $ Text.unlines dockerFile
             ruleCatchesNot "DL3021" $ Text.unlines dockerFile
+
+  describe "A comment can follow in the same line as a pragma" $ do
+
+    it "pragma followed just by space" $
+      let dockerFile =
+            [ "FROM ubuntu",
+              "# hadolint ignore=DL3002 ",
+              "USER root"
+            ]
+       in ruleCatchesNot "DL3002" $ Text.unlines dockerFile
+
+    it "ingore pragma with comment" $
+      let dockerFile =
+            [ "FROM ubuntu",
+              "# hadolint ignore=DL3002 # foobar",
+              "USER root"
+            ]
+       in ruleCatchesNot "DL3002" $ Text.unlines dockerFile
+
+    it "shell pragma with comment" $
+      let dockerFile =
+            [ "# hadolint shell=powershell # foobar",
+              "FROM ubuntu"
+            ]
+       in ruleCatchesNot "DL1001" $ Text.unlines dockerFile
diff --git a/test/Hadolint/Rule/DL3005Spec.hs b/test/Hadolint/Rule/DL3005Spec.hs
deleted file mode 100644
--- a/test/Hadolint/Rule/DL3005Spec.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-module Hadolint.Rule.DL3005Spec (spec) where
-
-import Data.Default
-import Helpers
-import Test.Hspec
-
-
-spec :: SpecWith ()
-spec = do
-  let ?config = def
-
-  describe "DL3005" $ do
-    it "apt-get dist-upgrade" $ do
-      ruleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"
-      onBuildRuleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"
diff --git a/test/Hadolint/Rule/DL3006Spec.hs b/test/Hadolint/Rule/DL3006Spec.hs
--- a/test/Hadolint/Rule/DL3006Spec.hs
+++ b/test/Hadolint/Rule/DL3006Spec.hs
@@ -17,6 +17,8 @@
       ruleCatchesNot "DL3006" "FROM ruby@sha256:f1dbca0f5dbc9"
     it "untagged digest is not an error" $
       ruleCatchesNot "DL3006" "FROM ruby:2"
+    it "using args is not an error" $
+      ruleCatchesNot "DL3006" "FROM ${VALUE}"
     it "local aliases are OK to be untagged" $
       let dockerFile =
             [ "FROM golang:1.9.3-alpine3.7 AS build",
diff --git a/test/Hadolint/Rule/DL3009Spec.hs b/test/Hadolint/Rule/DL3009Spec.hs
--- a/test/Hadolint/Rule/DL3009Spec.hs
+++ b/test/Hadolint/Rule/DL3009Spec.hs
@@ -10,7 +10,7 @@
 spec = do
   let ?config = def
 
-  describe "DL3009 - Delete the apt-get lists after installing something." $ do
+  describe "DL3009 - Delete the apt lists (/var/lib/apt/lists) after installing something." $ do
     it "apt-get no cleanup" $
       let dockerFile =
             [ "FROM scratch",
@@ -131,3 +131,39 @@
        in do
             ruleCatches "DL3009" $ Text.unlines dockerFile
             onBuildRuleCatches "DL3009" $ Text.unlines dockerFile
+
+    it "apt no cleanup" $
+      let dockerFile =
+            [ "FROM scratch",
+              "RUN apt update && apt install python"
+            ]
+       in do
+            ruleCatches "DL3009" $ Text.unlines dockerFile
+            onBuildRuleCatches "DL3009" $ Text.unlines dockerFile
+
+    it "apt cleanup" $
+      let dockerFile =
+            [ "FROM scratch",
+              "RUN apt update && apt install python && rm -rf /var/lib/apt/lists/*"
+            ]
+       in do
+            ruleCatchesNot "DL3009" $ Text.unlines dockerFile
+            onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile
+
+    it "aptitude no cleanup" $
+      let dockerFile =
+            [ "FROM scratch",
+              "RUN aptitude update && aptitude install python"
+            ]
+       in do
+            ruleCatches "DL3009" $ Text.unlines dockerFile
+            onBuildRuleCatches "DL3009" $ Text.unlines dockerFile
+
+    it "aptitude cleanup" $
+      let dockerFile =
+            [ "FROM scratch",
+              "RUN aptitude update && aptitude install python && rm -rf /var/lib/apt/lists/*"
+            ]
+       in do
+            ruleCatchesNot "DL3009" $ Text.unlines dockerFile
+            onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile
diff --git a/test/Hadolint/Rule/DL3011Spec.hs b/test/Hadolint/Rule/DL3011Spec.hs
--- a/test/Hadolint/Rule/DL3011Spec.hs
+++ b/test/Hadolint/Rule/DL3011Spec.hs
@@ -9,6 +9,13 @@
 spec = do
   let ?config = def
 
-  describe "EXPOSE rules" $ do
-    it "invalid port" $ ruleCatches "DL3011" "EXPOSE 80000"
-    it "valid port" $ ruleCatchesNot "DL3011" "EXPOSE 60000"
+  describe "DL3011 - Valid UNIX ports range from 0 to 65535" $ do
+    describe "DL3011 - single port" $ do
+      it "invalid port" $ ruleCatches "DL3011" "EXPOSE 80000"
+      it "valid port" $ ruleCatchesNot "DL3011" "EXPOSE 60000"
+      it "valid port variable" $ ruleCatchesNot "DL3011" "EXPOSE ${FOOBAR}"
+
+    describe "DL3011 - port range" $ do
+      it "invalid port in range" $ ruleCatches "DL3011" "EXPOSE 40000-80000/tcp"
+      it "valid port range" $ ruleCatchesNot "DL3011" "EXPOSE 40000-60000/tcp"
+      it "valid port range variable" $ ruleCatchesNot "DL3011" "EXPOSE 40000-${FOOBAR}"
diff --git a/test/Hadolint/Rule/DL3013Spec.hs b/test/Hadolint/Rule/DL3013Spec.hs
--- a/test/Hadolint/Rule/DL3013Spec.hs
+++ b/test/Hadolint/Rule/DL3013Spec.hs
@@ -165,3 +165,6 @@
       onBuildRuleCatchesNot
         "DL3013"
         "RUN pip install --index-url https://user:pass@eg.com/foo --extra-index-url https://user:pass@ex-eg.io/foo foobar==1.0.0"
+    it "pipenv is not pip" $ do
+      ruleCatchesNot "DL3013" "RUN pipenv install black"
+      onBuildRuleCatchesNot "DL3013" "RUN pipenv install black"
diff --git a/test/Hadolint/Rule/DL3014Spec.hs b/test/Hadolint/Rule/DL3014Spec.hs
--- a/test/Hadolint/Rule/DL3014Spec.hs
+++ b/test/Hadolint/Rule/DL3014Spec.hs
@@ -13,12 +13,18 @@
     it "apt-get auto yes" $ do
       ruleCatches "DL3014" "RUN apt-get install python"
       onBuildRuleCatches "DL3014" "RUN apt-get install python"
+
+    describe "Insufficient Quiet Levels" $ do
+      it "apt-get -q" $ do
+        ruleCatches "DL3014" "RUN apt-get install -q python"
+        onBuildRuleCatches "DL3014" "RUN apt-get install -q python"
+      it "apt-get --quiet" $ do
+        ruleCatches "DL3014" "RUN apt-get install --quiet python"
+        onBuildRuleCatches "DL3014" "RUN apt-get install -q python"
+
     it "apt-get yes shortflag" $ do
       ruleCatchesNot "DL3014" "RUN apt-get install -yq python"
       onBuildRuleCatchesNot "DL3014" "RUN apt-get install -yq python"
-    it "apt-get yes quiet level 2 implies -y" $ do
-      ruleCatchesNot "DL3014" "RUN apt-get install -qq python"
-      onBuildRuleCatchesNot "DL3014" "RUN apt-get install -qq python"
     it "apt-get yes different pos" $ do
       ruleCatchesNot "DL3014" "RUN apt-get install -y python"
       onBuildRuleCatchesNot "DL3014" "RUN apt-get install -y python"
@@ -31,3 +37,18 @@
     it "apt-get with assume-yes" $ do
       ruleCatchesNot "DL3014" "RUN apt-get --assume-yes install python"
       onBuildRuleCatchesNot "DL3014" "RUN apt-get --assume-yes install python"
+
+    describe "Quiet level 2 implies assume-yes" $ do
+      it "apt-get -qq" $ do
+        ruleCatchesNot "DL3014" "RUN apt-get install -qq python"
+        onBuildRuleCatchesNot "DL3014" "RUN apt-get install -qq python"
+      it "apt-get -q -q" $ do
+        ruleCatchesNot "DL3014" "RUN apt-get install -q -q python"
+        onBuildRuleCatchesNot "DL3014" "RUN apt-get install -q -q python"
+      it "apt-get -q=2" $ do
+        ruleCatchesNot "DL3014" "RUN apt-get install -q=2 python"
+        onBuildRuleCatchesNot "DL3014" "RUN apt-get install -q=2 python"
+      it "apt-get --quiet --quiet" $ do
+        ruleCatchesNot "DL3014" "RUN apt-get install --quiet --quiet python"
+        onBuildRuleCatchesNot "DL3014"
+          "RUN apt-get install --quiet --quiet python"
diff --git a/test/Hadolint/Rule/DL3022Spec.hs b/test/Hadolint/Rule/DL3022Spec.hs
--- a/test/Hadolint/Rule/DL3022Spec.hs
+++ b/test/Hadolint/Rule/DL3022Spec.hs
@@ -30,3 +30,20 @@
             ]
        in ruleCatchesNot "DL3022" $ Text.unlines dockerFile
     it "don't warn on external images" $ ruleCatchesNot "DL3022" "COPY --from=haskell:latest bar ."
+    it "warn on invalid stage count as --from=<count>" $ ruleCatches "DL3022" "COPY --from=0 bar ."
+    it "don't warn on valid stage count as --from=<count>" $
+      let dockerFile =
+            [ "FROM scratch as build",
+              "RUN foo",
+              "FROM node",
+              "COPY --from=0 foo ."
+            ]
+      in ruleCatchesNot "DL3022" $ Text.unlines dockerFile
+    it "don't warn on valid stage count as --from=<count>" $
+      let dockerFile =
+            [ "FROM scratch",
+              "RUN foo",
+              "FROM node",
+              "COPY --from=0 foo ."
+            ]
+      in ruleCatchesNot "DL3022" $ Text.unlines dockerFile
diff --git a/test/Hadolint/Rule/DL3027Spec.hs b/test/Hadolint/Rule/DL3027Spec.hs
--- a/test/Hadolint/Rule/DL3027Spec.hs
+++ b/test/Hadolint/Rule/DL3027Spec.hs
@@ -10,7 +10,7 @@
 spec = do
   let ?config = def
 
-  describe "DL3027 - Do not use `apt` as it is meant to be a end-user tool, use `apt-get` or `apt-cache` instead" $ do
+  describe "DL3027 - Do not use `apt` as it is meant to be an end-user tool, use `apt-get` or `apt-cache` instead" $ do
     it "apt" $
       let dockerFile =
             [ "FROM ubuntu",
diff --git a/test/Hadolint/Rule/DL3028Spec.hs b/test/Hadolint/Rule/DL3028Spec.hs
--- a/test/Hadolint/Rule/DL3028Spec.hs
+++ b/test/Hadolint/Rule/DL3028Spec.hs
@@ -39,6 +39,15 @@
         it "does not warn on --version with =" $ do
           ruleCatchesNot "DL3028" "RUN gem install bundler --version='2.0.1'"
           onBuildRuleCatchesNot "DL3028" "RUN gem install bundler --version='2.0.1'"
-        it "does not warn on extra flags" $ do
+        it "does not warn when using extra flags with equal sign and double dashes" $ do
           ruleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries=true"
           onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries=true"
+        it "does not warn when using extra flags with double dashes" $ do
+          ruleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries true"
+          onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries true"
+        it "does not warn when using extra flags" $ do
+          ruleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 --use-system-libraries true"
+          onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 --use-system-libraries true"
+        it "does not warn when using extra flags with equal sign" $ do
+          ruleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 --use-system-libraries=true"
+          onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 --use-system-libraries=true"
diff --git a/test/Hadolint/Rule/DL3033Spec.hs b/test/Hadolint/Rule/DL3033Spec.hs
--- a/test/Hadolint/Rule/DL3033Spec.hs
+++ b/test/Hadolint/Rule/DL3033Spec.hs
@@ -10,7 +10,7 @@
   let ?config = def
 
   describe "DL3033 - Specify version with `yum install -y <package>-<version>`" $ do
-    it "not ok wihout yum version pinning" $ do
+    it "not ok without yum version pinning" $ do
       ruleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"
       onBuildRuleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"
     it "ok with yum version pinning" $ do
@@ -18,7 +18,7 @@
       ruleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"
       onBuildRuleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"
       onBuildRuleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"
-    it "not ok wihout yum version pinning - modules" $ do
+    it "not ok without yum version pinning - modules" $ do
       ruleCatches "DL3033" "RUN yum module install -y tomcat && yum clean all"
       onBuildRuleCatches "DL3033" "RUN yum module install -y tomcat && yum clean all"
     it "ok with yum version pinning - modules" $ do
diff --git a/test/Hadolint/Rule/DL3041Spec.hs b/test/Hadolint/Rule/DL3041Spec.hs
--- a/test/Hadolint/Rule/DL3041Spec.hs
+++ b/test/Hadolint/Rule/DL3041Spec.hs
@@ -14,6 +14,10 @@
       ruleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"
       ruleCatches "DL3041" "RUN microdnf install -y tomcat && microdnf clean all"
       onBuildRuleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"
+    it "not ok without dnf version pinning - package name with `-`" $ do
+      ruleCatches "DL3041" "RUN dnf install -y rpm-sign && dnf clean all"
+      ruleCatches "DL3041" "RUN microdnf install -y rpm-sign && microdnf clean all"
+      onBuildRuleCatches "DL3041" "RUN dnf install -y rpm-sign && dnf clean all"
     it "ok with dnf version pinning" $ do
       ruleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"
       ruleCatchesNot "DL3041" "RUN microdnf install -y tomcat-9.0.1 && microdnf clean all"
@@ -21,7 +25,14 @@
       onBuildRuleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"
       onBuildRuleCatchesNot "DL3041" "RUN microdnf install -y tomcat-9.0.1 && microdnf clean all"
       onBuildRuleCatchesNot "DL3041" "RUN notdnf install tomcat"
-    it "not ok without dnf version pinning - moudles" $ do
+    it "ok with dnf version pinning - package name with `-`" $ do
+      ruleCatchesNot "DL3041" "RUN dnf install -y rpm-sign-4.16.1.3 && dnf clean all"
+      ruleCatchesNot "DL3041" "RUN microdnf install -y rpm-sign-4.16.1.3 && microdnf clean all"
+      ruleCatchesNot "DL3041" "RUN notdnf install rpm-sign"
+      onBuildRuleCatchesNot "DL3041" "RUN dnf install -y rpm-sign-4.16.1.3 && dnf clean all"
+      onBuildRuleCatchesNot "DL3041" "RUN microdnf install -y rpm-sign-4.16.1.3 && microdnf clean all"
+      onBuildRuleCatchesNot "DL3041" "RUN notdnf install rpm-sign"
+    it "not ok without dnf version pinning - modules" $ do
       ruleCatches "DL3041" "RUN dnf module install -y tomcat && dnf clean all"
       ruleCatches "DL3041" "RUN microdnf module install -y tomcat && microdnf clean all"
       onBuildRuleCatches "DL3041" "RUN dnf module install -y tomcat && dnf clean all"
diff --git a/test/Hadolint/Rule/DL3053Spec.hs b/test/Hadolint/Rule/DL3053Spec.hs
--- a/test/Hadolint/Rule/DL3053Spec.hs
+++ b/test/Hadolint/Rule/DL3053Spec.hs
@@ -12,7 +12,7 @@
 spec = do
   let ?config = def { labelSchema = Map.fromList [("datelabel", Rule.Rfc3339)] }
 
-  describe "DL3053 - Label `<label>` is not a valid time format - must be conform to RFC3339." $ do
+  describe "DL3053 - Label `<label>` is not a valid time format - must conform to RFC3339." $ do
     it "not ok with label not containing RFC3339 date" $ do
       ruleCatches "DL3053" "LABEL datelabel=\"not-date\""
       onBuildRuleCatches "DL3053" "LABEL datelabel=\"not-date\""
diff --git a/test/Hadolint/Rule/DL3058Spec.hs b/test/Hadolint/Rule/DL3058Spec.hs
--- a/test/Hadolint/Rule/DL3058Spec.hs
+++ b/test/Hadolint/Rule/DL3058Spec.hs
@@ -12,7 +12,7 @@
 spec = do
   let ?config = def { labelSchema = Map.fromList [("maintainer", Rule.Email)] }
 
-  describe "DL3058 - Label `<label>` is not a valid email format - must be conform to RFC5322." $ do
+  describe "DL3058 - Label `<label>` is not a valid email format - must conform to RFC5322." $ do
     it "not ok with label not containing valid email" $ do
       ruleCatches "DL3058" "LABEL maintainer=\"not-email\""
       onBuildRuleCatches "DL3058" "LABEL maintainer=\"not-email\""
diff --git a/test/Hadolint/Rule/ShellcheckSpec.hs b/test/Hadolint/Rule/ShellcheckSpec.hs
--- a/test/Hadolint/Rule/ShellcheckSpec.hs
+++ b/test/Hadolint/Rule/ShellcheckSpec.hs
@@ -11,7 +11,7 @@
   let ?config = def
 
   describe "Shellcheck" $ do
-    it "runs shellchek on RUN instructions" $ do
+    it "runs shellcheck on RUN instructions" $ do
       assertChecks "RUN echo $MISSING_QUOTES" failsShellcheck
       assertOnBuildChecks "RUN echo $MISSING_QUOTES" failsShellcheck
     it "not warns on valid scripts" $ do
@@ -88,7 +88,7 @@
               ]
        in do
             assertChecks dockerFile passesShellcheck
-            -- This is debatable, as it should actaully pass, but detecting it correctly
+            -- This is debatable, as it should actually pass, but detecting it correctly
             -- is quite difficult
             assertOnBuildChecks dockerFile failsShellcheck
     it "Resets the SHELL to sh after a FROM" $
