diff --git a/hadolint.cabal b/hadolint.cabal
--- a/hadolint.cabal
+++ b/hadolint.cabal
@@ -2,10 +2,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: dee8c05033f3341030b3d3436b7ec3db7ceb472be45eb05a90bce71111365c2a
+-- hash: 1968b5091609b7ed9433b56e82cdefd1ad484dce803b5f213ac9a3438de4313f
 
 name:           hadolint
-version:        1.7.1
+version:        1.7.2
 synopsis:       Dockerfile Linter JavaScript API
 description:    A smarter Dockerfile linter that helps you build best practice Docker images.
 category:       Development
@@ -36,6 +36,7 @@
     , containers
     , language-docker >=6.0.1 && <7
     , megaparsec >=6.4
+    , mtl
     , split >=0.2
     , text
     , void
diff --git a/src/Hadolint/Bash.hs b/src/Hadolint/Bash.hs
--- a/src/Hadolint/Bash.hs
+++ b/src/Hadolint/Bash.hs
@@ -1,16 +1,101 @@
 module Hadolint.Bash where
 
+import Control.Monad.Writer (Writer, execWriter, tell)
 import Data.Functor.Identity (runIdentity)
+import Data.List (nub)
+import Data.Maybe (mapMaybe)
+import qualified Data.Text as Text
+import qualified ShellCheck.AST
+import ShellCheck.AST (Id(..), Token(..))
+import qualified ShellCheck.ASTLib
 import ShellCheck.Checker
 import ShellCheck.Interface
+import qualified ShellCheck.Parser
 
-shellcheck :: String -> [Comment]
-shellcheck bashScript = map comment $ crComments $ runIdentity $ checkScript si spec
+data ParsedBash = ParsedBash
+    { original :: Text.Text
+    , parsed :: ParseResult
+    }
+
+shellcheck :: ParsedBash -> [Comment]
+shellcheck (ParsedBash txt _) = map comment $ crComments $ runIdentity $ checkScript si spec
   where
     comment (PositionedComment _ _ c) = c
     si = mockedSystemInterface [("", "")]
     spec = CheckSpec filename script sourced exclusions (Just Bash)
-    script = "#!/bin/bash\n" ++ bashScript
+    script = "#!/bin/bash\n" ++ Text.unpack txt
     filename = "" -- filename can be ommited because we only want the parse results back
     sourced = False
     exclusions = []
+
+parseShell :: Text.Text -> ParsedBash
+parseShell txt =
+    ParsedBash
+    { original = txt
+    , parsed =
+          runIdentity $
+          ShellCheck.Parser.parseScript
+              (mockedSystemInterface [("", "")])
+              ParseSpec
+              { psFilename = "" -- There is no filename
+              , psScript = "#!/bin/bash\n" ++ Text.unpack txt
+              , psCheckSourced = False
+              }
+    }
+
+findCommands :: ParsedBash -> [Token]
+findCommands (ParsedBash _ ast) =
+    case prRoot ast of
+        Nothing -> []
+        Just script -> nub . execWriter $ ShellCheck.AST.doAnalysis extract script
+  where
+    extract :: Token -> Writer [Token] ()
+    extract t =
+        case ShellCheck.ASTLib.getCommand t of
+            Nothing -> return ()
+            Just cmd -> tell [cmd]
+
+allCommands :: (Token -> Bool) -> ParsedBash -> Bool
+allCommands check script = all check (findCommands script)
+
+noCommands :: (Token -> Bool) -> ParsedBash -> Bool
+noCommands check = allCommands (not . check)
+
+getCommandName :: Token -> Maybe String
+getCommandName = ShellCheck.ASTLib.getCommandName
+
+findCommandNames :: ParsedBash -> [String]
+findCommandNames = mapMaybe getCommandName . findCommands
+
+cmdHasArgs :: String -> [String] -> Token -> Bool
+cmdHasArgs command arguments token@T_SimpleCommand {}
+    | ShellCheck.ASTLib.getCommandName token /= Just command = False
+    | otherwise = not $ null [arg | arg <- getAllArgs token, arg `elem` arguments]
+cmdHasArgs _ _ _ = False
+
+getAllArgs :: Token -> [String]
+getAllArgs (T_SimpleCommand _ _ (_:allArgs)) = concatMap ShellCheck.ASTLib.oversimplify allArgs
+getAllArgs _ = []
+
+getArgsNoFlags :: Token -> [String]
+getArgsNoFlags cmd@(T_SimpleCommand _ _ (_:allArgs)) = concatMap ShellCheck.ASTLib.oversimplify args
+  where
+    flags = [t | (t, _) <- getAllFlags cmd]
+    args = [a | a <- allArgs, a `notElem` flags]
+getArgsNoFlags _ = []
+
+getAllFlags :: Token -> [(Token, String)]
+getAllFlags cmd@T_SimpleCommand {} = [(t, f) | (t, f) <- ShellCheck.ASTLib.getAllFlags cmd, f /= ""]
+getAllFlags _ = []
+
+hasFlag :: String -> Token -> Bool
+hasFlag flag = any (\(_, f) -> f == flag) . getAllFlags
+
+dropFlagArg :: [String] -> Token -> Token
+dropFlagArg flags cmd@(T_SimpleCommand cid b allArgs) = T_SimpleCommand cid b filterdArgs
+  where
+    filterdArgs = [arg | arg <- allArgs, isNotNextToken arg]
+    isNotNextToken arg = ShellCheck.AST.getId arg `notElem` findTokensToDrop
+    findTokensToDrop = [next (ShellCheck.AST.getId t) | (t, f) <- getAllFlags cmd, f `elem` flags]
+    next (Id i) = Id (i + 2)
+dropFlagArg _ token = token
diff --git a/src/Hadolint/Rules.hs b/src/Hadolint/Rules.hs
--- a/src/Hadolint/Rules.hs
+++ b/src/Hadolint/Rules.hs
@@ -6,8 +6,7 @@
 import Control.Arrow ((&&&))
 import Data.List (dropWhile, isInfixOf, isPrefixOf, mapAccumL)
 import Data.List.NonEmpty (toList)
-import Data.List.Split (splitOneOf)
-import Hadolint.Bash
+import qualified Hadolint.Bash as Bash
 import Language.Docker.Syntax
 
 import Data.Semigroup ((<>))
@@ -42,33 +41,49 @@
 
 type IgnoreRuleParser = Megaparsec.Parsec Void Text.Text
 
+type ParsedFile = [InstructionPos Bash.ParsedBash]
+
 link :: Metadata -> Text.Text
 link (Metadata code _ _)
     | "SC" `Text.isPrefixOf` code = "https://github.com/koalaman/shellcheck/wiki/" <> code
     | "DL" `Text.isPrefixOf` code = "https://github.com/hadolint/hadolint/wiki/" <> code
     | otherwise = "https://github.com/hadolint/hadolint"
 
--- a Rule takes a Dockerfile and returns the executed checks
-type Rule = Dockerfile -> [RuleCheck]
+-- a Rule takes a Dockerfile with parsed bash and returns the executed checks
+type Rule = ParsedFile -> [RuleCheck]
 
 -- Apply a function on each instruction and create a check
 -- for the according line number
 mapInstructions ::
-       Metadata -> (state -> Linenumber -> Instruction Text.Text -> (state, Bool)) -> state -> Rule
+       Metadata
+    -> (state -> Linenumber -> Instruction Bash.ParsedBash -> (state, Bool))
+    -> state
+    -> Rule
 mapInstructions metadata f initialState dockerfile =
     let (_, results) = mapAccumL applyRule initialState dockerfile
     in results
   where
+    applyRule state (InstructionPos (OnBuild i) source linenumber) =
+        applyWithState state source linenumber i -- All rules applying to instructions also apply to ONBUILD,
+                                                 -- so we unwrap the OnBuild constructor and check directly the inner
+                                                 -- instruction
     applyRule state (InstructionPos i source linenumber) =
-        let (newState, res) = f state linenumber i
+        applyWithState state source linenumber i -- Otherwise, normal instructions are not unwrapped
+    applyWithState state source linenumber instruction =
+        let (newState, res) = f state linenumber instruction
         in (newState, RuleCheck metadata source linenumber res)
 
-instructionRule :: Text.Text -> Severity -> Text.Text -> (Instruction Text.Text -> Bool) -> Rule
+instructionRule ::
+       Text.Text -> Severity -> Text.Text -> (Instruction Bash.ParsedBash -> Bool) -> Rule
 instructionRule code severity message check =
     instructionRuleLine code severity message (const check)
 
 instructionRuleLine ::
-       Text.Text -> Severity -> Text.Text -> (Linenumber -> Instruction Text.Text -> Bool) -> Rule
+       Text.Text
+    -> Severity
+    -> Text.Text
+    -> (Linenumber -> Instruction Bash.ParsedBash -> Bool)
+    -> Rule
 instructionRuleLine code severity message check =
     instructionRuleState code severity message checkAndDropState ()
   where
@@ -78,7 +93,7 @@
        Text.Text
     -> Severity
     -> Text.Text
-    -> (state -> Linenumber -> Instruction Text.Text -> (state, Bool))
+    -> (state -> Linenumber -> Instruction Bash.ParsedBash -> (state, Bool))
     -> state
     -> Rule
 instructionRuleState code severity message = mapInstructions (Metadata code severity message)
@@ -86,22 +101,21 @@
 withState :: a -> b -> (a, b)
 withState st res = (st, res)
 
-argumentsRule :: ([String] -> a) -> Arguments Text.Text -> a
+argumentsRule :: (Bash.ParsedBash -> a) -> Arguments Bash.ParsedBash -> a
 argumentsRule applyRule args =
     case args of
-        ArgumentsText as -> applyRule . normalizeArgs $ as
-        ArgumentsList as -> applyRule . normalizeArgs $ as
-  where
-    normalizeArgs = words . Text.unpack
+        ArgumentsText as -> applyRule as
+        ArgumentsList as -> applyRule as
 
 -- Enforce rules on a dockerfile and return failed checks
 analyze :: [Rule] -> Dockerfile -> [RuleCheck]
-analyze list dockerfile = filter failed $ concat [r dockerfile | r <- list]
+analyze list dockerfile = filter failed $ concat [r parsedFile | r <- list]
   where
     failed RuleCheck {metadata = Metadata {code}, linenumber, success} =
         not success && not (wasIgnored code linenumber)
     wasIgnored c ln = not $ null [line | (line, codes) <- allIgnores, line == ln, c `elem` codes]
     allIgnores = ignored dockerfile
+    parsedFile = map (fmap Bash.parseShell) dockerfile
 
 ignored :: Dockerfile -> [(Linenumber, [Text.Text])]
 ignored dockerfile =
@@ -165,7 +179,7 @@
 commentMetadata (ShellCheck.Interface.Comment severity code message) =
     Metadata (Text.pack ("SC" ++ show code)) severity (Text.pack message)
 
-shellcheckBash :: Dockerfile -> [RuleCheck]
+shellcheckBash :: ParsedFile -> [RuleCheck]
 shellcheckBash = concatMap check
   where
     check (InstructionPos (Run args) source linenumber) =
@@ -173,32 +187,28 @@
     check _ = []
     applyRule source linenumber args =
         rmDup [RuleCheck m source linenumber False | m <- convert args]
-    convert args = [commentMetadata c | c <- shellcheck $ unwords args]
+    convert args = [commentMetadata c | c <- Bash.shellcheck args]
     rmDup :: [RuleCheck] -> [RuleCheck]
     rmDup [] = []
     rmDup (x:xs) = x : rmDup (filter (\y -> metadata x /= metadata y) xs)
 
--- Split different bash commands
-bashCommands :: [String] -> [[String]]
-bashCommands = splitOneOf [";", "|", "&&"]
-
-allFromImages :: Dockerfile -> [(Linenumber, BaseImage)]
+allFromImages :: ParsedFile -> [(Linenumber, BaseImage)]
 allFromImages dockerfile = [(l, f) | (l, From f) <- instr]
   where
     instr = fmap (lineNumber &&& instruction) dockerfile
 
-allAliasedImages :: Dockerfile -> [(Linenumber, ImageAlias)]
+allAliasedImages :: ParsedFile -> [(Linenumber, ImageAlias)]
 allAliasedImages dockerfile =
     [(l, alias) | (l, Just alias) <- map extractAlias (allFromImages dockerfile)]
   where
     extractAlias (l, f) = (l, fromAlias f)
 
-allImageNames :: Dockerfile -> [(Linenumber, Text.Text)]
+allImageNames :: ParsedFile -> [(Linenumber, Text.Text)]
 allImageNames dockerfile = [(l, fromName baseImage) | (l, baseImage) <- allFromImages dockerfile]
 
 -- | Returns a list of all image aliases in FROM instructions that
 --  are defined before the given line number.
-previouslyDefinedAliases :: Linenumber -> Dockerfile -> [Text.Text]
+previouslyDefinedAliases :: Linenumber -> ParsedFile -> [Text.Text]
 previouslyDefinedAliases line dockerfile =
     [i | (l, ImageAlias i) <- allAliasedImages dockerfile, l < line]
 
@@ -245,8 +255,8 @@
     check _ = True
 
 -- Check if a command contains a program call in the Run instruction
-usingProgram :: String -> [String] -> Bool
-usingProgram prog args = or [True | cmd:_ <- bashCommands args, cmd == prog]
+usingProgram :: String -> Bash.ParsedBash -> Bool
+usingProgram prog args = not $ null [cmd | cmd <- Bash.findCommandNames args, cmd == prog]
 
 multipleCmds :: Rule
 multipleCmds = instructionRuleState code severity message check Nothing
@@ -284,7 +294,8 @@
         let newArgs = extractCommands args
             newState = Set.union state newArgs
         in withState newState (Set.size newState < 2)
-    extractCommands args = Set.fromList [w | w <- args, w == "curl" || w == "wget"]
+    extractCommands args =
+        Set.fromList [w | w <- Bash.findCommandNames args, w == "curl" || w == "wget"]
 
 invalidCmd :: Rule
 invalidCmd = instructionRule code severity message check
@@ -294,10 +305,9 @@
     message =
         "For some bash commands it makes no sense running them in a Docker container like `ssh`, \
         \`vim`, `shutdown`, `service`, `ps`, `free`, `top`, `kill`, `mount`, `ifconfig`"
-    check (Run args) = argumentsRule applyRule args
+    check (Run args) = argumentsRule detectInvalid args
     check _ = True
-    applyRule (arg:_) = arg `notElem` invalidCmds
-    applyRule _ = True
+    detectInvalid args = null [arg | arg <- Bash.findCommandNames args, arg `elem` invalidCmds]
     invalidCmds = ["ssh", "vim", "shutdown", "service", "ps", "free", "top", "kill", "mount"]
 
 noRootUser :: Rule
@@ -338,7 +348,7 @@
     code = "DL3005"
     severity = ErrorC
     message = "Do not use apt-get upgrade or dist-upgrade"
-    check (Run args) = argumentsRule (not . isInfixOf ["apt-get", "upgrade"]) args
+    check (Run args) = argumentsRule (Bash.noCommands (Bash.cmdHasArgs "apt-get" ["upgrade"])) args
     check _ = True
 
 noUntagged :: Rule
@@ -371,19 +381,20 @@
     message =
         "Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get \
         \install <package>=<version>`"
-    check (Run args) = argumentsRule (\as -> and [versionFixed p | p <- aptGetPackages as]) args
+    check (Run args) = argumentsRule (all versionFixed . aptGetPackages) args
     check _ = True
     versionFixed package = "=" `isInfixOf` package
 
-aptGetPackages :: [String] -> [String]
+aptGetPackages :: Bash.ParsedBash -> [String]
 aptGetPackages args =
-    concat
-        [ filter noOption (dropOptionsWithArg ["-t", "--target-release"] cmd)
-        | cmd <- bashCommands args
-        , isAptGetInstall cmd
-        ]
+    [ arg
+    | cmd <- dropTarget <$> Bash.findCommands args
+    , arg <- Bash.getArgsNoFlags cmd
+    , Bash.cmdHasArgs "apt-get" ["install"] cmd
+    , arg /= "install"
+    ]
   where
-    noOption arg = arg `notElem` ["apt-get", "install"] && not ("-" `isPrefixOf` arg)
+    dropTarget = Bash.dropFlagArg ["t", "target-release"]
 
 aptGetCleanup :: Rule
 aptGetCleanup dockerfile = instructionRuleState code severity message check Nothing dockerfile
@@ -398,13 +409,16 @@
     --   or if it is used as the base image for another FROM clause.
     check _ line f@(From _) = withState (Just (line, f)) True -- Remember the last FROM instruction found
     check st@(Just (line, From baseimage)) _ (Run args) =
-        withState st (argumentsRule (applyRule line baseimage) args)
+        withState st (argumentsRule (didNotForgetToCleanup line baseimage) args)
     check st _ _ = withState st True
-    applyRule line baseimage args
+    -- Check all commands in the script for the presence of apt-get update
+    -- If the command is there, then we need to verify that the user is also removing the lists folder
+    didNotForgetToCleanup line baseimage args
         | not (hasUpdate args) || not (imageIsUsed line baseimage) = True
         | otherwise = hasCleanup args
-    hasCleanup cmd = ["rm", "-rf", "/var/lib/apt/lists/*"] `isInfixOf` cmd
-    hasUpdate cmd = ["apt-get", "update"] `isInfixOf` cmd
+    hasCleanup args =
+        any (Bash.cmdHasArgs "rm" ["-rf", "/var/lib/apt/lists/*"]) (Bash.findCommands args)
+    hasUpdate args = any (Bash.cmdHasArgs "apt-get" ["update"]) (Bash.findCommands args)
     imageIsUsed line baseimage = isLastImage line baseimage || imageIsUsedLater line baseimage
     isLastImage line baseimage =
         case reverse (allFromImages dockerfile) of
@@ -416,24 +430,15 @@
             Just (ImageAlias alias) ->
                 alias `elem` [i | (l, i) <- allImageNames dockerfile, l > line]
 
-dropOptionsWithArg :: [String] -> [String] -> [String]
-dropOptionsWithArg _ [] = []
-dropOptionsWithArg os (x:xs)
-    | x `elem` os = dropOptionsWithArg os (drop 1 xs)
-    | otherwise = x : dropOptionsWithArg os xs
-
 noApkUpgrade :: Rule
 noApkUpgrade = instructionRule code severity message check
   where
     code = "DL3017"
     severity = ErrorC
     message = "Do not use apk upgrade"
-    check (Run args) = argumentsRule (not . isInfixOf ["apk", "upgrade"]) args
+    check (Run args) = argumentsRule (Bash.noCommands (Bash.cmdHasArgs "apk" ["upgrade"])) args
     check _ = True
 
-isApkAdd :: [String] -> Bool
-isApkAdd cmd = ["apk"] `isInfixOf` cmd && ["add"] `isInfixOf` cmd
-
 apkAddVersionPinned :: Rule
 apkAddVersionPinned = instructionRule code severity message check
   where
@@ -445,16 +450,16 @@
     check _ = True
     versionFixed package = "=" `isInfixOf` package
 
-apkAddPackages :: [String] -> [String]
+apkAddPackages :: Bash.ParsedBash -> [String]
 apkAddPackages args =
-    concat
-        [ filter noOption (dropOptionsWithArg ["-t", "--virtual"] cmd)
-        | cmd <- bashCommands args
-        , isApkAdd cmd
-        ]
+    [ arg
+    | cmd <- dropTarget <$> Bash.findCommands args
+    , arg <- Bash.getArgsNoFlags cmd
+    , Bash.cmdHasArgs "apk" ["add"] cmd
+    , arg /= "add"
+    ]
   where
-    noOption arg = arg `notElem` options && not ("--" `isPrefixOf` arg)
-    options = ["apk", "add", "-q", "-p", "-v", "-f", "-t"]
+    dropTarget = Bash.dropFlagArg ["t", "virtual"]
 
 apkAddNoCache :: Rule
 apkAddNoCache = instructionRule code severity message check
@@ -464,9 +469,9 @@
     message =
         "Use the `--no-cache` switch to avoid the need to use `--update` and remove \
         \`/var/cache/apk/*` when done installing packages"
-    check (Run args) = argumentsRule (\as -> not (isApkAdd as) || hasNoCacheOption as) args
+    check (Run args) = argumentsRule (Bash.noCommands forgotCacheOption) args
     check _ = True
-    hasNoCacheOption cmd = ["--no-cache"] `isInfixOf` cmd
+    forgotCacheOption cmd = Bash.cmdHasArgs "apk" ["add"] cmd && not (Bash.hasFlag "no-cache" cmd)
 
 useAdd :: Rule
 useAdd = instructionRule code severity message check
@@ -502,46 +507,27 @@
     message =
         "Pin versions in pip. Instead of `pip install <package>` use `pip install \
         \<package>==<version>`"
-    check (Run args) =
-        argumentsRule (\as -> not (isPipInstall as) || all versionFixed (packages as)) args
+    check (Run args) = argumentsRule (Bash.noCommands forgotToPinVersion) args
     check _ = True
-    isPipInstall :: [String] -> Bool
+    forgotToPinVersion cmd = isPipInstall cmd && not (all versionFixed (packages cmd))
+    -- Check if the command is a pip* install command, and that specific pacakges are being listed
     isPipInstall cmd =
-        case getInstallArgs cmd of
-            Nothing -> False
-            Just args -> not (["-r"] `isInfixOf` args || ["."] `isInfixOf` args)
-    packages cmd =
-        case getInstallArgs cmd of
-            Nothing -> []
-            Just args -> findPackages args
-    getInstallArgs = stripInstallPrefix isInstallCommand
-    isInstallCommand ('p':'i':'p':_) = True
-    isInstallCommand _ = False
+        case Bash.getCommandName cmd of
+            Just ('p':'i':'p':_) -> relevantInstall cmd
+            _ -> False
+    -- If the user is installing requirements from a file or just the local module, then we are not interested
+    -- in running this rule
+    relevantInstall cmd =
+        ["install"] `isInfixOf` Bash.getAllArgs cmd &&
+        not (["-r"] `isInfixOf` Bash.getAllArgs cmd || ["."] `isInfixOf` Bash.getAllArgs cmd)
+    packages cmd = stripInstallPrefix (Bash.getArgsNoFlags cmd)
     versionFixed package = hasVersionSymbol package || isVersionedGit package
     isVersionedGit package = "git+http" `isInfixOf` package && "@" `isInfixOf` package
-    versionSymbols = ["==", ">=", "<=", ">", "<", "!="]
+    versionSymbols = ["==", ">=", "<=", ">", "<", "!=", "~=", "==="]
     hasVersionSymbol package = or [s `isInfixOf` package | s <- versionSymbols]
 
--- | Returns all the packages after pip install
-findPackages :: [String] -> [String]
-findPackages = takeWhile (not . isEnd) . dropWhile isFlag
-  where
-    isEnd word = isFlag word || word `elem` ["&&", "||", ";", "|"]
-    isFlag ('-':_) = True
-    isFlag _ = False
-
-stripInstallPrefix :: (String -> Bool) -> [String] -> Maybe [String]
-stripInstallPrefix isCommand args =
-    if ["install"] `isPrefixOf` dropUntilInstall
-        then dropUntilInstall |> drop 1 |> Just
-        else Nothing
-  where
-    dropUntilInstall =
-        args |> -- using a pipiline for readability
-        dropWhile (not . isCommand) |>
-        drop 1 |>
-        dropWhile (/= "install")
-    a |> f = f a
+stripInstallPrefix :: [String] -> [String]
+stripInstallPrefix = dropWhile (== "install")
 
 {-|
   Rule for pinning NPM packages to version, tag, or commit
@@ -561,13 +547,11 @@
     message =
         "Pin versions in npm. Instead of `npm install <package>` use `npm install \
         \<package>@<version>`"
-    check (Run args) = argumentsRule (all versionFixed . packages) args
+    check (Run args) = argumentsRule (Bash.noCommands forgotToPinVersion) args
     check _ = True
-    packages cmd =
-        case getInstallArgs cmd of
-            Nothing -> []
-            Just args -> findPackages args
-    getInstallArgs = stripInstallPrefix (== "npm")
+    forgotToPinVersion cmd = isNpmInstall cmd && not (all versionFixed (packages cmd))
+    isNpmInstall = Bash.cmdHasArgs "npm" ["install"]
+    packages cmd = stripInstallPrefix (Bash.getArgsNoFlags cmd)
     versionFixed package =
         if hasGitPrefix package
             then isVersionedGit package
@@ -582,21 +566,20 @@
                 then dropWhile ('/' <) pkg
                 else pkg
 
-isAptGetInstall :: [String] -> Bool
-isAptGetInstall cmd = ["apt-get"] `isInfixOf` cmd && ["install"] `isInfixOf` cmd
-
 aptGetYes :: Rule
 aptGetYes = instructionRule code severity message check
   where
     code = "DL3014"
     severity = WarningC
     message = "Use the `-y` switch to avoid manual input `apt-get -y install <package>`"
-    check (Run args) = argumentsRule (\as -> not (isAptGetInstall as) || hasYesOption as) args
+    check (Run args) = argumentsRule (Bash.noCommands forgotAptYesOption) args
     check _ = True
+    forgotAptYesOption cmd = isAptGetInstall cmd && not (hasYesOption cmd)
+    isAptGetInstall = Bash.cmdHasArgs "apt-get" ["install"]
     hasYesOption cmd =
-        ["-y"] `isInfixOf` cmd ||
-        ["--yes"] `isInfixOf` cmd || ["-qq"] `isInfixOf` cmd || startsWithYesFlag cmd
-    startsWithYesFlag cmd = True `elem` ["-y" `isInfixOf` arg | arg <- cmd]
+        "y" `elem` allFlags cmd ||
+        "yes" `elem` allFlags cmd || length (filter (== "q") (allFlags cmd)) > 1
+    allFlags cmd = snd <$> Bash.getAllFlags cmd
 
 aptGetNoRecommends :: Rule
 aptGetNoRecommends = instructionRule code severity message check
@@ -604,10 +587,11 @@
     code = "DL3015"
     severity = InfoC
     message = "Avoid additional packages by specifying `--no-install-recommends`"
-    check (Run args) =
-        argumentsRule (\as -> not (isAptGetInstall as) || hasNoRecommendsOption as) args
+    check (Run args) = argumentsRule (Bash.noCommands forgotNoInstallRecommends) args
     check _ = True
-    hasNoRecommendsOption cmd = ["--no-install-recommends"] `isInfixOf` cmd
+    forgotNoInstallRecommends cmd = isAptGetInstall cmd && not (hasRecommendsOption cmd)
+    isAptGetInstall = Bash.cmdHasArgs "apt-get" ["install"]
+    hasRecommendsOption = Bash.hasFlag "no-install-recommends"
 
 isArchive :: Text.Text -> Bool
 isArchive path =
@@ -695,6 +679,5 @@
     code = "DL4005"
     severity = WarningC
     message = "Use SHELL to change the default shell"
-    check (Run args) = argumentsRule (not . any shellSymlink . bashCommands) args
+    check (Run args) = argumentsRule (Bash.noCommands (Bash.cmdHasArgs "ln" ["/bin/sh"])) args
     check _ = True
-    shellSymlink args = usingProgram "ln" args && isInfixOf ["/bin/sh"] args
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -6,6 +6,7 @@
 import Hadolint.Rules
 
 import Language.Docker.Parser
+import Language.Docker.Syntax
 import Data.Semigroup ((<>))
 import qualified Data.Text as Text
 
@@ -23,7 +24,7 @@
                     "FROM hub.docker.io/debian@sha256:\
                     \7959ed6f7e35f8b1aaa06d1d8259d4ee25aa85a086d5c125480c333183f9deeb"
             it "explicit tagged with name" $
-              ruleCatchesNot noLatestTag "FROM debian:jessie AS builder"
+                ruleCatchesNot noLatestTag "FROM debian:jessie AS builder"
             it "local aliases are OK to be untagged" $
                 let dockerFile =
                         [ "FROM golang:1.9.3-alpine3.7 AS build"
@@ -33,7 +34,9 @@
                         , "FROM alpine:3.7"
                         , "RUN baz"
                         ]
-                in ruleCatchesNot noUntagged $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot noUntagged $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot noUntagged $ Text.unlines dockerFile
             it "other untagged cases are not ok" $
                 let dockerFile =
                         [ "FROM golang:1.9.3-alpine3.7 AS build"
@@ -43,33 +46,49 @@
                         , "FROM alpine:3.7"
                         , "RUN baz"
                         ]
-                in ruleCatches noUntagged $ Text.unlines dockerFile
+                in do
+                  ruleCatches noUntagged $ Text.unlines dockerFile
+                  onBuildRuleCatches noUntagged $ Text.unlines dockerFile
         --
         describe "no root or sudo rules" $ do
-            it "sudo" $ ruleCatches noSudo "RUN sudo apt-get update"
+            it "sudo" $ do
+              ruleCatches noSudo "RUN sudo apt-get update"
+              onBuildRuleCatches noSudo "RUN sudo apt-get update"
             it "no root" $ ruleCatches noRootUser "USER root"
             it "no root" $ ruleCatchesNot noRootUser "USER foo"
             it "no root UID" $ ruleCatches noRootUser "USER 0"
             it "no root:root" $ ruleCatches noRootUser "USER root:root"
             it "no root UID:GID" $ ruleCatches noRootUser "USER 0:0"
-            it "install sudo" $ ruleCatchesNot noSudo "RUN apt-get install sudo"
-            it "sudo chained programs" $
+            it "install sudo" $ do
+                ruleCatchesNot noSudo "RUN apt-get install sudo"
+                onBuildRuleCatchesNot noSudo "RUN apt-get install sudo"
+            it "sudo chained programs" $ do
                 ruleCatches noSudo "RUN apt-get update && sudo apt-get install"
+                onBuildRuleCatches noSudo "RUN apt-get update && sudo apt-get install"
         --
         describe "invalid CMD rules" $ do
-            it "invalid cmd" $ ruleCatches invalidCmd "RUN top"
-            it "install ssh" $ ruleCatchesNot invalidCmd "RUN apt-get install ssh"
+            it "invalid cmd" $ do
+                ruleCatches invalidCmd "RUN top"
+                onBuildRuleCatches invalidCmd "RUN top"
+            it "install ssh" $ do
+                ruleCatchesNot invalidCmd "RUN apt-get install ssh"
+                onBuildRuleCatchesNot invalidCmd "RUN apt-get install ssh"
         --
         describe "apt-get rules" $ do
-            it "apt upgrade" $ ruleCatches noAptGetUpgrade "RUN apt-get update && apt-get upgrade"
-            it "apt-get version pinning" $
+            it "apt upgrade" $ do
+                ruleCatches noAptGetUpgrade "RUN apt-get update && apt-get upgrade"
+                onBuildRuleCatches noAptGetUpgrade "RUN apt-get update && apt-get upgrade"
+            it "apt-get version pinning" $ do
                 ruleCatches aptGetVersionPinned "RUN apt-get update && apt-get install python"
+                onBuildRuleCatches aptGetVersionPinned "RUN apt-get update && apt-get install python"
             it "apt-get no cleanup" $
                 let dockerFile =
                         [ "FROM scratch"
                         , "RUN apt-get update && apt-get install python"
                         ]
-                in ruleCatches aptGetCleanup $ Text.unlines dockerFile
+                in do
+                  ruleCatches aptGetCleanup $ Text.unlines dockerFile
+                  onBuildRuleCatches aptGetCleanup $ Text.unlines dockerFile
             it "apt-get cleanup in stage image" $
                 let dockerFile =
                         [ "FROM ubuntu as foo"
@@ -77,7 +96,9 @@
                         , "FROM scratch"
                         , "RUN echo hey!"
                         ]
-                in ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot aptGetCleanup $ Text.unlines dockerFile
             it "apt-get no cleanup in last stage" $
                 let dockerFile =
                         [ "FROM ubuntu as foo"
@@ -85,7 +106,9 @@
                         , "FROM scratch"
                         , "RUN apt-get update && apt-get install python"
                         ]
-                in ruleCatches aptGetCleanup $ Text.unlines dockerFile
+                in do
+                  ruleCatches aptGetCleanup $ Text.unlines dockerFile
+                  onBuildRuleCatches aptGetCleanup $ Text.unlines dockerFile
             it "apt-get no cleanup in intermediate stage" $
                 let dockerFile =
                         [ "FROM ubuntu as foo"
@@ -93,15 +116,19 @@
                         , "FROM foo"
                         , "RUN hey!"
                         ]
-                in ruleCatches aptGetCleanup $ Text.unlines dockerFile
-            it "now warn apt-get cleanup in intermediate stage that cleans lists" $
+                in do
+                  ruleCatches aptGetCleanup $ Text.unlines dockerFile
+                  onBuildRuleCatches aptGetCleanup $ Text.unlines dockerFile
+            it "no warn apt-get cleanup in intermediate stage that cleans lists" $
                 let dockerFile =
                         [ "FROM ubuntu as foo"
                         , "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*"
                         , "FROM foo"
                         , "RUN hey!"
                         ]
-                in ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot aptGetCleanup $ Text.unlines dockerFile
             it "no warn apt-get cleanup in intermediate stage when stage not used later" $
                 let dockerFile =
                         [ "FROM ubuntu as foo"
@@ -109,13 +136,17 @@
                         , "FROM scratch"
                         , "RUN hey!"
                         ]
-                in ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot aptGetCleanup $ Text.unlines dockerFile
             it "apt-get cleanup" $
                 let dockerFile =
                         [ "FROM scratch"
                         , "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*"
                         ]
-                in ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot aptGetCleanup $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot aptGetCleanup $ Text.unlines dockerFile
 
             it "apt-get pinned chained" $
                 let dockerFile =
@@ -123,7 +154,10 @@
                         , " && apt-get -yqq --no-install-recommends install nodejs=0.10 \\"
                         , " && rm -rf /var/lib/apt/lists/*"
                         ]
-                in ruleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+
             it "apt-get pinned regression" $
                 let dockerFile =
                         [ "RUN apt-get update && apt-get install --no-install-recommends -y \\"
@@ -132,21 +166,31 @@
                         , "git=1:2.5.0* \\"
                         , "ruby=1:2.1.*"
                         ]
-                in ruleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+
             it "has deprecated maintainer" $
                 ruleCatches hasNoMaintainer "FROM busybox\nMAINTAINER hudu@mail.com"
         --
         describe "apk add rules" $ do
-            it "apk upgrade" $ ruleCatches noApkUpgrade "RUN apk update && apk upgrade"
-            it "apk add version pinning single" $ ruleCatches apkAddVersionPinned "RUN apk add flex"
-            it "apk add no version pinning single" $
+            it "apk upgrade" $ do
+                ruleCatches noApkUpgrade "RUN apk update && apk upgrade"
+                onBuildRuleCatches noApkUpgrade "RUN apk update && apk upgrade"
+            it "apk add version pinning single" $ do
+                ruleCatches apkAddVersionPinned "RUN apk add flex"
+                onBuildRuleCatches apkAddVersionPinned "RUN apk add flex"
+            it "apk add no version pinning single" $ do
                 ruleCatchesNot apkAddVersionPinned "RUN apk add flex=2.6.4-r1"
+                onBuildRuleCatchesNot apkAddVersionPinned "RUN apk add flex=2.6.4-r1"
             it "apk add version pinned chained" $
                 let dockerFile =
                         [ "RUN apk add --no-cache flex=2.6.4-r1 \\"
                         , " && pip install -r requirements.txt"
                         ]
-                in ruleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
             it "apk add version pinned regression" $
                 let dockerFile =
                         [ "RUN apk add --no-cache \\"
@@ -155,7 +199,9 @@
                         , "python2=2.7.13-r1 \\"
                         , "libbz2=1.0.6-r5"
                         ]
-                in ruleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
             it "apk add version pinned regression - one missed" $
                 let dockerFile =
                         [ "RUN apk add --no-cache \\"
@@ -164,10 +210,15 @@
                         , "python2=2.7.13-r1 \\"
                         , "libbz2=1.0.6-r5"
                         ]
-                in ruleCatches apkAddVersionPinned $ Text.unlines dockerFile
-            it "apk add with --no-cache" $ ruleCatches apkAddNoCache "RUN apk add flex=2.6.4-r1"
-            it "apk add without --no-cache" $
+                in do
+                  ruleCatches apkAddVersionPinned $ Text.unlines dockerFile
+                  onBuildRuleCatches apkAddVersionPinned $ Text.unlines dockerFile
+            it "apk add with --no-cache" $ do
+                ruleCatches apkAddNoCache "RUN apk add flex=2.6.4-r1"
+                onBuildRuleCatches apkAddNoCache "RUN apk add flex=2.6.4-r1"
+            it "apk add without --no-cache" $ do
                 ruleCatchesNot apkAddNoCache "RUN apk add --no-cache flex=2.6.4-r1"
+                onBuildRuleCatchesNot apkAddNoCache "RUN apk add --no-cache flex=2.6.4-r1"
             it "apk add virtual package" $
                 let dockerFile =
                         [ "RUN apk add \\"
@@ -177,139 +228,226 @@
                         , "&& python setup.py install \\"
                         , "&& apk del build-dependencies"
                         ]
-                in ruleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot apkAddVersionPinned $ Text.unlines dockerFile
         --
         describe "EXPOSE rules" $ do
             it "invalid port" $ ruleCatches invalidPort "EXPOSE 80000"
             it "valid port" $ ruleCatchesNot invalidPort "EXPOSE 60000"
         --
         describe "pip pinning" $ do
-            it "pip2 version not pinned" $
+            it "pip2 version not pinned" $ do
                 ruleCatches pipVersionPinned "RUN pip2 install MySQL_python"
-            it "pip3 version not pinned" $
+                onBuildRuleCatches pipVersionPinned "RUN pip2 install MySQL_python"
+            it "pip3 version not pinned" $ do
                 ruleCatches pipVersionPinned "RUN pip3 install MySQL_python"
-            it "pip3 version pinned" $
+                onBuildRuleCatches pipVersionPinned "RUN pip2 install MySQL_python"
+            it "pip3 version pinned" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip3 install MySQL_python==1.2.2"
-            it "pip install requirements" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip3 install MySQL_python==1.2.2"
+            it "pip install requirements" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install -r requirements.txt"
-            it "pip install use setup.py" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install -r requirements.txt"
+            it "pip install use setup.py" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install ."
-            it "pip version not pinned" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install ."
+            it "pip version not pinned" $ do
                 ruleCatches pipVersionPinned "RUN pip install MySQL_python"
-            it "pip version pinned" $
+                onBuildRuleCatches pipVersionPinned "RUN pip install MySQL_python"
+            it "pip version pinned" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install MySQL_python==1.2.2"
-            it "pip version pinned with flag" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install MySQL_python==1.2.2"
+            it "pip version pinned with ~= operator" $ do
+                ruleCatchesNot pipVersionPinned "RUN pip install MySQL_python~=1.2.2"
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install MySQL_python~=1.2.2"
+            it "pip version pinned with === operator" $ do
+                ruleCatchesNot pipVersionPinned "RUN pip install MySQL_python===1.2.2"
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install MySQL_python===1.2.2"
+            it "pip version pinned with flag" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install --ignore-installed MySQL_python==1.2.2"
-            it "pip version pinned with python -m" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install --ignore-installed MySQL_python==1.2.2"
+            it "pip version pinned with python -m" $ do
                 ruleCatchesNot pipVersionPinned "RUN python -m pip install example==1.2.2"
-            it "pip install git" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN python -m pip install example==1.2.2"
+            it "pip install git" $ do
                 ruleCatchesNot
                     pipVersionPinned
                     "RUN pip install git+https://github.com/rtfd/r-ext.git@0.6-alpha#egg=r-ext"
-            it "pip install unversioned git" $
+                onBuildRuleCatchesNot
+                    pipVersionPinned
+                    "RUN pip install git+https://github.com/rtfd/r-ext.git@0.6-alpha#egg=r-ext"
+            it "pip install unversioned git" $ do
                 ruleCatches
                     pipVersionPinned
                     "RUN pip install git+https://github.com/rtfd/read-ext.git#egg=read-ext"
-            it "pip install upper bound" $
+                onBuildRuleCatches
+                    pipVersionPinned
+                    "RUN pip install git+https://github.com/rtfd/read-ext.git#egg=read-ext"
+            it "pip install upper bound" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install 'alabaster>=0.7'"
-            it "pip install lower bound" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install 'alabaster>=0.7'"
+            it "pip install lower bound" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install 'alabaster<0.7'"
-            it "pip install excluded version" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install 'alabaster<0.7'"
+            it "pip install excluded version" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install 'alabaster!=0.7'"
-            it "pip install user directory" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install 'alabaster!=0.7'"
+            it "pip install user directory" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install MySQL_python==1.2.2 --user"
-            it "pip install no pip version check" $
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install MySQL_python==1.2.2 --user"
+            it "pip install no pip version check" $ do
                 ruleCatchesNot
                     pipVersionPinned
                     "RUN pip install MySQL_python==1.2.2 --disable-pip-version-check"
-            it "pip install no cache dir" $
+                onBuildRuleCatchesNot
+                    pipVersionPinned
+                    "RUN pip install MySQL_python==1.2.2 --disable-pip-version-check"
+            it "pip install no cache dir" $ do
                 ruleCatchesNot pipVersionPinned "RUN pip install MySQL_python==1.2.2 --no-cache-dir"
+                onBuildRuleCatchesNot pipVersionPinned "RUN pip install MySQL_python==1.2.2 --no-cache-dir"
         --
         describe "npm pinning" $ do
-            it "version pinned in package.json" $ ruleCatchesNot npmVersionPinned "RUN npm install"
-            it "version pinned in package.json with arguments" $
+            it "version pinned in package.json" $ do
+                ruleCatchesNot npmVersionPinned "RUN npm install"
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install"
+            it "version pinned in package.json with arguments" $ do
                 ruleCatchesNot npmVersionPinned "RUN npm install --progress=false"
-            it "version pinned" $ ruleCatchesNot npmVersionPinned "RUN npm install express@4.1.1"
-            it "version pinned with scope" $
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install --progress=false"
+            it "version pinned" $ do
+                ruleCatchesNot npmVersionPinned "RUN npm install express@4.1.1"
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install express@4.1.1"
+            it "version pinned with scope" $ do
                 ruleCatchesNot npmVersionPinned "RUN npm install @myorg/privatepackage@\">=0.1.0\""
-            it "version pinned multiple packages" $
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install @myorg/privatepackage@\">=0.1.0\""
+            it "version pinned multiple packages" $ do
                 ruleCatchesNot npmVersionPinned "RUN npm install express@\"4.1.1\" sax@0.1.1"
-            it "version pinned with --global" $
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install express@\"4.1.1\" sax@0.1.1"
+            it "version pinned with --global" $ do
                 ruleCatchesNot npmVersionPinned "RUN npm install --global express@\"4.1.1\""
-            it "version pinned with -g" $
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install --global express@\"4.1.1\""
+            it "version pinned with -g" $ do
                 ruleCatchesNot npmVersionPinned "RUN npm install -g express@\"4.1.1\""
-            it "commit pinned for git+ssh" $
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install -g express@\"4.1.1\""
+            it "commit pinned for git+ssh" $ do
                 ruleCatchesNot
                     npmVersionPinned
                     "RUN npm install git+ssh://git@github.com:npm/npm.git#v1.0.27"
-            it "commit pinned for git+http" $
+                onBuildRuleCatchesNot
+                    npmVersionPinned
+                    "RUN npm install git+ssh://git@github.com:npm/npm.git#v1.0.27"
+            it "commit pinned for git+http" $ do
                 ruleCatchesNot
                     npmVersionPinned
                     "RUN npm install git+http://isaacs@github.com/npm/npm#semver:^5.0"
-            it "commit pinned for git+https" $
+                onBuildRuleCatchesNot
+                    npmVersionPinned
+                    "RUN npm install git+http://isaacs@github.com/npm/npm#semver:^5.0"
+            it "commit pinned for git+https" $ do
                 ruleCatchesNot
                     npmVersionPinned
                     "RUN npm install git+https://isaacs@github.com/npm/npm.git#v1.0.27"
-            it "commit pinned for git" $
+                onBuildRuleCatchesNot
+                    npmVersionPinned
+                    "RUN npm install git+https://isaacs@github.com/npm/npm.git#v1.0.27"
+            it "commit pinned for git" $ do
                 ruleCatchesNot
                     npmVersionPinned
                     "RUN npm install git://github.com/npm/npm.git#v1.0.27"
+                onBuildRuleCatchesNot
+                    npmVersionPinned
+                    "RUN npm install git://github.com/npm/npm.git#v1.0.27"
+
       --version range is not supported
-      --it "version pinned with scope" $
-      --  ruleCatchesNot npmVersionPinned "RUN npm install @myorg/privatepackage@\">=0.1.0 <0.2.0\""
-            it "version not pinned" $ ruleCatches npmVersionPinned "RUN npm install express"
-            it "version not pinned with scope" $
+            it "version pinned with scope" $ do
+                ruleCatchesNot npmVersionPinned "RUN npm install @myorg/privatepackage@\">=0.1.0 <0.2.0\""
+                onBuildRuleCatchesNot npmVersionPinned "RUN npm install @myorg/privatepackage@\">=0.1.0 <0.2.0\""
+            it "version not pinned" $ do
+                ruleCatches npmVersionPinned "RUN npm install express"
+                onBuildRuleCatches npmVersionPinned "RUN npm install express"
+            it "version not pinned with scope" $ do
                 ruleCatches npmVersionPinned "RUN npm install @myorg/privatepackage"
-            it "version not pinned multiple packages" $
+                onBuildRuleCatches npmVersionPinned "RUN npm install @myorg/privatepackage"
+            it "version not pinned multiple packages" $ do
                 ruleCatches npmVersionPinned "RUN npm install express sax@0.1.1"
-            it "version not pinned with --global" $
+                onBuildRuleCatches npmVersionPinned "RUN npm install express sax@0.1.1"
+            it "version not pinned with --global" $ do
                 ruleCatches npmVersionPinned "RUN npm install --global express"
-            it "commit not pinned for git+ssh" $
+                onBuildRuleCatches npmVersionPinned "RUN npm install --global express"
+            it "commit not pinned for git+ssh" $ do
                 ruleCatches npmVersionPinned "RUN npm install git+ssh://git@github.com:npm/npm.git"
-            it "commit not pinned for git+http" $
+                onBuildRuleCatches npmVersionPinned "RUN npm install git+ssh://git@github.com:npm/npm.git"
+            it "commit not pinned for git+http" $ do
                 ruleCatches npmVersionPinned "RUN npm install git+http://isaacs@github.com/npm/npm"
-            it "commit not pinned for git+https" $
+                onBuildRuleCatches npmVersionPinned "RUN npm install git+http://isaacs@github.com/npm/npm"
+            it "commit not pinned for git+https" $ do
                 ruleCatches
                     npmVersionPinned
                     "RUN npm install git+https://isaacs@github.com/npm/npm.git"
-            it "commit not pinned for git" $
+                onBuildRuleCatches
+                    npmVersionPinned
+                    "RUN npm install git+https://isaacs@github.com/npm/npm.git"
+            it "commit not pinned for git" $ do
                 ruleCatches npmVersionPinned "RUN npm install git://github.com/npm/npm.git"
+                onBuildRuleCatches npmVersionPinned "RUN npm install git://github.com/npm/npm.git"
         --
         describe "use SHELL" $ do
-            it "RUN ln" $ ruleCatches useShell "RUN ln -sfv /bin/bash /bin/sh"
-            it "RUN ln with unrelated symlinks" $
+            it "RUN ln" $ do
+                ruleCatches useShell "RUN ln -sfv /bin/bash /bin/sh"
+                onBuildRuleCatches useShell "RUN ln -sfv /bin/bash /bin/sh"
+            it "RUN ln with unrelated symlinks" $ do
                 ruleCatchesNot useShell "RUN ln -sf /bin/true /sbin/initctl"
-            it "RUN ln with multiple acceptable commands" $
+                onBuildRuleCatchesNot useShell "RUN ln -sf /bin/true /sbin/initctl"
+            it "RUN ln with multiple acceptable commands" $ do
                 ruleCatchesNot useShell "RUN ln -s foo bar && unrelated && something_with /bin/sh"
+                onBuildRuleCatchesNot useShell "RUN ln -s foo bar && unrelated && something_with /bin/sh"
         --
         describe "COPY rules" $ do
             it "use add" $ ruleCatches useAdd "COPY packaged-app.tar /usr/src/app"
             it "use not add" $ ruleCatchesNot useAdd "COPY package.json /usr/src/app"
         --
         describe "other rules" $ do
-            it "apt-get auto yes" $ ruleCatches aptGetYes "RUN apt-get install python"
-            it "apt-get yes shortflag" $ ruleCatchesNot aptGetYes "RUN apt-get install -yq python"
-            it "apt-get yes quiet level 2 implies -y" $
+            it "apt-get auto yes" $ do
+              ruleCatches aptGetYes "RUN apt-get install python"
+              onBuildRuleCatches aptGetYes "RUN apt-get install python"
+            it "apt-get yes shortflag" $ do
+              ruleCatchesNot aptGetYes "RUN apt-get install -yq python"
+              onBuildRuleCatchesNot aptGetYes "RUN apt-get install -yq python"
+            it "apt-get yes quiet level 2 implies -y" $ do
                 ruleCatchesNot aptGetYes "RUN apt-get install -qq python"
-            it "apt-get yes different pos" $
+                onBuildRuleCatchesNot aptGetYes "RUN apt-get install -qq python"
+            it "apt-get yes different pos" $ do
                 ruleCatchesNot aptGetYes "RUN apt-get install -y python"
-            it "apt-get with auto yes" $ ruleCatchesNot aptGetYes "RUN apt-get -y install python"
-            it "apt-get with auto expanded yes" $
+                onBuildRuleCatchesNot aptGetYes "RUN apt-get install -y python"
+            it "apt-get with auto yes" $ do
+                ruleCatchesNot aptGetYes "RUN apt-get -y install python"
+                onBuildRuleCatchesNot aptGetYes "RUN apt-get -y install python"
+            it "apt-get with auto expanded yes" $ do
                 ruleCatchesNot aptGetYes "RUN apt-get --yes install python"
-            it "apt-get install recommends" $
+                onBuildRuleCatchesNot aptGetYes "RUN apt-get --yes install python"
+            it "apt-get install recommends" $ do
                 ruleCatchesNot
                     aptGetNoRecommends
                     "RUN apt-get install --no-install-recommends python"
-            it "apt-get no install recommends" $
+                onBuildRuleCatchesNot
+                    aptGetNoRecommends
+                    "RUN apt-get install --no-install-recommends python"
+            it "apt-get no install recommends" $ do
                 ruleCatches aptGetNoRecommends "RUN apt-get install python"
-            it "apt-get no install recommends" $
+                onBuildRuleCatches aptGetNoRecommends "RUN apt-get install python"
+            it "apt-get no install recommends" $ do
                 ruleCatches aptGetNoRecommends "RUN apt-get -y install python"
-            it "apt-get version" $
+                onBuildRuleCatches aptGetNoRecommends "RUN apt-get -y install python"
+            it "apt-get version" $ do
                 ruleCatchesNot aptGetVersionPinned "RUN apt-get install -y python=1.2.2"
-            it "apt-get pinned" $
+                onBuildRuleCatchesNot aptGetVersionPinned "RUN apt-get install -y python=1.2.2"
+            it "apt-get pinned" $ do
                 ruleCatchesNot
                     aptGetVersionPinned
                     "RUN apt-get -y --no-install-recommends install nodejs=0.10"
+                onBuildRuleCatchesNot
+                    aptGetVersionPinned
+                    "RUN apt-get -y --no-install-recommends install nodejs=0.10"
             it "apt-get tolerate target-release" $
                 let dockerFile =
                         [ "RUN set -e &&\\"
@@ -320,7 +458,9 @@
                           \openjdk-8-jdk=8u131-b11-1~bpo8+1 &&\\"
                         , " rm -rf /var/lib/apt/lists/*"
                         ]
-                in ruleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+                in do
+                  ruleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
+                  onBuildRuleCatchesNot aptGetVersionPinned $ Text.unlines dockerFile
 
             it "has maintainer" $ ruleCatches hasNoMaintainer "FROM debian\nMAINTAINER Lukas"
             it "has maintainer first" $ ruleCatches hasNoMaintainer "MAINTAINER Lukas\nFROM DEBIAN"
@@ -464,6 +604,17 @@
         Left err -> assertFailure $ show err
         Right dockerFile -> makeAssertions $ analyze [rule] dockerFile
 
+
+assertOnBuildChecks :: HasCallStack => Rule -> Text.Text -> ([RuleCheck] -> IO a) -> IO a
+assertOnBuildChecks rule s makeAssertions =
+    case parseText (s <> "\n") of
+        Left err -> assertFailure $ show err
+        Right dockerFile -> checkOnBuild dockerFile
+  where
+    checkOnBuild dockerFile = makeAssertions $ analyze [rule] (fmap wrapInOnBuild dockerFile)
+    wrapInOnBuild (InstructionPos (Run args) so li) = InstructionPos (OnBuild (Run args)) so li
+    wrapInOnBuild i = i
+
 -- Assert a failed check exists for rule
 ruleCatches :: HasCallStack => Rule -> Text.Text -> Assertion
 ruleCatches rule s = assertChecks rule s f
@@ -472,7 +623,19 @@
       assertEqual "No check for rule found" 1 $ length checks
       assertBool "Incorrect line number for result" $ null [c | c <- checks, linenumber c <= 0]
 
+onBuildRuleCatches :: HasCallStack => Rule -> Text.Text -> Assertion
+onBuildRuleCatches rule s = assertOnBuildChecks rule s f
+  where
+    f checks = do
+      assertEqual "No check for rule found" 1 $ length checks
+      assertBool "Incorrect line number for result" $ null [c | c <- checks, linenumber c <= 0]
+
 ruleCatchesNot :: HasCallStack => Rule -> Text.Text -> Assertion
 ruleCatchesNot rule s = assertChecks rule s f
+  where
+    f checks = assertEqual "Found check of rule" 0 $ length checks
+
+onBuildRuleCatchesNot :: HasCallStack => Rule -> Text.Text -> Assertion
+onBuildRuleCatchesNot rule s = assertOnBuildChecks rule s f
   where
     f checks = assertEqual "Found check of rule" 0 $ length checks
