CI Hex pm License: MIT

Parse and match .dockerignore files in Elixir with Docker-compatible semantics.

Dockerignore answers whether a relative path should be kept in or excluded from a Docker build context. It implements ordered rules, negation, parent-directory matching, recursive ** patterns, Docker's ignore-file preprocessing, and source-aware validation errors.

Use it when an Elixir application needs to make the same .dockerignore path-selection decisions as Docker tooling, including:

  • build-context archive generators and remote builders;
  • CI pipelines, deployment tools, and repository scanners;
  • developer tools that preview or validate Docker build contexts;
  • AI coding agents and MCP servers that must avoid ignored build artifacts; and
  • any service that accepts .dockerignore content and evaluates paths without shelling out to Docker or Go.

Compile a source once and reuse the immutable matcher across processes. The core package does not walk the filesystem and has zero runtime dependencies.

Matcher behavior is differentially verified against moby/patternmatcher v0.6.1. Moby, Docker, and Go are not runtime dependencies.

Why not glob?

.dockerignore is an ordered rule language, not a list of independent glob patterns. A generic matcher can produce a different build context from Docker.

FeatureGeneric globDockerignore
Comments, blank lines, whitespace, and path cleaningUsually caller-definedPatternMatcher-compatible preprocessing
Negation with !Usually unavailableOrdered exclusion and re-inclusion
Parent-directory decisionsPattern-onlyMatching parents affect descendants
Recursive **Library-specificmoby/patternmatcher v0.6.1 behavior
Invalid patternsOften fail during matching or silently missValidated during compilation with source line context
Compatibility evidenceImplementation-specificDifferentially verified against the pinned Go matcher

This matters for tooling: an approximate matcher can send unnecessary build artifacts, omit required files, or report a context that disagrees with Docker.

Installation

Add dockerignore to your dependencies in mix.exs:

def deps do
  [
    {:dockerignore, "~> 0.1"}
  ]
end

To use the repository version before or between Hex releases:

{:dockerignore, git: "https://github.com/ivan-podgurskiy/dockerignore.git"}

Quick start

source = """
_build/
*.log
!important.log
"""

matcher = Dockerignore.compile!(source)

Dockerignore.ignored?(matcher, "_build/prod/lib/app.beam")
#=> true

Dockerignore.ignored?(matcher, "debug.log")
#=> true

Dockerignore.ignored?(matcher, "important.log")
#=> false

Dockerignore.filter(matcher, ["debug.log", "README.md", "important.log"])
#=> ["README.md", "important.log"]

Dockerignore.explain(matcher, "important.log")
#=> {:kept, %Dockerignore.Pattern{source: "!important.log", line: 3, ...}}

All paths passed to the matcher are relative to the Docker build-context root and use / as the separator.

Public API

Parse

parse/1 preprocesses and validates a source, returning compiled public pattern data in source order. parse!/1 returns the same list or raises Dockerignore.Error.

source = "_build/\n*.log\n!important.log"

{:ok, patterns} = Dockerignore.parse(source)
Enum.map(patterns, &{&1.pattern, &1.negated?})
#=> [{"_build", false}, {"*.log", false}, {"important.log", true}]

patterns = Dockerignore.parse!(source)
length(patterns)
#=> 3

Compile

compile/1 parses the source and builds an immutable matcher. compile!/1 returns the matcher or raises Dockerignore.Error. Matching does not mutate the matcher or lazily add state, so it can be shared between processes.

{:ok, matcher} = Dockerignore.compile(source)
%Dockerignore.Matcher{patterns: patterns} = matcher

matcher = Dockerignore.compile!(source)
is_struct(matcher, Dockerignore.Matcher)
#=> true

Match

ignored?/2 cleans the path with pure POSIX path rules and returns whether the path is ignored. A compiled matcher cannot produce a pattern error during matching.

Dockerignore.ignored?(matcher, "_build/prod/lib/app.beam")
#=> true

Dockerignore.ignored?(matcher, "important.log")
#=> false

Filter

filter/2 accepts any enumerable and eagerly returns only kept paths. It preserves input order and duplicate entries.

paths = ["debug.log", "README.md", "important.log", "README.md"]
Dockerignore.filter(matcher, paths)
#=> ["README.md", "important.log", "README.md"]

Dockerignore.filter(matcher, Stream.map(paths, & &1))
#=> ["README.md", "important.log", "README.md"]

Explain

explain/2 returns the final state and the last rule that changed it. The rule may have matched the path itself or one of its parent directories.

Dockerignore.explain(matcher, "debug.log")
#=> {:ignored, %Dockerignore.Pattern{source: "*.log", ...}}

Dockerignore.explain(matcher, "important.log")
#=> {:kept, %Dockerignore.Pattern{source: "!important.log", ...}}

Dockerignore.explain(matcher, "README.md")
#=> {:kept, :no_match}

The possible results are {:ignored, pattern}, {:kept, pattern}, and {:kept, :no_match}. %Dockerignore.Pattern{} exposes the original source line, one-based line number, cleaned pattern, negation flag, match type, and generated regular-expression source when applicable.

Paths and errors

Matching paths are relative to the build-context root and must use / as the separator. Repeated separators, . segments, .. segments, and trailing separators are cleaned with host-independent POSIX rules. The library does not walk the filesystem, resolve symlinks, expand paths, or consult the host OS. The cleaned path . is always kept.

Patterns are processed in source order. Normal rules ignore matching paths; when a normal rule matches an ancestor, its descendants are ignored too. A later ! rule can re-include a matching path, including a path below a parent rule, following the upstream matcher behavior.

Invalid content is rejected before matching and retains original source line context, including comments and blank lines:

{:error, error} = Dockerignore.compile("ok\n[")
error.line
#=> 2

Exception.message(error)
#=> "invalid .dockerignore pattern on line 2: \"[\" (invalid character class)"

Dockerignore.compile!("ok\n[")
# raises Dockerignore.Error with the same line context

Compatibility and verification

Version 0.1.0 targets the observable behavior of moby/patternmatcher v0.6.1, including its ignorefile preprocessing. The compatibility boundary is the same source and the same relative, slash-delimited path producing the same kept or ignored decision.

Regexp-mode behavior is pinned to the Go 1.26.0 RE2 controller. The package embeds Unicode 15.0.0 category, script, and alias tables generated from that controller, so applications do not need Go or a runtime regular-expression dependency.

The conformance suite includes the upstream decision and error cases plus differential edge cases. The development oracle currently verifies 140 match decisions and 24 compile errors with zero mismatches against the pinned Go implementation. See the conformance test and oracle.

The upstream implementation is the contract for supported inputs. This library does not copy Go's internal mutability or platform-dependent path behavior.

Roadmap

  • Publish and maintain the v0.1 line on Hex and HexDocs.
  • Evaluate future moby/patternmatcher releases as explicit compatibility targets before changing matcher semantics.
  • Expand differential fixtures when new upstream edge cases are identified.
  • Evaluate optional helpers for Dockerfile-specific ignore discovery and build context assembly while keeping the core matcher independent from filesystem I/O.
  • Add repeatable performance and memory benchmarks for representative build contexts.

Changelog

See CHANGELOG.md for release history. Version 0.1.0 introduces the parser, immutable matcher, public decision APIs, bounded RE2-compatible engine, embedded Unicode tables, and the pinned PatternMatcher conformance suite.

Non-goals

Version 0.1 does not:

  • walk a filesystem or construct a Docker build-context archive;
  • reproduce Docker CLI rules that always transmit special files;
  • discover per-Dockerfile ignore files;
  • support Windows separator semantics; or
  • claim compatibility with PatternMatcher versions other than v0.6.1.

License and attribution

The package code is MIT licensed. Matcher semantics and adapted conformance data derive from Docker's moby/patternmatcher v0.6.1 and are attributed under Apache-2.0 in NOTICE and LICENSES/Apache-2.0.txt.

The embedded Go 1.26.0 Unicode 15.0.0 tables are attributed under BSD-3-Clause in NOTICE and LICENSES/BSD-3-Clause-Go.txt.