diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -203,6 +203,14 @@
 | [DL3031](https://github.com/hadolint/hadolint/wiki/DL3031)   | Do not use `yum update`                                                                                                                             |
 | [DL3032](https://github.com/hadolint/hadolint/wiki/DL3032)   | `yum clean all` missing after yum command.                                                                                                          |
 | [DL3033](https://github.com/hadolint/hadolint/wiki/DL3033)   | Specify version with `yum install -y <package>-<version>`                                                                                           |
+| [DL3034](https://github.com/hadolint/hadolint/wiki/DL3034)   | Non-interactive switch missing from `zypper` command: `zypper install -y`                                                                           |
+| [DL3035](https://github.com/hadolint/hadolint/wiki/DL3035)   | Do not use `zypper update`.                                                                                                                         |
+| [DL3036](https://github.com/hadolint/hadolint/wiki/DL3036)   | `zypper clean` missing after zypper use.                                                                                                            |
+| [DL3037](https://github.com/hadolint/hadolint/wiki/DL3037)   | Specify version with `zypper install -y <package>[=]<version>`.                                                                                     |
+| [DL3038](https://github.com/hadolint/hadolint/wiki/DL3038)   | Use the `-y` switch to avoid manual input `dnf install -y <package>`                                                                                |
+| [DL3039](https://github.com/hadolint/hadolint/wiki/DL3039)   | Do not use `dnf update`                                                                                                                             |
+| [DL3040](https://github.com/hadolint/hadolint/wiki/DL3040)   | `dnf clean all` missing after dnf command.                                                                                                          |
+| [DL3041](https://github.com/hadolint/hadolint/wiki/DL3041)   | Specify version with `dnf install -y <package>-<version>`                                                                                           |
 | [DL4000](https://github.com/hadolint/hadolint/wiki/DL4000)   | MAINTAINER is deprecated.                                                                                                                           |
 | [DL4001](https://github.com/hadolint/hadolint/wiki/DL4001)   | Either use Wget or Curl but not both.                                                                                                               |
 | [DL4003](https://github.com/hadolint/hadolint/wiki/DL4003)   | Multiple `CMD` instructions found.                                                                                                                  |
diff --git a/hadolint.cabal b/hadolint.cabal
--- a/hadolint.cabal
+++ b/hadolint.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 418683858197c94a7d90196b639708548b3c5966bc20506c43b2383a836a281b
+-- hash: afee9524ce1aad80f4f3a651eef3a68c327005e1f97e965b5a82d3cd36afa09e
 
 name:           hadolint
-version:        1.18.2
+version:        1.19.0
 synopsis:       Dockerfile Linter JavaScript API
 description:    A smarter Dockerfile linter that helps you build best practice Docker images.
 category:       Development
@@ -61,7 +61,7 @@
     , directory >=1.3.0
     , filepath
     , language-docker >=9.1.2 && <10
-    , megaparsec >=7.0
+    , megaparsec >=9.0.0
     , mtl
     , parallel
     , split >=0.2
@@ -82,7 +82,7 @@
     , gitrev >=1.3.1
     , hadolint
     , language-docker >=9.1.2 && <10
-    , megaparsec >=7.0
+    , megaparsec >=9.0.0
     , optparse-applicative >=0.14.0
     , text
   if flag(static) && !(os(osx))
@@ -108,7 +108,7 @@
     , hadolint
     , hspec
     , language-docker >=9.1.2 && <10
-    , megaparsec >=7.0
+    , megaparsec >=9.0.0
     , split >=0.2
     , text
   default-language: Haskell2010
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
@@ -17,9 +17,10 @@
 import Hadolint.Formatter.Format
 import Hadolint.Rules (Metadata (..), RuleCheck (..))
 import ShellCheck.Interface
-import Text.Megaparsec (Stream)
+import Text.Megaparsec (TraversableStream)
 import Text.Megaparsec.Error
 import Text.Megaparsec.Pos (sourceColumn, sourceLine, sourceName, unPos)
+import Text.Megaparsec.Stream (VisualStream)
 
 data CheckStyle = CheckStyle
   { file :: String,
@@ -30,7 +31,7 @@
     source :: String
   }
 
-errorToCheckStyle :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> CheckStyle
+errorToCheckStyle :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> CheckStyle
 errorToCheckStyle err =
   CheckStyle
     { file = sourceName pos,
@@ -83,7 +84,7 @@
         else "&#" ++ show (ord c) ++ ";"
     isOk x = any (\check -> check x) [isAsciiUpper, isAsciiLower, isDigit, (`elem` [' ', '.', '/'])]
 
-formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Builder.Builder
+formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Builder.Builder
 formatResult (Result errors checks) =
   "<?xml version='1.0' encoding='UTF-8'?><checkstyle version='4.3'>" <> xmlBody <> "</checkstyle>"
   where
@@ -94,5 +95,5 @@
     checkstyleChecks = fmap ruleToCheckStyle checks
     sameFileName CheckStyle {file = f1} CheckStyle {file = f2} = f1 == f2
 
-printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO ()
+printResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> IO ()
 printResult result = B.putStr (Builder.toLazyByteString (formatResult result))
diff --git a/src/Hadolint/Formatter/Codacy.hs b/src/Hadolint/Formatter/Codacy.hs
--- a/src/Hadolint/Formatter/Codacy.hs
+++ b/src/Hadolint/Formatter/Codacy.hs
@@ -14,9 +14,10 @@
 import qualified Data.Text as Text
 import Hadolint.Formatter.Format (Result (..), errorPosition)
 import Hadolint.Rules (Metadata (..), RuleCheck (..))
-import Text.Megaparsec (Stream)
+import Text.Megaparsec (TraversableStream)
 import Text.Megaparsec.Error
 import Text.Megaparsec.Pos (sourceLine, sourceName, unPos)
+import Text.Megaparsec.Stream (VisualStream)
 
 data Issue = Issue
   { filename :: String,
@@ -29,7 +30,7 @@
   toJSON Issue {..} =
     object ["filename" .= filename, "patternId" .= patternId, "message" .= msg, "line" .= line]
 
-errorToIssue :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> Issue
+errorToIssue :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> Issue
 errorToIssue err =
   Issue
     { filename = sourceName pos,
@@ -50,14 +51,14 @@
       line = linenumber
     }
 
-formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Seq Issue
+formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Seq Issue
 formatResult (Result errors checks) = allIssues
   where
     allIssues = errorMessages <> checkMessages
     errorMessages = fmap errorToIssue errors
     checkMessages = fmap checkToIssue checks
 
-printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO ()
+printResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> IO ()
 printResult result = mapM_ output (formatResult result)
   where
     output value = B.putStrLn (encode value)
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
@@ -17,9 +17,10 @@
 import Hadolint.Formatter.Format (Result (..), errorPosition)
 import Hadolint.Rules (Metadata (..), RuleCheck (..))
 import ShellCheck.Interface
-import Text.Megaparsec (Stream)
+import Text.Megaparsec (TraversableStream)
 import Text.Megaparsec.Error
 import Text.Megaparsec.Pos (sourceColumn, sourceLine, sourceName, unPos)
+import Text.Megaparsec.Stream (VisualStream)
 
 data Issue = Issue
   { checkName :: String,
@@ -60,7 +61,7 @@
         "severity" .= impact
       ]
 
-errorToIssue :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> Issue
+errorToIssue :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> Issue
 errorToIssue err =
   Issue
     { checkName = "DL1000",
@@ -90,14 +91,14 @@
     InfoC -> "info"
     StyleC -> "minor"
 
-formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Seq Issue
+formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Seq Issue
 formatResult (Result errors checks) = allIssues
   where
     allIssues = errorMessages <> checkMessages
     errorMessages = fmap errorToIssue errors
     checkMessages = fmap checkToIssue checks
 
-printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO ()
+printResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> IO ()
 printResult result = mapM_ output (formatResult result)
   where
     output value = do
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
@@ -19,9 +19,10 @@
 import qualified Data.Sequence as Seq
 import Hadolint.Rules
 import ShellCheck.Interface
-import Text.Megaparsec (Stream (..), pstateSourcePos)
+import Text.Megaparsec (TraversableStream (..), pstateSourcePos)
 import Text.Megaparsec.Error
 import Text.Megaparsec.Pos (SourcePos, sourcePosPretty)
+import Text.Megaparsec.Stream (VisualStream)
 
 data Result s e = Result
   { errors :: !(Seq (ParseErrorBundle s e)),
@@ -62,14 +63,14 @@
           else c
     )
 
-errorMessageLine :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> String
+errorMessageLine :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> String
 errorMessageLine err@(ParseErrorBundle e _) =
   errorPositionPretty err ++ " " ++ parseErrorTextPretty (NE.head e)
 
-errorPositionPretty :: Stream s => ParseErrorBundle s e -> String
+errorPositionPretty :: TraversableStream s => ParseErrorBundle s e -> String
 errorPositionPretty err = sourcePosPretty (errorPosition err)
 
-errorPosition :: Stream s => ParseErrorBundle s e -> Text.Megaparsec.Pos.SourcePos
+errorPosition :: TraversableStream s => ParseErrorBundle s e -> Text.Megaparsec.Pos.SourcePos
 errorPosition (ParseErrorBundle e s) =
   let (_, posState) = reachOffset (errorOffset (NE.head e)) s
    in pstateSourcePos posState
diff --git a/src/Hadolint/Formatter/Json.hs b/src/Hadolint/Formatter/Json.hs
--- a/src/Hadolint/Formatter/Json.hs
+++ b/src/Hadolint/Formatter/Json.hs
@@ -13,15 +13,16 @@
 import Hadolint.Formatter.Format (Result (..), errorPosition, severityText)
 import Hadolint.Rules (Metadata (..), RuleCheck (..))
 import ShellCheck.Interface
-import Text.Megaparsec (Stream)
+import Text.Megaparsec (TraversableStream)
 import Text.Megaparsec.Error
 import Text.Megaparsec.Pos (sourceColumn, sourceLine, sourceName, unPos)
+import Text.Megaparsec.Stream (VisualStream)
 
 data JsonFormat s e
   = JsonCheck RuleCheck
   | JsonParseError (ParseErrorBundle s e)
 
-instance (Stream s, ShowErrorComponent e) => ToJSON (JsonFormat s e) where
+instance (VisualStream s, TraversableStream s, ShowErrorComponent e) => ToJSON (JsonFormat s e) where
   toJSON (JsonCheck RuleCheck {..}) =
     object
       [ "file" .= filename,
@@ -43,12 +44,12 @@
     where
       pos = errorPosition err
 
-formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Value
+formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Value
 formatResult (Result errors checks) = toJSON allMessages
   where
     allMessages = errorMessages <> checkMessages
     errorMessages = fmap JsonParseError errors
     checkMessages = fmap JsonCheck checks
 
-printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO ()
+printResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> IO ()
 printResult result = B.putStrLn (encode (formatResult result))
diff --git a/src/Hadolint/Formatter/TTY.hs b/src/Hadolint/Formatter/TTY.hs
--- a/src/Hadolint/Formatter/TTY.hs
+++ b/src/Hadolint/Formatter/TTY.hs
@@ -13,13 +13,14 @@
 import Hadolint.Formatter.Format
 import Hadolint.Rules
 import Language.Docker.Syntax
-import Text.Megaparsec (Stream (..))
+import Text.Megaparsec (TraversableStream)
 import Text.Megaparsec.Error
+import Text.Megaparsec.Stream (VisualStream)
 
-formatErrors :: (Stream s, ShowErrorComponent e, Functor f) => f (ParseErrorBundle s e) -> f String
+formatErrors :: (VisualStream s, TraversableStream s, ShowErrorComponent e, Functor f) => f (ParseErrorBundle s e) -> f String
 formatErrors = fmap formatError
 
-formatError :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> String
+formatError :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> String
 formatError err = stripNewlines (errorMessageLine err)
 
 formatChecks :: Functor f => f RuleCheck -> f Text.Text
@@ -31,7 +32,7 @@
 formatPos :: Filename -> Linenumber -> Text.Text
 formatPos source line = source <> ":" <> Text.pack (show line) <> " "
 
-printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO ()
+printResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> IO ()
 printResult Result {errors, checks} = printErrors >> printChecks
   where
     printErrors = mapM_ putStrLn (formatErrors errors)
diff --git a/src/Hadolint/Rules.hs b/src/Hadolint/Rules.hs
--- a/src/Hadolint/Rules.hs
+++ b/src/Hadolint/Rules.hs
@@ -210,7 +210,15 @@
     yumYes,
     noYumUpdate,
     yumCleanup,
-    yumVersionPinned
+    yumVersionPinned,
+    zypperYes,
+    noZypperUpdate,
+    zypperCleanup,
+    zypperVersionPinned,
+    dnfYes,
+    noDnfUpdate,
+    dnfCleanup,
+    dnfVersionPinned
   ]
 
 optionalRules :: RulesConfig -> [Rule]
@@ -284,10 +292,14 @@
     severity = ErrorC
     message = "Use absolute WORKDIR"
     check (Workdir loc)
-      | "$" `Text.isPrefixOf` loc = True
-      | "/" `Text.isPrefixOf` loc = True
+      | "$" `Text.isPrefixOf` Text.dropAround dropQuotes loc = True
+      | "/" `Text.isPrefixOf` Text.dropAround dropQuotes loc = True
       | otherwise = False
     check _ = True
+    dropQuotes chr
+      | chr == '\"' = True
+      | chr == '\'' = True
+      | otherwise = False
 
 hasNoMaintainer :: Rule
 hasNoMaintainer = instructionRule code severity message check
@@ -635,10 +647,12 @@
               "upgrade-strategy"
             ]
             cmd
-    versionFixed package = hasVersionSymbol package || isVersionedGit package
+    versionFixed package = hasVersionSymbol package || isVersionedGit package || isLocalPackage package
     isVersionedGit package = "git+http" `Text.isInfixOf` package && "@" `Text.isInfixOf` package
     versionSymbols = ["==", ">=", "<=", ">", "<", "!=", "~=", "==="]
     hasVersionSymbol package = or [s `Text.isInfixOf` package | s <- versionSymbols]
+    localPackageFileExtensions = [".whl", ".tar.gz"]
+    isLocalPackage package = or [s `Text.isSuffixOf` package | s <- localPackageFileExtensions]
 
 stripInstallPrefix :: [Text.Text] -> [Text.Text]
 stripInstallPrefix cmd = dropWhile (== "install") (dropWhile (/= "install") cmd)
@@ -950,6 +964,148 @@
 yumPackages :: Shell.ParsedShell -> [Text.Text]
 yumPackages args =
   [ arg | cmd <- Shell.presentCommands args, Shell.cmdHasArgs "yum" ["install"] cmd, arg <- Shell.getArgsNoFlags cmd, arg /= "install"
+  ]
+
+zypperYes :: Rule
+zypperYes = instructionRule code severity message check
+  where
+    code = "DL3034"
+    severity = WarningC
+    message = "Non-interactive switch missing from `zypper` command: `zypper install -y`"
+    check (Run (RunArgs args _)) = argumentsRule (Shell.noCommands forgotZypperYesOption) args
+    check _ = True
+    forgotZypperYesOption cmd = isZypperInstall cmd && not (hasYesOption cmd)
+    isZypperInstall =
+      Shell.cmdHasArgs
+        "zypper"
+        [ "install",
+          "in",
+          "remove",
+          "rm",
+          "source-install",
+          "si",
+          "patch"
+        ]
+    hasYesOption = Shell.hasAnyFlag ["no-confirm", "y"]
+
+noZypperUpdate :: Rule
+noZypperUpdate = instructionRule code severity message check
+  where
+    code = "DL3035"
+    severity = WarningC
+    message = "Do not use `zypper update`."
+    check (Run (RunArgs args _)) =
+      argumentsRule
+        ( Shell.noCommands
+            ( Shell.cmdHasArgs
+                "zypper"
+                [ "update",
+                  "up",
+                  "dist-upgrade",
+                  "dup"
+                ]
+            )
+        )
+        args
+    check _ = True
+
+zypperCleanup :: Rule
+zypperCleanup = instructionRule code severity message check
+  where
+    code = "DL3036"
+    severity = WarningC
+    message = "`zypper clean` missing after zypper use."
+    check (Run (RunArgs args _)) =
+      argumentsRule (Shell.noCommands zypperInstall) args
+        || ( argumentsRule (Shell.anyCommands zypperInstall) args
+               && argumentsRule (Shell.anyCommands zypperClean) args
+           )
+    check _ = True
+    zypperInstall = Shell.cmdHasArgs "zypper" ["install", "in"]
+    zypperClean = Shell.cmdHasArgs "zypper" ["clean", "cc"]
+
+zypperVersionPinned :: Rule
+zypperVersionPinned = instructionRule code severity message check
+  where
+    code = "DL3037"
+    severity = WarningC
+    message = "Specify version with `zypper install -y <package>=<version>`."
+    check (Run (RunArgs args _)) = argumentsRule (all versionFixed . zypperPackages) args
+    check _ = True
+    versionFixed package =
+      "=" `Text.isInfixOf` package
+        || ">=" `Text.isInfixOf` package
+        || ">" `Text.isInfixOf` package
+        || "<=" `Text.isInfixOf` package
+        || "<" `Text.isInfixOf` package
+        || ".rpm" `Text.isSuffixOf` package
+
+zypperPackages :: Shell.ParsedShell -> [Text.Text]
+zypperPackages args =
+  [ arg | cmd <- Shell.presentCommands args, Shell.cmdHasArgs "zypper" ["install", "in"] cmd, arg <- Shell.getArgsNoFlags cmd, arg /= "install", arg /= "in"
+  ]
+
+dnfYes :: Rule
+dnfYes = instructionRule code severity message check
+  where
+    code = "DL3038"
+    severity = WarningC
+    message = "Use the -y switch to avoid manual input `dnf install -y <package`"
+    check (Run (RunArgs args _)) = argumentsRule (Shell.noCommands forgotDnfYesOption) args
+    check _ = True
+    forgotDnfYesOption cmd = isDnfInstall cmd && not (hasYesOption cmd)
+    isDnfInstall = Shell.cmdHasArgs "dnf" ["install", "groupinstall", "localinstall"]
+    hasYesOption = Shell.hasAnyFlag ["y", "assumeyes"]
+
+noDnfUpdate :: Rule
+noDnfUpdate = instructionRule code severity message check
+  where
+    code = "DL3039"
+    severity = ErrorC
+    message = "Do not use dnf update."
+    check (Run (RunArgs args _)) =
+      argumentsRule
+        ( Shell.noCommands
+            ( Shell.cmdHasArgs
+                "dnf"
+                [ "upgrade",
+                  "upgrade-minimal"
+                ]
+            )
+        )
+        args
+    check _ = True
+
+dnfCleanup :: Rule
+dnfCleanup = instructionRule code severity message check
+  where
+    code = "DL3040"
+    severity = WarningC
+    message = "`dnf clean all` missing after dnf command."
+    check (Run (RunArgs args _)) =
+      argumentsRule (Shell.noCommands dnfInstall) args
+        || ( argumentsRule (Shell.anyCommands dnfInstall) args
+              && argumentsRule (Shell.anyCommands dnfClean) args
+           )
+    check _ = True
+    dnfInstall = Shell.cmdHasArgs "dnf" ["install"]
+    dnfClean = Shell.cmdHasArgs "dnf" ["clean", "all"]
+
+dnfVersionPinned :: Rule
+dnfVersionPinned = instructionRule code severity message check
+  where
+    code = "DL3041"
+    severity = WarningC
+    message = "Specify version with `dnf install -y <package>-<version>`."
+    check (Run (RunArgs args _)) = argumentsRule (all versionFixed . dnfPackages) args
+    check _ = True
+    versionFixed package =
+      "-" `Text.isInfixOf` package
+        || ".rpm" `Text.isSuffixOf` package
+
+dnfPackages :: Shell.ParsedShell -> [Text.Text]
+dnfPackages args =
+  [ arg | cmd <- Shell.presentCommands args, Shell.cmdHasArgs "dnf" ["install"] cmd, arg <- Shell.getArgsNoFlags cmd, arg /= "install"
   ]
 
 gems :: Shell.ParsedShell -> [Text.Text]
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -190,19 +190,127 @@
             onBuildRuleCatchesNot gemVersionPinned "RUN gem install bundler:2.0.1 -- --use-system-libraries=true"
     --
     describe "yum rules" $ do
-      it "yum update" $ do
-        ruleCatches noYumUpdate "RUN yum update"
-        onBuildRuleCatches noYumUpdate "RUN yum update"
-      it "yum version pinning" $ do
-        ruleCatches yumVersionPinned "RUN yum install -y tomcat && yum clean all"
-        onBuildRuleCatches yumVersionPinned "RUN yum install -y tomcat && yum clean all"
-      it "yum no clean all" $ do
-        ruleCatches yumCleanup "RUN yum install -y mariadb-10.4"
-        onBuildRuleCatches yumCleanup "RUN yum install -y mariadb-10.4"
-      it "yum non-interactive" $ do
-        ruleCatches yumYes "RUN yum install httpd-2.4.24 && yum clean all"
-        onBuildRuleCatches yumYes "RUN yum install httpd-2.4.24 && yum clean all"
+        it "yum update" $ do
+            ruleCatches noYumUpdate "RUN yum update"
+            ruleCatchesNot noYumUpdate "RUN yum install -y httpd-2.4.42 && yum clean all"
+            ruleCatchesNot noYumUpdate "RUN bash -c `# not even a yum command`"
+            onBuildRuleCatches noYumUpdate "RUN yum update"
+            onBuildRuleCatchesNot noYumUpdate "RUN yum install -y httpd-2.4.42 && yum clean all"
+            onBuildRuleCatchesNot noYumUpdate "RUN bash -c `# not even a yum command`"
+        it "yum version pinning" $ do
+            ruleCatches yumVersionPinned "RUN yum install -y tomcat && yum clean all"
+            ruleCatchesNot yumVersionPinned "RUN yum install -y tomcat-9.2 && yum clean all"
+            ruleCatchesNot yumVersionPinned "RUN bash -c `# not even a yum command`"
+            onBuildRuleCatches yumVersionPinned "RUN yum install -y tomcat && yum clean all"
+            onBuildRuleCatchesNot yumVersionPinned "RUN yum install -y tomcat-9.2 && yum clean all"
+            onBuildRuleCatchesNot yumVersionPinned "RUN bash -c `# not even a yum command`"
+        it "yum no clean all" $ do
+            ruleCatches yumCleanup "RUN yum install -y mariadb-10.4"
+            ruleCatchesNot yumCleanup "RUN yum install -y mariadb-10.4 && yum clean all"
+            ruleCatchesNot yumCleanup "RUN bash -c `# not even a yum command`"
+            onBuildRuleCatches yumCleanup "RUN yum install -y mariadb-10.4"
+            onBuildRuleCatchesNot yumCleanup "RUN yum install -y mariadb-10.4 && yum clean all"
+            onBuildRuleCatchesNot yumCleanup "RUN bash -c `# not even a yum command`"
+        it "yum non-interactive" $ do
+            ruleCatches yumYes "RUN yum install httpd-2.4.24 && yum clean all"
+            ruleCatchesNot yumYes "RUN yum install -y httpd-2.4.24 && yum clean all"
+            ruleCatchesNot yumYes "RUN bash -c `# not even a yum command`"
+            onBuildRuleCatches yumYes "RUN yum install httpd-2.4.24 && yum clean all"
+            onBuildRuleCatchesNot yumYes "RUN yum install -y httpd-2.4.24 && yum clean all"
+            onBuildRuleCatchesNot yumYes "RUN bash -c `# not even a yum command`"
     --
+    describe "zypper rules" $ do
+        it "zupper update" $ do
+            ruleCatches noZypperUpdate "RUN zypper update"
+            ruleCatches noZypperUpdate "RUN zypper up"
+            ruleCatches noZypperUpdate "RUN zypper dist-upgrade"
+            ruleCatches noZypperUpdate "RUN zypper dup"
+            onBuildRuleCatches noZypperUpdate "RUN zypper update"
+            onBuildRuleCatches noZypperUpdate "RUN zypper up"
+            onBuildRuleCatches noZypperUpdate "RUN zypper dist-upgrade"
+            onBuildRuleCatches noZypperUpdate "RUN zypper dup"
+        it "zypper version pinning" $ do
+-- NOTE: In Haskell strings, '\' has to be escaped. And in shell commands, '>'
+-- and '<' have to be escaped. Hence the double escaping.
+            ruleCatches zypperVersionPinned "RUN zypper install -y tomcat && zypper clean"
+            ruleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat=9.0.39 && zypper clean"
+            ruleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\>=9.0 && zypper clean"
+            ruleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\>9.0 && zypper clean"
+            ruleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\<=9.0 && zypper clean"
+            ruleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\<9.0 && zypper clean"
+            ruleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat-9.0.39-1.rpm && zypper clean"
+            onBuildRuleCatches zypperVersionPinned "RUN zypper install -y tomcat && zypper clean"
+            onBuildRuleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat=9.0.39 && zypper clean"
+            onBuildRuleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\>=9.0 && zypper clean"
+            onBuildRuleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\>9.0 && zypper clean"
+            onBuildRuleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\<=9.0 && zypper clean"
+            onBuildRuleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat\\<9.0 && zypper clean"
+            onBuildRuleCatchesNot zypperVersionPinned "RUN zypper install -y tomcat-9.0.39-1.rpm && zypper clean"
+        it "zypper no clean all" $ do
+            ruleCatches zypperCleanup "RUN zypper install -y mariadb=10.4"
+            ruleCatchesNot zypperCleanup "RUN zypper install -y mariadb=10.4 && zypper clean"
+            ruleCatchesNot zypperCleanup "RUN zypper install -y mariadb=10.4 && zypper cc"
+            onBuildRuleCatches zypperCleanup "RUN zypper install -y mariadb=10.4"
+            onBuildRuleCatchesNot zypperCleanup "RUN zypper install -y mariadb=10.4 && zypper clean"
+            onBuildRuleCatchesNot zypperCleanup "RUN zypper install -y mariadb=10.4 && zypper cc"
+        it "zypper non-interactive" $ do
+            ruleCatches zypperYes "RUN zypper install httpd=2.4.24 && zypper clean"
+            ruleCatchesNot zypperYes "RUN zypper install -y httpd=2.4.24 && zypper clean"
+            ruleCatchesNot zypperYes "RUN zypper install --no-confirm httpd=2.4.24 && zypper clean"
+            onBuildRuleCatches zypperYes "RUN zypper install httpd=2.4.24 && zypper clean"
+            onBuildRuleCatchesNot zypperYes "RUN zypper install -y httpd=2.4.24 && zypper clean"
+            onBuildRuleCatchesNot zypperYes "RUN zypper install --no-confirm httpd=2.4.24 && zypper clean"
+    --
+    describe "dnf rules" $ do
+      it "dnf update" $ do
+        ruleCatches noDnfUpdate "RUN dnf upgrade"
+        onBuildRuleCatches noDnfUpdate "RUN dnf upgrade"
+      it "dnf version pinning" $ do
+        ruleCatches dnfVersionPinned "RUN dnf install -y tomcat && dnf clean all"
+        ruleCatchesNot dnfVersionPinned "RUN dnf install -y tomcat-9.0.1 && dnf clean all"
+        onBuildRuleCatches dnfVersionPinned "RUN dnf install -y tomcat && dnf clean all"
+        onBuildRuleCatchesNot dnfVersionPinned "RUN dnf install -y tomcat-9.0.1 && dnf clean all"
+      it "dnf no clean all" $ do
+        ruleCatches dnfCleanup "RUN dnf install -y mariadb-10.4"
+        ruleCatchesNot dnfCleanup "RUN dnf install -y mariadb-10.4 && dnf clean all"
+        onBuildRuleCatches dnfCleanup "RUN dnf install -y mariadb-10.4"
+        onBuildRuleCatchesNot dnfCleanup "RUN dnf install -y mariadb-10.4 && dnf clean all"
+      it "dnf non-interactive" $ do
+        ruleCatches dnfYes "RUN dnf install httpd-2.4.24 && dnf clean all"
+        ruleCatchesNot dnfYes "RUN dnf install -y httpd-2.4.24 && dnf clean all"
+        onBuildRuleCatches dnfYes "RUN dnf install httpd-2.4.24 && dnf clean all"
+        onBuildRuleCatchesNot dnfYes "RUN dnf install -y httpd-2.4.24 && dnf clean all"
+    --
+    describe "dnf rules" $ do
+      it "dnf update" $ do
+        ruleCatches noDnfUpdate "RUN dnf upgrade"
+        ruleCatches noDnfUpdate "RUN dnf upgrade-minimal"
+        ruleCatchesNot noDnfUpdate "RUN notdnf upgrade"
+        onBuildRuleCatches noDnfUpdate "RUN dnf upgrade"
+        onBuildRuleCatches noDnfUpdate "RUN dnf upgrade-minimal"
+        onBuildRuleCatchesNot noDnfUpdate "RUN notdnf upgrade"
+      it "dnf version pinning" $ do
+        ruleCatches dnfVersionPinned "RUN dnf install -y tomcat && dnf clean all"
+        ruleCatchesNot dnfVersionPinned "RUN dnf install -y tomcat-9.0.1 && dnf clean all"
+        ruleCatchesNot dnfVersionPinned "RUN notdnf install tomcat"
+        onBuildRuleCatches dnfVersionPinned "RUN dnf install -y tomcat && dnf clean all"
+        onBuildRuleCatchesNot dnfVersionPinned "RUN dnf install -y tomcat-9.0.1 && dnf clean all"
+        onBuildRuleCatchesNot dnfVersionPinned "RUN notdnf install tomcat"
+      it "dnf no clean all" $ do
+        ruleCatches dnfCleanup "RUN dnf install -y mariadb-10.4"
+        ruleCatchesNot dnfCleanup "RUN dnf install -y mariadb-10.4 && dnf clean all"
+        ruleCatchesNot dnfCleanup "RUN notdnf install mariadb"
+        onBuildRuleCatches dnfCleanup "RUN dnf install -y mariadb-10.4"
+        onBuildRuleCatchesNot dnfCleanup "RUN dnf install -y mariadb-10.4 && dnf clean all"
+        onBuildRuleCatchesNot dnfCleanup "RUN notdnf install mariadb"
+      it "dnf non-interactive" $ do
+        ruleCatches dnfYes "RUN dnf install httpd-2.4.24 && dnf clean all"
+        ruleCatchesNot dnfYes "RUN dnf install -y httpd-2.4.24 && dnf clean all"
+        ruleCatchesNot dnfYes "RUN notdnf install httpd"
+        onBuildRuleCatches dnfYes "RUN dnf install httpd-2.4.24 && dnf clean all"
+        onBuildRuleCatchesNot dnfYes "RUN dnf install -y httpd-2.4.24 && dnf clean all"
+        onBuildRuleCatchesNot dnfYes "RUN notdnf install httpd"
+    --
     describe "apt-get rules" $ do
       it "apt" $
         let dockerFile =
@@ -410,6 +518,11 @@
       it "pip3 version pinned" $ do
         ruleCatchesNot pipVersionPinned "RUN pip3 install MySQL_python==1.2.2"
         onBuildRuleCatchesNot pipVersionPinned "RUN pip3 install MySQL_python==1.2.2"
+      it "pip3 install from local package" $ do
+        ruleCatchesNot pipVersionPinned "RUN pip3 install mypkg.whl"
+        ruleCatchesNot pipVersionPinned "RUN pip3 install mypkg.tar.gz"
+        onBuildRuleCatchesNot pipVersionPinned "RUN pip3 install mypkg.whl"
+        onBuildRuleCatchesNot pipVersionPinned "RUN pip3 install mypkg.tar.gz"
       it "pip install requirements" $ do
         ruleCatchesNot pipVersionPinned "RUN pip install -r requirements.txt"
         onBuildRuleCatchesNot pipVersionPinned "RUN pip install -r requirements.txt"
@@ -898,9 +1011,18 @@
                 "ENTRYPOINT another"
               ]
          in ruleCatches multipleEntrypoints $ Text.unlines dockerFile
+
       it "single entry" $ ruleCatchesNot multipleEntrypoints "ENTRYPOINT /bin/true"
       it "no entry" $ ruleCatchesNot multipleEntrypoints "FROM busybox"
+      it "workdir relative" $ ruleCatches absoluteWorkdir "WORKDIR relative/dir"
+      it "workdir absolute" $ ruleCatchesNot absoluteWorkdir "WORKDIR /usr/local"
       it "workdir variable" $ ruleCatchesNot absoluteWorkdir "WORKDIR ${work}"
+      it "workdir relative single quotes" $ ruleCatches absoluteWorkdir "WORKDIR \'relative/dir\'"
+      it "workdir absolute single quotes" $ ruleCatchesNot absoluteWorkdir "WORKDIR \'/usr/local\'"
+      -- no test for variable/single quotes since the variable would not expand.
+      it "workdir relative double quotes" $ ruleCatches absoluteWorkdir "WORKDIR \"relative/dir\""
+      it "workdir absolute double quotes" $ ruleCatchesNot absoluteWorkdir "WORKDIR \"/usr/local\""
+      it "workdir variable double quotes" $ ruleCatchesNot absoluteWorkdir "WORKDIR \"${dir}\""
       it "scratch" $ ruleCatchesNot noUntagged "FROM scratch"
     --
     describe "add files and archives" $ do
