Preflight provides consistent checks for validating your container environment. Use it at different stages:
- Build time (
RUN preflight ...) – validate binaries, configs, and paths during image build - Runtime (
CMD ["sh", "-c", "preflight ... && ./app"]) – verify services are reachable before app startup - CI/CD (
docker run myimage preflight ...) – verify built images have correct tools and settings
This is especially useful for complex multi-stage builds where it's easy to make small mistakes (wrong COPY paths, missing shared libraries, misconfigured environment).
Commands
preflight cmd– verify binary exists and versionpreflight env– validate environment variablespreflight file– check file/directory propertiespreflight json– validate JSON and check keyspreflight prometheus– check Prometheus metricspreflight git– verify git repository statepreflight tcp– check TCP connectivitypreflight http– HTTP health checkspreflight hash– verify file checksumspreflight sys– check OS and architecturepreflight resource– verify system resourcespreflight user– check user existspreflight run– run checks from file
Reference
Verifies a command exists on PATH and can execute. By default, runs <command> --version to ensure the binary actually works (catches missing shared libraries, corrupt binaries, etc.).
preflight cmd <command> [flags]| Flag | Description |
|---|---|
--min <version> |
Minimum version required (inclusive) |
--max <version> |
Maximum version allowed (exclusive) |
--exact <version> |
Exact version required |
--range <constraint> |
Semver constraint (e.g., >=1.0, <2.0, ^1.5) |
--match <pattern> |
Regex pattern to match against version output |
--version-regex <pat> |
Regex with capture group to extract version |
--version-cmd <arg> |
Override the default --version argument |
--timeout <duration> |
Timeout for version command (default: 30s) |
# Basic check - exists and runs
preflight cmd myapp
# Version constraints
preflight cmd myapp --min 2.0
preflight cmd myapp --min 2.0 --max 3.0
preflight cmd onnxruntime --exact 1.16.0
# Regex match on version output
preflight cmd myapp --match "^v2\."
# Custom version command
preflight cmd ffmpeg --version-cmd -version # runs: ffmpeg -version
preflight cmd java --version-cmd "-version" # runs: java -version
# Custom timeout (for slow commands)
preflight cmd slow-binary --timeout 60s
preflight cmd quick-check --timeout 5s
# Extract version from messy output using regex capture group
# For output like "myapp version: 2.5.3 (built 2024-01-01)"
preflight cmd myapp --version-regex "version[:\s]+(\d+\.\d+\.\d+)" --min 2.0
# Semver range constraints
preflight cmd terraform --range ">=1.0, <2.0" # Between 1.x and 2.0
preflight cmd node --range "^18.0.0" # Compatible with 18.x
preflight cmd python3 --range "~3.11.0" # Patch releases of 3.11Validates environment variables exist and match requirements. By default, the variable must exist and be non-empty.
preflight env <variable> [flags]| Flag | Description |
|---|---|
--required |
Fail if not set (allows empty values) |
--match <pattern> |
Regex pattern to match against value |
--exact <value> |
Exact value required |
--one-of <values> |
Value must be one of these (comma-separated) |
--starts-with <str> |
Value must start with string |
--ends-with <str> |
Value must end with string |
--contains <str> |
Value must contain substring |
--is-numeric |
Value must be a valid number |
--min-len <n> |
Minimum string length |
--max-len <n> |
Maximum string length |
--hide-value |
Don't show value in output |
--mask-value |
Show first/last 3 chars only (e.g., sk-•••xyz) |
# Basic check - exists and non-empty
preflight env MODEL_PATH
# Allow empty values (just check if defined)
preflight env OPTIONAL_CONFIG --required
# Pattern matching
preflight env MODEL_PATH --match '^/models/'
preflight env AWS_SECRET_ARN --match '^arn:aws:secretsmanager:'
# Exact value
preflight env APP_ENV --exact production
# Value from allowed list
preflight env APP_ENV --one-of dev,staging,production
# String matchers
preflight env MODEL_PATH --starts-with /models/
preflight env CONFIG_FILE --ends-with .yaml
preflight env WEBHOOK_URL --contains example.com
# Numeric and length validation
preflight env PORT --is-numeric
preflight env API_KEY --min-len 32
preflight env CODE --max-len 6
# Hide sensitive values in logs
preflight env AWS_SECRET_ARN --hide-value # shows: [hidden]
preflight env AWS_SECRET_ARN --mask-value # shows: arn•••xyzChecks that a file or directory exists and meets requirements. By default, the path must exist and be readable.
preflight file <path> [flags]| Flag | Description |
|---|---|
--dir |
Expect a directory (fail if it's a file) |
--socket |
Expect a Unix socket (e.g., docker.sock) |
--symlink |
Expect a symbolic link |
--symlink-target <path> |
Expected symlink target path |
--writable |
Check write permission |
--executable |
Check execute permission |
--not-empty |
File must have size > 0 |
--min-size <bytes> |
Minimum file size |
--max-size <bytes> |
Maximum file size |
--match <pattern> |
Regex pattern to match against content |
--contains <string> |
Literal string to search in content |
--head <bytes> |
Limit content read to first N bytes |
--mode <perms> |
Minimum permissions (e.g., 0644 passes for 0666) |
--mode-exact <perms> |
Exact permissions required |
--owner <uid> |
Expected owner UID |
# Basic check - exists and readable
preflight file /etc/nginx/nginx.conf
# Directory checks
preflight file /var/log/app --dir --writable
# Unix socket checks (Docker-in-Docker, containerd)
preflight file /var/run/docker.sock --socket
preflight file /run/containerd/containerd.sock --socket
# Permission checks (minimum - file has at least these perms)
preflight file /etc/ssl/private/key.pem --mode 0600
# Permission checks (exact)
preflight file /etc/ssl/private/key.pem --mode-exact 0600
# Ownership checks (HashiCorp Consul/Vault pattern)
preflight file /data --owner 1000
preflight file /app/data --dir --owner 1000
# Symlink checks (capability management, alternative binaries)
preflight file /usr/bin/python --symlink
preflight file /usr/bin/python --symlink --symlink-target /usr/bin/python3
# Content checks (reads full file)
preflight file /etc/nginx/nginx.conf --contains "worker_processes"
preflight file /etc/hosts --match "127\.0\.0\.1"
# Content checks (limited to first 1KB)
preflight file /var/log/huge.log --contains "ERROR" --head 1024
# Size constraints
preflight file /app/data.json --not-empty
preflight file /var/log/app.log --max-size 10485760 # 10MBValidates JSON files and checks key/value assertions. Useful for verifying configuration files are valid and contain required settings.
preflight json <file> [flags]| Flag | Description |
|---|---|
--has-key <path> |
Check key exists (dot notation for nested keys) |
--key <path> |
Key to check value of (dot notation for nested keys) |
--exact <value> |
Exact value required (requires --key) |
--match <pattern> |
Regex pattern for value (requires --key) |
# Validate JSON syntax only
preflight json config.json
# Check key exists
preflight json config.json --has-key database.host
# Check nested key exists
preflight json package.json --has-key dependencies.express
# Check exact value
preflight json config.json --key environment --exact production
# Check value matches pattern
preflight json package.json --key version --match "^1\."
# Combined: validate and check required key
preflight json config.json --has-key database.hostUse dot notation to access nested keys:
{
"database": {
"host": "localhost",
"port": 5432
}
}preflight json config.json --has-key database.host
preflight json config.json --key database.port --exact 5432Non-string values are converted to strings for comparison:
# Numbers
preflight json config.json --key port --exact 8080
# Booleans
preflight json config.json --key enabled --exact true
# Null
preflight json config.json --key optional --exact nullCI - verify package.json:
preflight json package.json --key version --match "^[0-9]+\.[0-9]+\.[0-9]+$"Docker - validate config before startup:
COPY config.json /app/
RUN preflight json /app/config.json --has-key database.hostKubernetes - verify ConfigMap mounted correctly:
readinessProbe:
exec:
command: ["preflight", "json", "/etc/config/app.json", "--has-key", "api.endpoint"]preflight json is intentionally limited. For complex JSON queries, use jq:
- Not supported: Array indexing (
items[0]) - Not supported: JSONPath queries
- Not supported: Data extraction/transformation
Queries a Prometheus server and validates metric values against thresholds. Useful for pre-deployment checks like "don't deploy if error rate is already high" or verifying service health via Prometheus metrics.
preflight prometheus <url> --query '<promql>' [flags]| Flag | Description |
|---|---|
--query <promql> |
PromQL query (required) |
--min <value> |
Minimum value (fail if below) |
--max <value> |
Maximum value (fail if above) |
--exact <value> |
Exact value required |
--timeout <duration> |
Request timeout (default: 5s) |
--retry <n> |
Retry count on failure |
--retry-delay <dur> |
Delay between retries (default: 1s) |
--insecure |
Skip TLS certificate verification |
--header <key:value> |
Custom header (can be repeated) |
# Check service is up
preflight prometheus http://prometheus:9090 --query 'up{job="myapp"}' --exact 1
# Check error rate is below threshold
preflight prometheus http://prometheus:9090 --query 'error_rate' --max 0.05
# Check request count is above minimum
preflight prometheus http://prometheus:9090 --query 'request_count' --min 100
# With authentication
preflight prometheus http://prometheus:9090 --query 'up' --exact 1 \
--header "Authorization:Bearer token123"
# With retry for flaky metrics
preflight prometheus http://prometheus:9090 --query 'up{job="myapp"}' --exact 1 \
--retry 3 --retry-delay 5sPre-deployment validation:
# Don't deploy if error rate is already high
preflight prometheus http://prometheus:9090 --query 'error_rate{service="api"}' --max 0.01
kubectl apply -f deployment.yamlCI pipeline health gate:
# GitHub Actions
- name: Check production health before deploy
run: |
preflight prometheus $PROMETHEUS_URL --query 'up{job="api"}' --exact 1
preflight prometheus $PROMETHEUS_URL --query 'error_rate{job="api"}' --max 0.05Kubernetes readiness check:
readinessProbe:
exec:
command:
- preflight
- prometheus
- http://prometheus:9090
- --query
- up{job="myapp"}
- --exact
- "1"The PromQL query must return exactly one result. If your query returns multiple time series, make it more specific:
# This fails if multiple jobs match
preflight prometheus http://prom:9090 --query 'up' --exact 1
# Error: query returned 5 results, expected 1
# Use a more specific query
preflight prometheus http://prom:9090 --query 'up{job="myapp",instance="localhost:8080"}' --exact 1preflight prometheus replaces curl + jq patterns for querying Prometheus:
Before:
result=$(curl -s "http://prometheus:9090/api/v1/query?query=up{job=\"myapp\"}" | jq -r '.data.result[0].value[1]')
if [ "$result" != "1" ]; then
echo "Service is down"
exit 1
fiAfter:
preflight prometheus http://prometheus:9090 --query 'up{job="myapp"}' --exact 1Verifies git repository state. Useful for CI pipelines that need to ensure a clean working directory or verify branch/tag state before builds or releases.
preflight git [flags]| Flag | Description |
|---|---|
--clean |
Working directory must be clean (no changes at all) |
--no-uncommitted |
No staged or modified files allowed |
--no-untracked |
No untracked files allowed |
--branch <name> |
Must be on specified branch |
--tag-match <p> |
HEAD must have tag matching glob pattern |
At least one flag is required.
# Verify clean state after code generation
go generate ./...
preflight git --clean
# Check formatting didn't change anything
go fmt ./...
preflight git --no-uncommitted
# Allow untracked files, but no uncommitted changes
preflight git --no-uncommitted
# Must be on specific branch
preflight git --branch main
# HEAD must have a version tag
preflight git --tag-match "v*"
# Combined checks for release
preflight git --clean --branch release --tag-match "v*"CI vendor verification:
# Verify go mod tidy didn't change anything
go mod tidy
preflight git --clean
# Verify formatting is consistent
go fmt ./...
preflight git --no-uncommittedRelease verification:
# Ensure releasing from correct branch with proper tag
preflight git --branch main --tag-match "v*"GitHub Actions:
- name: Verify clean state
run: |
go generate ./...
preflight git --cleanpreflight git replaces common shell patterns for git state verification:
Before (vendor verification):
hugo mod vendor
if [ -n "$(git status --porcelain)" ]; then
echo 'ERROR: Vendor result differs'
exit 1
fiAfter:
hugo mod vendor
preflight git --cleanBefore (formatting check):
go fmt ./... && git diff --exit-codeAfter:
go fmt ./...
preflight git --no-uncommittedChecks TCP connectivity to a host:port. Useful for verifying that a database, cache, or other service is reachable before starting your application.
preflight tcp <host:port> [flags]| Flag | Description |
|---|---|
--timeout <dur> |
Connection timeout (default 5s) |
# Check database is reachable
preflight tcp localhost:5432
# Check Redis with custom timeout
preflight tcp redis:6379 --timeout 10s
# Check multiple services in container startup
preflight tcp postgres:5432 && preflight tcp redis:6379TCP checks are most useful for runtime validation - ensuring services are reachable before your application starts.
Container startup scripts:
CMD ["sh", "-c", "preflight tcp postgres:5432 && ./myapp"]Kubernetes readiness probes:
readinessProbe:
exec:
command: ["preflight", "tcp", "redis:6379"]CI with service containers (GitHub Actions):
services:
postgres:
image: postgres:15
steps:
- run: preflight tcp localhost:5432 --timeout 30sDocker Compose health checks:
healthcheck:
test: ["CMD", "preflight", "tcp", "db:5432"]preflight tcp replaces several widely-used service waiting tools:
| Tool | Stars | What preflight replaces |
|---|---|---|
| wait-for-it.sh | 9,700+ | wait-for-it.sh host:port |
| dockerize | 4,800+ | dockerize -wait tcp://host:port |
| netcat | - | nc -z host port |
| bash built-in | - | echo > /dev/tcp/$HOST/$PORT |
Before (wait-for-it.sh):
COPY wait-for-it.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/wait-for-it.sh
CMD ["wait-for-it.sh", "db:5432", "--", "./app"]After (preflight):
CMD ["sh", "-c", "preflight tcp db:5432 && ./app"]HTTP health checks for verifying services are up and responding correctly. Replaces curl --fail or wget --spider in containers, eliminating the need to install those tools.
preflight http <url> [flags]| Flag | Description |
|---|---|
--status <code> |
Expected HTTP status (default: 200) |
--timeout <duration> |
Request timeout (default: 5s) |
--retry <n> |
Retry count on failure |
--retry-delay <dur> |
Delay between retries (default: 1s) |
--method <method> |
HTTP method: GET or HEAD |
--header <key:value> |
Custom header (can be repeated) |
--insecure |
Skip TLS certificate verification |
# Basic health check
preflight http http://localhost:8080/health
# Custom expected status
preflight http http://localhost/api --status 204
# With timeout
preflight http http://slow-service:8080/ready --timeout 30s
# Retry on failure (3 retries = 4 total attempts)
preflight http http://localhost:8080/health --retry 3 --retry-delay 2s
# HEAD request (lighter weight)
preflight http http://localhost:8080/health --method HEAD
# Custom headers
preflight http http://localhost/api --header "Authorization:Bearer token123"
preflight http http://localhost/api --header "X-API-Key:secret" --header "Accept:application/json"
# Skip TLS verification (self-signed certs)
preflight http https://internal-service/health --insecureRedirects are not followed automatically. If the server returns a 3xx status, that status is checked against --status. This matches curl --fail behavior.
# This fails if server returns 302
preflight http http://localhost/old-path
# This passes if server returns 302
preflight http http://localhost/old-path --status 302HTTP checks are useful for runtime validation - ensuring services are healthy before your application starts.
Container startup (HEALTHCHECK):
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD preflight http http://localhost:8080/healthContainer startup script:
CMD ["sh", "-c", "preflight http http://api:8080/ready --retry 5 && ./myapp"]Kubernetes liveness/readiness probes:
livenessProbe:
exec:
command: ["preflight", "http", "http://localhost:8080/health"]
readinessProbe:
exec:
command: ["preflight", "http", "http://localhost:8080/ready", "--timeout", "10s"]Docker Compose health checks:
healthcheck:
test: ["CMD", "preflight", "http", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3preflight http provides several advantages over curl --fail:
- No extra dependencies - curl is 3-8MB and requires TLS libraries
- Consistent exit codes - curl's exit codes vary by error type
- Built-in retry - no need for shell loops
- Same syntax - consistent with other preflight commands
Verifies file checksums for supply chain security. Replaces sha256sum -c and similar patterns in Dockerfiles.
preflight hash <file> [flags]| Flag | Description |
|---|---|
--sha256 <hash> |
Expected SHA256 hash (64 hex characters) |
--sha512 <hash> |
Expected SHA512 hash (128 hex characters) |
--md5 <hash> |
Expected MD5 hash (32 hex characters) |
--checksum-file <path> |
Verify against checksum file (GNU or BSD format) |
# Verify SHA256 hash
preflight hash --sha256 67574ee...2cf downloaded.tar.gz
# Verify SHA512 hash
preflight hash --sha512 abc123... binary.tar.gz
# Verify MD5 (legacy, not recommended for security)
preflight hash --md5 deadbeef... legacy.zip
# Verify against checksum file (GNU format: hash filename)
preflight hash --checksum-file SHASUMS256.txt node-v20.tar.gz
# Verify against checksum file (BSD format: SHA256 (filename) = hash)
preflight hash --checksum-file checksums.txt myapp.tar.gzPreflight supports two common checksum file formats:
GNU format (used by sha256sum, Node.js SHASUMS):
67574ee2f0d8e... myfile.tar.gz
abc123def456... otherfile.tar.gz
BSD format (used by macOS shasum):
SHA256 (myfile.tar.gz) = 67574ee2f0d8e...
SHA512 (otherfile.tar.gz) = abc123def456...
The algorithm is auto-detected from BSD format, or inferred from hash length in GNU format.
Dockerfile - verify downloaded binary:
RUN curl -fsSL https://example.com/app.tar.gz -o /tmp/app.tar.gz
RUN preflight hash --sha256 $EXPECTED_HASH /tmp/app.tar.gz
RUN tar -xzf /tmp/app.tar.gz -C /usr/local/binMulti-stage build - verify with checksum file:
RUN curl -fsSL https://nodejs.org/dist/v20.0.0/SHASUMS256.txt -o /tmp/SHASUMS256.txt
RUN curl -fsSL https://nodejs.org/dist/v20.0.0/node-v20.0.0.tar.gz -o /tmp/node.tar.gz
RUN preflight hash --checksum-file /tmp/SHASUMS256.txt /tmp/node.tar.gzChecks the system's OS and architecture. Useful for multi-architecture container builds to verify the correct platform.
preflight sys [flags]| Flag | Description |
|---|---|
--os <os> |
Required OS (linux, darwin, windows) |
--arch <arch> |
Required architecture (amd64, arm64) |
At least one of --os or --arch is required.
# Check OS only
preflight sys --os linux
# Check architecture only
preflight sys --arch amd64
# Check both
preflight sys --os linux --arch arm64preflight sys replaces common architecture detection patterns critical for multi-platform builds:
| Pattern | Use Case |
|---|---|
uname -m | sed s/x86_64/amd64/ |
Normalize arch names |
dpkg --print-architecture |
Debian-based arch detection |
TARGETARCH (BuildKit ARG) |
Multi-platform Docker builds |
Before (binary download scripts):
# Common pattern in official images downloading binaries
arch="$(dpkg --print-architecture)"; case "$arch" in
amd64) GOSU_ARCH='amd64';;
arm64) GOSU_ARCH='arm64';;
*) echo "unsupported: $arch"; exit 1;;
esac
curl -L "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$GOSU_ARCH" -o /usr/local/bin/gosuAfter (preflight):
# Verify expected platform before downloading
RUN preflight sys --arch amd64
RUN curl -L "https://example.com/binary-amd64" -o /usr/local/bin/binaryRuntime verification:
# Before - brittle
arch=$(uname -m | sed s/aarch64/arm64/ | sed s/x86_64/amd64/)
if [ "$arch" != "amd64" ]; then
echo "Unsupported architecture: $arch"
exit 1
fi
# After - clear intent
preflight sys --arch amd64Multi-arch Dockerfile:
# Verify we're building for the expected platform
RUN preflight sys --os linux --arch arm64CI platform verification:
# GitHub Actions
- name: Verify platform
run: preflight sys --arch amd64Checks system resources meet minimum requirements. Critical for CI pipelines where runners have limited disk space, or containers with memory limits.
Container-aware: Memory checks read cgroup limits (v1 and v2), not just host
/proc/meminfo. Your 512MB container limit is detected correctly.
preflight resource [flags]| Flag | Description |
|---|---|
--min-disk <size> |
Minimum free disk space (e.g., 10G, 500M) |
--min-memory <size> |
Minimum available memory (e.g., 2G, 512M) |
--min-cpus <n> |
Minimum number of CPU cores |
--path <path> |
Path for disk space check (default: current dir) |
At least one of --min-disk, --min-memory, or --min-cpus is required.
Sizes support common units (case-insensitive):
| Unit | Example | Bytes |
|---|---|---|
| B (bytes) | 1024 |
1,024 |
| K/KB | 500K |
512,000 |
| M/MB | 500M |
524,288,000 |
| G/GB | 10G |
10,737,418,240 |
| T/TB | 1T |
1,099,511,627,776 |
Decimals are supported: 1.5G, 2.5TB
# Check disk space
preflight resource --min-disk 10G
# Check disk space at specific path
preflight resource --min-disk 10G --path /var/lib/docker
# Check available memory
preflight resource --min-memory 2G
# Check CPU cores
preflight resource --min-cpus 4
# Combined checks
preflight resource --min-disk 10G --min-memory 2G --min-cpus 2GitHub Actions - prevent disk space failures:
- name: Check disk space before Docker build
run: preflight resource --min-disk 10GDocker build - verify container resources:
# Verify container has enough resources before heavy operations
RUN preflight resource --min-memory 1G --min-cpus 2CI pipeline - verify runner capacity:
# Before starting parallel tests
preflight resource --min-cpus 4 --min-memory 4Gpreflight resource replaces common shell patterns for checking system resources:
Before (disk space check):
available=$(df -BG /var/lib/docker | tail -1 | awk '{print $4}' | tr -d 'G')
if [ "$available" -lt 10 ]; then
echo "Not enough disk space"
exit 1
fiAfter:
preflight resource --min-disk 10G --path /var/lib/dockerBefore (memory check):
mem_kb=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
mem_gb=$((mem_kb / 1024 / 1024))
if [ "$mem_gb" -lt 2 ]; then
echo "Not enough memory"
exit 1
fiAfter:
preflight resource --min-memory 2GChecks that a user exists on the system and optionally validates uid, gid, and home directory. Useful for verifying non-root container configurations.
preflight user <username> [flags]| Flag | Description |
|---|---|
--uid <id> |
Expected user ID |
--gid <id> |
Expected primary group ID |
--home <path> |
Expected home directory |
# Check user exists
preflight user appuser
# Verify specific uid/gid (common for non-root containers)
preflight user appuser --uid 1000 --gid 1000
# Verify home directory
preflight user appuser --home /app
# All constraints
preflight user appuser --uid 1000 --gid 1000 --home /appNon-root container validation:
RUN preflight user appuser --uid 1000 --gid 1000
USER appuserKubernetes security context verification:
# Verify container runs as expected user
preflight user nobody --uid 65534preflight user replaces common shell patterns found in virtually every official Docker image for privilege management:
Before (PostgreSQL, MySQL, MongoDB official images):
if [ "$1" = 'postgres' ] && [ "$(id -u)" = '0' ]; then
mkdir -p "$PGDATA"
chown -R postgres "$PGDATA"
chmod 700 "$PGDATA"
exec gosu postgres "$BASH_SOURCE" "$@"
fiAfter (preflight):
# Verify user configuration at build time
RUN preflight user postgres --uid 999 --gid 999Volume mount validation:
# Before - common Docker run pattern
docker run -u $(id -u):$(id -g) -v $(pwd):/workspace myimage
# After - verify inside container
preflight user appuser --uid 1000 --gid 1000Run multiple checks from a .preflight file. This is useful for defining all your checks in one place and running them together.
preflight run [flags]| Flag | Description |
|---|---|
--file <path> |
Path to preflight file (default: auto-discover) |
# .preflight
# Lines starting with # are comments
# Empty lines are ignored
# Commands can omit the "preflight" prefix
file /etc/localtime --not-empty
cmd myapp --min 2.0
env HOME
# Or include it explicitly
preflight tcp localhost:5432- Lines starting with
#are treated as comments - Empty lines are ignored
- Lines without
preflightprefix are automatically prepended withpreflight - Commands execute sequentially
When run without --file, preflight run searches for a .preflight file:
- Start from the current directory
- Search upward through parent directories
- Stop when finding
.preflight, reaching$HOME, or encountering a.gitdirectory
This allows you to run preflight run from any subdirectory in your project.
Make .preflight files executable and run them directly:
#!/usr/bin/env preflight
file /models/bert.onnx --not-empty
cmd myapp
env PATHchmod +x .preflight
./.preflight# Auto-discover .preflight file
preflight run
# Specify file explicitly
preflight run --file /path/to/.preflight
# In Dockerfile
COPY .preflight .
RUN preflight runPreflight can verify container images in CI pipelines, replacing ad-hoc shell scripts. These examples assume preflight is installed in the container image.
# Instead of:
TAG=$(docker run "myapp:latest" sh -c "echo \$APP_VERSION")
if [ "$TAG" != "$EXPECTED" ]; then exit 1; fi
# Use:
docker run myapp:latest preflight env APP_VERSION --exact "$EXPECTED"docker run myapp:latest sh -c '
preflight env APP_VERSION --exact "$EXPECTED_VERSION" &&
preflight cmd myapp &&
preflight file /models/model.onnx --not-empty
'- name: Verify container
run: |
docker run myapp:${{ github.sha }} preflight env APP_VERSION --exact "${{ github.sha }}"The examples above copy preflight into your final image, which adds ~6MB. Here are two patterns to keep your production images lean.
Keep preflight in the image but run checks externally via docker run:
# Run a single check
docker run myapp:latest preflight cmd myapp --min 2.0
# Or use a .preflight file for multiple checks
docker run myapp:latest preflight runThis works well in CI pipelines where you build, validate, then push only if checks pass.
Run preflight checks at container startup before your app starts:
#!/bin/sh
# entrypoint.sh
set -e
preflight tcp postgres:5432 --timeout 30s
preflight env DATABASE_URL
preflight file /app/config.yaml --not-empty
exec "$@"COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["myapp"]The container waits for dependencies and validates its environment before starting the main process.
See examples/runtime-checks-with-entrypoint for a complete example.
For distroless or scratch images without a shell, use preflight's exec mode. Add -- followed by your application command:
FROM gcr.io/distroless/static-debian12
COPY --from=ghcr.io/vertti/preflight:latest /preflight /preflight
COPY myapp /myapp
# Wait for postgres, then start app (no shell needed)
ENTRYPOINT ["/preflight", "tcp", "postgres:5432", "--retry", "10", "--"]
CMD ["/myapp"]When checks pass, preflight uses exec() to replace itself with your command—proper signal handling, PID 1, everything works correctly.
Multiple checks with .preflight file:
COPY .preflight /.preflight
ENTRYPOINT ["/preflight", "run", "--"]
CMD ["/myapp"]How it works:
- Preflight runs the check(s)
- If all checks pass, it execs into the command after
-- - If any check fails, it exits with code 1 (command never runs)
This works with any preflight command:
# Wait for database
preflight tcp postgres:5432 --retry 10 -- ./myapp
# Check environment then start
preflight env DATABASE_URL -- ./myapp
# HTTP health check then start
preflight http http://api:8080/ready --retry 5 -- ./myappNote: Exec mode is not supported on Windows.
Use a dedicated validation stage that runs checks during build but doesn't ship preflight:
# Stage 1: Build
FROM golang:1.21 AS build
WORKDIR /app
COPY . .
RUN go build -o myapp
# Stage 2: Validate (runs checks, not included in final image)
FROM alpine:3.19 AS validate
COPY --from=ghcr.io/vertti/preflight:latest /preflight /usr/local/bin/preflight
COPY --from=build /app/myapp /usr/local/bin/myapp
RUN preflight cmd myapp --min 2.0
RUN preflight file /usr/local/bin/myapp --executable
# Stage 3: Final (continues from build, no preflight)
FROM build AS final
CMD ["myapp"]The validate stage runs during build—if any check fails, the build fails. But the final image only contains your app.
See examples/multistage-dockerfile for a complete example.
[OK] cmd:myapp
path: /usr/local/bin/myapp
version: 2.1.0
[FAIL] cmd:myapp
not found in PATH
[FAIL] cmd:myapp
version 1.5.0 < minimum 2.0.0
[FAIL] env:MODEL_PATH
not set
[FAIL] env:MODEL_PATH
value does not match pattern "^/models/"
[FAIL] file:/var/log/app
not writable
[FAIL] file:/missing/path
not found
[OK] tcp:localhost:5432
connected to localhost:5432
[FAIL] tcp:localhost:9999
connection failed: dial tcp [::1]:9999: connect: connection refused
[OK] http: http://localhost:8080/health
status 200
[FAIL] http: http://localhost:8080/health
status 503, expected 200
[FAIL] http: http://localhost:9999/health
request failed: dial tcp [::1]:9999: connect: connection refused
[OK] user:appuser
uid: 1000
gid: 1000
home: /app
[FAIL] user:nonexistent
user not found: user: unknown user nonexistent
| Code | Meaning |
|---|---|
0 |
All checks passed |
1 |
One or more checks failed |
Preflight outputs colored status indicators:
[OK]in green[FAIL]in red
- Terminals - colors are detected automatically via TTY detection
- CI systems - colors are enabled automatically for:
- GitHub Actions
- GitLab CI
- CircleCI
- Travis CI
- Jenkins (with AnsiColor plugin)
- Azure Pipelines
- Buildkite
- TeamCity
- Drone CI
- Bitbucket Pipelines
- AWS CodeBuild
- Woodpecker CI
Docker builds don't allocate a TTY, so colors are disabled by default. To enable colors in your Dockerfile, set PREFLIGHT_COLOR=1:
FROM alpine
ENV PREFLIGHT_COLOR=1
RUN preflight cmd myapp
RUN preflight env MODEL_PATH
RUN preflight file /app/config.yaml| Variable | Description |
|---|---|
PREFLIGHT_COLOR=1 |
Force colors on (for Docker builds) |
PREFLIGHT_COLOR=0 |
Force colors off |
CLICOLOR_FORCE=1 |
Force colors on (standard env var) |
NO_COLOR=1 |
Disable colors (https://no-color.org/) |
# Disable colors
NO_COLOR=1 preflight cmd myapp
# Enable colors in scripts/Docker
PREFLIGHT_COLOR=1 preflight cmd myapp