https://github.com/azu/dockerfile-pin
A CLI tool that adds @sha256:<digest> to FROM lines in Dockerfiles and image fields in docker-compose.yml to prevent supply chain attacks.
https://github.com/azu/dockerfile-pin
docker docker-compose security tools
Last synced: 3 months ago
JSON representation
A CLI tool that adds @sha256:<digest> to FROM lines in Dockerfiles and image fields in docker-compose.yml to prevent supply chain attacks.
- Host: GitHub
- URL: https://github.com/azu/dockerfile-pin
- Owner: azu
- License: mit
- Created: 2026-03-27T04:21:07.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-03-27T07:37:21.000Z (3 months ago)
- Last Synced: 2026-03-27T16:55:26.174Z (3 months ago)
- Topics: docker, docker-compose, security, tools
- Language: Go
- Homepage:
- Size: 102 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# dockerfile-pin
A CLI tool that adds `@sha256:` to `FROM` lines in Dockerfiles and `image` fields in docker-compose.yml to prevent supply chain attacks.
## Install
### Homebrew / curl
```bash
# Download binary (macOS Apple Silicon)
curl -sL "https://github.com/azu/dockerfile-pin/releases/latest/download/dockerfile-pin_darwin_arm64.tar.gz" | tar xz
sudo mv dockerfile-pin /usr/local/bin/
# Download binary (Linux amd64)
curl -sL "https://github.com/azu/dockerfile-pin/releases/latest/download/dockerfile-pin_linux_amd64.tar.gz" | tar xz
sudo mv dockerfile-pin /usr/local/bin/
```
### aqua
```bash
aqua init
aqua generate -i azu/dockerfile-pin
aqua i
aqua exec -- dockerfile-pin --help
```
### Go
```bash
go install github.com/azu/dockerfile-pin@latest
```
See [GitHub Releases](https://github.com/azu/dockerfile-pin/releases) for all platforms.
## Usage
### Run
Add digests to Dockerfile `FROM` lines or docker-compose.yml `image` fields.
By default, shows changes without modifying files (dry-run).
```bash
# Preview changes (dry-run, default)
dockerfile-pin run
# Preview a specific file
dockerfile-pin run -f path/to/Dockerfile
# Preview multiple files using glob
dockerfile-pin run --glob '**/Dockerfile*'
# Multiple patterns with brace expansion
dockerfile-pin run --glob '**/{Dockerfile,Dockerfile.*,docker-compose.yml,compose.yaml}'
# Preview docker-compose.yml
dockerfile-pin run -f docker-compose.yml
# Actually write changes to files
dockerfile-pin run --write
# Update existing digests
dockerfile-pin run --write --update
```
**Before:**
```dockerfile
FROM node:20.11.1
FROM python:3.12-slim AS builder
FROM scratch
```
**After:**
```dockerfile
FROM node:20.11.1@sha256:e06aae17c40c7a6b5296ca6f942a02e6737ae61bbbf3e2158624bb0f887991b5
FROM python:3.12-slim@sha256:3d5ed973e45820f5ba5e46bd065bd88b3a504ff0724d85980dcd05eab361fcf4 AS builder
FROM scratch
```
### Check
Validate that digests are present and exist in the registry.
```bash
# Check a single Dockerfile
dockerfile-pin check -f Dockerfile
# Check multiple files
dockerfile-pin check --glob '**/Dockerfile*'
# Multiple patterns with brace expansion
dockerfile-pin check --glob '**/{Dockerfile,Dockerfile.*,dockerfile_*.tmpl,docker-compose.yml,compose.yaml}'
# Syntax check only (no registry queries)
dockerfile-pin check --syntax-only
# JSON output for CI
dockerfile-pin check --format json
# Ignore specific images
dockerfile-pin check --ignore-images scratch,mylocal
```
**Output:**
```
FAIL Dockerfile:1 FROM node:20.11.1 missing digest
OK Dockerfile:3 FROM python:3.12@sha256:abc123...
SKIP Dockerfile:5 FROM scratch scratch image
```
Exit code is `1` when any check fails (configurable with `--exit-code`).
## Supported Patterns
### Dockerfiles
| Pattern | Supported |
|---------|-----------|
| `FROM image:tag` | Yes |
| `FROM image:tag AS name` | Yes |
| `FROM --platform=linux/amd64 image:tag` | Yes |
| `FROM image:tag@sha256:...` (already pinned) | Skipped (use `--update` to refresh) |
| `FROM scratch` | Skipped |
| `FROM ` (multi-stage ref) | Skipped |
| `ARG VERSION=1.0` + `FROM image:${VERSION}` | Yes (expanded from default) |
| `ARG BASE` + `FROM ${BASE}` (no default) | Skipped with warning |
| `FROM ghcr.io/org/image:tag` | Yes |
| `FROM registry:5000/image:tag` | Yes |
### docker-compose.yml
| Pattern | Supported |
|---------|-----------|
| `image: node:20` | Yes |
| `image: node:20@sha256:...` | Skipped (use `--update`) |
| Service with `build:` directive | Skipped |
| Service without `image:` key | Skipped |
## CI Integration
### Check (PR validation)
Validate that all images are pinned on every pull request.
With aqua (if your project already uses aqua, add `azu/dockerfile-pin` to your `aqua.yaml`):
```yaml
# .github/workflows/dockerfile-check.yml
name: Dockerfile Digest Check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aquaproj/aqua-installer@v3
with:
aqua_version: v2.45.0
- run: dockerfile-pin check
```
Without aqua:
```yaml
# .github/workflows/dockerfile-check.yml
name: Dockerfile Digest Check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dockerfile-pin
run: |
curl -sL "https://github.com/azu/dockerfile-pin/releases/latest/download/dockerfile-pin_linux_amd64.tar.gz" | tar xz -C /usr/local/bin
- run: dockerfile-pin check
```
`dockerfile-pin check` exits with code 1 if any image is missing a digest.
When `-f` and `--glob` are omitted, it auto-detects target files using `git ls-files` filtered by the default glob pattern:
`**/{Dockerfile,Dockerfile.*,docker-compose*.yml,docker-compose*.yaml,compose.yml,compose.yaml}`
Outside a git repository, it falls back to the same glob pattern with common directories (`node_modules`, `vendor`) excluded.
### Pin (migration)
Run locally to add digests to all Dockerfiles and compose files:
```bash
# Preview changes
dockerfile-pin run
# Apply changes
dockerfile-pin run --write
```
### Private registries
For private registries (GCR, GHCR, ECR), configure Docker credentials before running:
```yaml
# GHCR
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# GCR
- uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- uses: google-github-actions/setup-gcloud@v2
- run: gcloud auth configure-docker
```
`dockerfile-pin` uses `~/.docker/config.json` for authentication, so any `docker login` or credential helper works.
## How It Works
- Uses [go-containerregistry](https://github.com/google/go-containerregistry) (crane) for registry API calls
- Uses BuildKit's Dockerfile parser for accurate FROM line parsing
- `run` resolves digests via HEAD requests (does not count against Docker Hub pull rate limits)
- `check` verifies digest existence via HEAD requests
- Authenticates using `~/.docker/config.json` (supports Docker Hub, GHCR, GCR, ECR, etc.)
## Digest Updates
`--update` re-resolves each tag against the registry and replaces the existing digest with the current digest of that tag. The tag itself is not changed.
```bash
# Re-resolve all pinned digests from the registry
dockerfile-pin run --write --update
```
For automated ongoing digest updates, use [Renovate](https://docs.renovatebot.com/docker/) which understands the `image:tag@sha256:digest` format.
## License
MIT