<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DEEPAK M S</title>
    <description>The latest articles on DEV Community by DEEPAK M S (@deepak_ms).</description>
    <link>https://dev.to/deepak_ms</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3969008%2F1c20a044-b823-4244-9937-5da2c8c2fd4b.png</url>
      <title>DEV Community: DEEPAK M S</title>
      <link>https://dev.to/deepak_ms</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deepak_ms"/>
    <language>en</language>
    <item>
      <title>I Built an Offline Docker Security Scanner in Go Parsing Dockerfiles, Building a Rule Engine, and Shipping a Cross-Platform CLI</title>
      <dc:creator>DEEPAK M S</dc:creator>
      <pubDate>Fri, 05 Jun 2026 02:34:14 +0000</pubDate>
      <link>https://dev.to/deepak_ms/i-built-an-offline-docker-security-scanner-in-go-parsing-dockerfiles-building-a-rule-engine-and-ib7</link>
      <guid>https://dev.to/deepak_ms/i-built-an-offline-docker-security-scanner-in-go-parsing-dockerfiles-building-a-rule-engine-and-ib7</guid>
      <description>&lt;p&gt;Most developers don't write Dockerfiles from scratch.&lt;/p&gt;

&lt;p&gt;They copy snippets from blog posts, GitHub repositories, Stack Overflow answers, or previous projects.&lt;/p&gt;

&lt;p&gt;The problem is that many of those examples aren't production-safe.&lt;/p&gt;

&lt;p&gt;Over the years I've repeatedly seen Dockerfiles that:&lt;/p&gt;

&lt;p&gt;Run containers as root&lt;br&gt;
Store secrets directly in ENV instructions&lt;br&gt;
Use latest image tags&lt;br&gt;
Pipe curl output directly into bash&lt;br&gt;
Expose unnecessary ports&lt;br&gt;
Skip resource limits entirely&lt;/p&gt;

&lt;p&gt;These issues often remain unnoticed until a security review, a penetration test, or a production incident.&lt;/p&gt;

&lt;p&gt;I wanted a simple way to catch these problems early.&lt;/p&gt;

&lt;p&gt;That led me to build dockersec.&lt;/p&gt;

&lt;p&gt;An open-source CLI tool that scans Dockerfiles and docker-compose files for security vulnerabilities, misconfigurations, and bad practices.&lt;/p&gt;

&lt;p&gt;The entire scanner runs offline and ships as a single Go binary.&lt;/p&gt;

&lt;p&gt;The Problem: Bad Dockerfiles Reach Production&lt;/p&gt;

&lt;p&gt;Let's take a simple example.&lt;/p&gt;

&lt;p&gt;FROM node:latest&lt;/p&gt;

&lt;p&gt;ENV AWS_SECRET_KEY=my-secret-key&lt;/p&gt;

&lt;p&gt;RUN curl &lt;a href="https://example.com/install.sh" rel="noopener noreferrer"&gt;https://example.com/install.sh&lt;/a&gt; | bash&lt;/p&gt;

&lt;p&gt;CMD ["npm", "start"]&lt;/p&gt;

&lt;p&gt;At first glance this may look harmless.&lt;/p&gt;

&lt;p&gt;But it contains multiple security problems:&lt;/p&gt;

&lt;p&gt;Unpinned base image (latest)&lt;br&gt;
Secret embedded into image layers&lt;br&gt;
Remote code execution risk via curl | bash&lt;br&gt;
Container runs as root&lt;/p&gt;

&lt;p&gt;These are exactly the kinds of issues dockersec was designed to catch.&lt;/p&gt;

&lt;p&gt;What dockersec Does&lt;/p&gt;

&lt;p&gt;Running a scan is intentionally simple.&lt;/p&gt;

&lt;p&gt;dockersec scan .&lt;/p&gt;

&lt;p&gt;The scanner analyzes:&lt;/p&gt;

&lt;p&gt;Dockerfiles&lt;br&gt;
docker-compose.yml files&lt;br&gt;
Custom YAML security rules&lt;/p&gt;

&lt;p&gt;Example output:&lt;/p&gt;

&lt;p&gt;Severity: CRITICAL&lt;br&gt;
Rule: DF004&lt;/p&gt;

&lt;p&gt;Possible secret in ENV instruction: AWS_SECRET_KEY&lt;/p&gt;

&lt;p&gt;Fix:&lt;br&gt;
Use runtime secret injection instead.&lt;/p&gt;

&lt;p&gt;The goal isn't just finding issues.&lt;/p&gt;

&lt;p&gt;The tool also explains:&lt;/p&gt;

&lt;p&gt;Why the issue matters&lt;br&gt;
How attackers can exploit it&lt;br&gt;
How to fix it&lt;/p&gt;

&lt;p&gt;This makes the output useful even for developers who aren't security specialists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08erzlgb8gxg8h98dw8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08erzlgb8gxg8h98dw8v.png" alt=" " width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Current coverage includes:&lt;/p&gt;

&lt;p&gt;20 Dockerfile rules&lt;br&gt;
8 docker-compose rules&lt;br&gt;
Custom YAML rules&lt;br&gt;
Parsing Dockerfiles Using BuildKit AST&lt;/p&gt;

&lt;p&gt;One design decision I made early was avoiding regular expressions for Dockerfile parsing.&lt;/p&gt;

&lt;p&gt;Dockerfiles contain many edge cases:&lt;/p&gt;

&lt;p&gt;RUN echo hello&lt;/p&gt;

&lt;p&gt;RUN \&lt;br&gt;
    apt-get update &amp;amp;&amp;amp; \&lt;br&gt;
    apt-get install curl&lt;/p&gt;

&lt;p&gt;Trying to analyze these correctly using string matching quickly becomes fragile.&lt;/p&gt;

&lt;p&gt;Instead, I used Docker's own parser from the BuildKit project.&lt;/p&gt;

&lt;p&gt;result, err := parser.Parse(file)&lt;/p&gt;

&lt;p&gt;The parser produces an Abstract Syntax Tree (AST).&lt;/p&gt;

&lt;p&gt;Each instruction becomes a node that can be inspected programmatically.&lt;/p&gt;

&lt;p&gt;for child := ast.AST.Children; child != nil; child = child.Next {&lt;br&gt;
    fmt.Println(child.Value)&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This gives the scanner a structured representation of the Dockerfile rather than raw text.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;p&gt;Handles Docker syntax correctly&lt;br&gt;
Easier rule implementation&lt;br&gt;
Future-proof against syntax variations&lt;br&gt;
Less parser maintenance&lt;/p&gt;

&lt;p&gt;Using the same parser Docker itself relies on significantly reduced complexity.&lt;/p&gt;

&lt;p&gt;Building the Rule Engine&lt;/p&gt;

&lt;p&gt;Once the parser was working, I needed a clean way to organize security rules.&lt;/p&gt;

&lt;p&gt;I wanted contributors to be able to add new rules without modifying a giant switch statement.&lt;/p&gt;

&lt;p&gt;The solution was a self-registration pattern.&lt;/p&gt;

&lt;p&gt;Each rule registers itself during package initialization.&lt;/p&gt;

&lt;p&gt;func init() {&lt;br&gt;
    RegisterRule(NoRootUserRule{})&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Every rule implements a common interface.&lt;/p&gt;

&lt;p&gt;type Rule interface {&lt;br&gt;
    ID() string&lt;br&gt;
    Severity() Severity&lt;br&gt;
    Check(ast *parser.Result) []Finding&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;When the scanner starts, all rules have already registered themselves.&lt;/p&gt;

&lt;p&gt;The engine simply loops through them.&lt;/p&gt;

&lt;p&gt;for _, rule := range rules {&lt;br&gt;
    findings := rule.Check(parsedFile)&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;p&gt;Easy extensibility&lt;br&gt;
Clean separation of concerns&lt;br&gt;
No central registry maintenance&lt;br&gt;
New rules automatically become available&lt;/p&gt;

&lt;p&gt;This pattern is widely used in plugin systems and worked extremely well for this project.&lt;/p&gt;

&lt;p&gt;YAML Rules: Security Rules Without Writing Go&lt;/p&gt;

&lt;p&gt;One goal for dockersec was making community contributions easy.&lt;/p&gt;

&lt;p&gt;Not everyone wants to write Go code.&lt;/p&gt;

&lt;p&gt;So I added support for YAML-based rules.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;id: CUSTOM001&lt;br&gt;
type: dockerfile&lt;br&gt;
severity: HIGH&lt;/p&gt;

&lt;p&gt;description: |&lt;br&gt;
  netcat found in image&lt;/p&gt;

&lt;p&gt;fix: |&lt;br&gt;
  Remove netcat&lt;/p&gt;

&lt;p&gt;match:&lt;br&gt;
  instruction: RUN&lt;br&gt;
  contains: "nc"&lt;/p&gt;

&lt;p&gt;Drop the file into:&lt;/p&gt;

&lt;p&gt;rules/dockerfile/&lt;/p&gt;

&lt;p&gt;and dockersec loads it automatically.&lt;/p&gt;

&lt;p&gt;No compilation required.&lt;/p&gt;

&lt;p&gt;This approach allows:&lt;/p&gt;

&lt;p&gt;Security teams to create internal policies&lt;br&gt;
Contributors to add rules quickly&lt;br&gt;
Organizations to enforce custom standards&lt;/p&gt;

&lt;p&gt;without touching application code.&lt;/p&gt;

&lt;p&gt;Shipping Releases with GoReleaser&lt;/p&gt;

&lt;p&gt;I wanted installation to be as simple as possible.&lt;/p&gt;

&lt;p&gt;Users shouldn't need Go installed.&lt;/p&gt;

&lt;p&gt;They should be able to download a binary and run it.&lt;/p&gt;

&lt;p&gt;For releases I chose GoReleaser.&lt;/p&gt;

&lt;p&gt;A single tag push generates builds for:&lt;/p&gt;

&lt;p&gt;Linux&lt;br&gt;
macOS&lt;br&gt;
Windows&lt;/p&gt;

&lt;p&gt;Example release workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: Release
uses: goreleaser/goreleaser-action@v6&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined with GitHub Actions, publishing a new version became completely automated.&lt;/p&gt;

&lt;p&gt;The release pipeline:&lt;/p&gt;

&lt;p&gt;Push a tag&lt;br&gt;
GitHub Actions starts&lt;br&gt;
GoReleaser builds binaries&lt;br&gt;
Release assets are uploaded automatically&lt;/p&gt;

&lt;p&gt;This significantly reduced maintenance overhead.&lt;/p&gt;

&lt;p&gt;CI/CD Integration&lt;/p&gt;

&lt;p&gt;dockersec can also act as a security gate.&lt;/p&gt;

&lt;p&gt;dockersec scan . --fail-on HIGH&lt;/p&gt;

&lt;p&gt;If a HIGH or CRITICAL issue exists, the command exits with code 1.&lt;/p&gt;

&lt;p&gt;That means GitHub Actions can block merges automatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: Scan
run: dockersec scan . --fail-on HIGH&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows teams to enforce security checks before code reaches production.&lt;/p&gt;

&lt;p&gt;Lessons Learned&lt;/p&gt;

&lt;p&gt;Building dockersec taught me several things:&lt;/p&gt;

&lt;p&gt;Parsing structured files is usually better than regexes.&lt;br&gt;
Small focused CLIs can solve real problems.&lt;br&gt;
Self-registering plugins keep codebases maintainable.&lt;br&gt;
GoReleaser dramatically simplifies distribution.&lt;br&gt;
Good error messages are as important as detection logic.&lt;/p&gt;

&lt;p&gt;Most importantly, security tooling doesn't need to be complicated to be useful.&lt;/p&gt;

&lt;p&gt;Sometimes a fast, local, developer-friendly tool provides more value than an enterprise platform.&lt;/p&gt;

&lt;p&gt;Try It Out&lt;/p&gt;

&lt;p&gt;dockersec is fully open source and available on GitHub.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Deepak-coder80/dockersec" rel="noopener noreferrer"&gt;https://github.com/Deepak-coder80/dockersec&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Project Page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deepakms.com/projects/dockersec.html" rel="noopener noreferrer"&gt;https://deepakms.com/projects/dockersec.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find it useful, feel free to open issues, suggest new rules, or contribute improvements.&lt;/p&gt;

&lt;p&gt;I'd love to hear feedback from the DevOps, Platform Engineering, and Security communities.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
