Predict container failures before you run, and diagnose them at the root cause when they happen.
container-preflight predicts whether a container project will build and run on a machine before you run it, and explains each predicted failure once, at its root cause.
Linters look only at your Dockerfile. docker info looks only at your machine. Most "works on my machine" failures come from the mismatch between the two: an amd64-only image on an Apple silicon laptop, a Compose file that needs more memory than Docker Desktop's VM has, a COPY of a file your .dockerignore excludes, a port something else already holds. container-preflight check checks the project and the host together.
container-preflight check .Preflight builds three views and joins them:
- What the project needs. The Compose file and every Dockerfile are parsed with Docker's own parsers (
compose-go, BuildKit's Dockerfile frontend). This covers platforms, memory limits and replicas, published ports, bind mounts,COPYsources (only in stages BuildKit will actually build), BuildKit-only syntax, Compose features and interpolated variables. - What the host offers. The daemon's platform and memory (on macOS and Windows these are the VM's, not the laptop's), emulation, Compose/buildx versions, kernel settings, local images, credential helpers and live port usage.
- What registries publish. Each image's platforms and download size, read from the registry without pulling.
Each finding quotes its evidence from both sides and the error Docker would print:
✗ [web] COPY source ".env.production" is excluded by .dockerignore Dockerfile:11
web/.dockerignore: pattern ".env*"
would fail with: "failed to compute cache key: \"/.env.production\": not found"
fix: Remove or negate the pattern ".env*" in web/.dockerignore.
Checks depend on facts: the Docker CLI → the daemon → its platform and memory, and so on. When a fact cannot be established, every check that needs it is grouped under that single root cause instead of failing separately. Checks that do not depend on it keep running.
ROOT CAUSES
✗ Docker daemon is not reachable
fix: Start Docker Desktop (or `colima start`, `sudo systemctl start docker`) …
blocks 5 check(s): image.platform, resources.memory, resources.gpu, …
# on the target machine (a teammate's laptop, a CI runner, a server)
container-preflight snapshot -o ci-runner.json
# anywhere
container-preflight check . --host ci-runner.jsonexamples/ contains a deliberately broken project and two example host snapshots. The same project gets different predictions on each:
container-preflight check examples/broken-shop --host examples/hosts/macbook-m2-desktop.json
container-preflight check examples/broken-shop --host examples/hosts/ubuntu-legacy-server.jsonBuilt-in rules cover common failures. learn turns the failures your project actually hits into new rules:
container-preflight learn -- docker compose up dbIt runs the command and extracts the decisive error line from the output. It turns that line into a signature, with addresses, IDs, paths and numbers generalized. It then records the conditions at the time: image, platform, emulation (QEMU/Rosetta), memory, builder, and which environment variables were set (never their values). The rule goes to .container-preflight/learned.yaml; commit it, and preflight predicts the failure for everyone on the team.
Rules improve with evidence:
- Another failure with the same error drops the conditions the two runs disagree on, which makes the rule more general.
- A working run (
learn --success -- docker compose up -d) that satisfies a rule adds the condition that tells it apart from the failures, which makes the rule more specific. If no such condition exists, the rule is marked unreliable and no longer reported. - A failure a built-in rule already predicted is reported as such and is not learned twice.
Example from a real run: Postgres started without a password failed. learn created a rule for image=postgres. After a password was added, the successful run refined the rule to env.POSTGRES_PASSWORD=unset, and preflight now flags any Compose file that uses Postgres without one.
| Rule | Predicts |
|---|---|
image.exists |
tag or repository missing from the registry |
image.platform |
no variant for the host's platform, emulation needed or unavailable (exec format error) |
registry.credentials |
configured credential helper not installed (error getting credentials) |
build.context |
COPY/ADD sources missing, excluded by .dockerignore, or outside the context; bad target or ARG |
build.buildkit |
RUN --mount, heredocs, COPY --chmod/--link without BuildKit |
compose.version |
Compose features newer than the installed Compose (include, name, develop, hooks, gpus, models) |
compose.env |
unset variables, required ${VAR:?} without a value |
resources.memory |
known image minimums and total limits vs Docker's memory |
resources.disk |
image download size ×2.5 vs free disk (the smaller of the VM disk and the host volume it lives on) |
host.disk |
too little space on this machine for Docker Desktop/Colima/OrbStack to start or grow their VM |
resources.gpu |
GPU requests without the NVIDIA runtime, or on macOS |
kernel.sysctl |
vm.max_map_count for Elasticsearch/OpenSearch/SonarQube |
ports.conflict |
two services, or several replicas, on one host port |
ports.host |
host port already held by another process or container; privileged ports under rootless Docker |
mounts.bind |
missing bind sources that Docker will turn into directories, stale empty directories |
mounts.permissions |
non-root containers writing to bind mounts Docker creates as root or that another uid owns; SELinux without :z; Postgres data folders bind-mounted from Windows |
mounts.sharing |
paths outside Docker Desktop's shared folders (read from the running VM on macOS; home only on Linux) |
Containers started without Compose are checked the same way:
container-preflight check --run "docker run -p 8080:80 -v ./site:/usr/share/nginx/html nginx:1.27"Use --format json for machine-readable output and --offline to skip registry lookups. The exit code is 1 when a blocking problem is predicted.
- System Diagnostics: Checks your operating system, CPU architecture, memory (RAM), and disk space to ensure you meet minimum requirements.
- Docker Health: Validates that Docker is installed, the daemon is running, the socket is accessible, and client/server versions are compatible.
- Docker Compose & Buildx: Ensures crucial container plugins are installed and ready.
- Static Inspection: The
inspectcommand statically analyzes yourDockerfileanddocker-compose.ymlfor common anti-patterns (like using the:latesttag in production). - Multiple Output Formats: View reports natively in your terminal, or export them to JSON, Markdown, or HTML.
If you have Go installed, you can install the CLI directly:
go install github.com/raphgm/container-preflight@latestPre-compiled binaries for macOS, Linux, and Windows are automatically generated for every release. Head over to the Releases page to download the binary for your operating system.
Run the diagnostic engine to check your environment:
container-preflight hostYou can export the results to different formats using the --format flag:
# JSON Output
container-preflight host --format json > report.json
# Markdown Output
container-preflight host --format markdown > report.md
# HTML Output (Standalone Webpage)
container-preflight host --format html > report.htmlStatically analyze the configuration files in your current directory (or any specified path):
container-preflight inspect .container-preflight is built with a highly decoupled, plugin-based architecture.
- Engine: The core
internal/engineruns the execution loop and aggregates results. - Providers: Checks are grouped by Providers (e.g.,
system,docker,compose). - Renderers: Custom output rendering is handled via the
Rendererinterface ininternal/renderer.
Adding a new check is as simple as creating a struct that implements the check.Check interface and registering it with a Provider!
This project is licensed under the MIT License.