Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions .github/workflows/build-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:

env:
PREK_BASE_IMG: ghcr.io/${{ github.repository_owner }}/prek
PREK_ALPINE_IMG: ghcr.io/${{ github.repository_owner }}/prek-alpine

permissions:
contents: read
Expand Down Expand Up @@ -42,7 +43,7 @@ jobs:
fi

docker-build:
name: Build Docker image ghcr.io/j178/prek for ${{ matrix.platform }}
name: Build Docker image (${{ matrix.image }}, ${{ matrix.platform }})
needs: plan
runs-on: ubuntu-latest
environment:
Expand All @@ -53,11 +54,20 @@ jobs:
platform:
- linux/amd64
- linux/arm64
image:
- minimal
- alpine
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

# QEMU is required to run the target-arch `RUN` steps in the Alpine stage
# when cross-building linux/arm64 on an amd64 runner. The minimal (scratch)
# image only copies the cross-compiled binary, so it needs no emulation.
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
if: ${{ matrix.image == 'alpine' }}

- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
Expand Down Expand Up @@ -88,7 +98,7 @@ jobs:
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
with:
images: ${{ env.PREK_BASE_IMG }}
images: ${{ matrix.image == 'alpine' && env.PREK_ALPINE_IMG || env.PREK_BASE_IMG }}
# Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
tags: |
type=raw,value=dry-run,enable=${{ needs.plan.outputs.publish != 'true' }}
Expand All @@ -106,10 +116,11 @@ jobs:
with:
context: .
platforms: ${{ matrix.platform }}
cache-from: type=gha,scope=prek-${{ env.PLATFORM_TUPLE }}
cache-to: type=gha,mode=min,scope=prek-${{ env.PLATFORM_TUPLE }}
target: ${{ matrix.image }}
cache-from: type=gha,scope=prek-${{ matrix.image }}-${{ env.PLATFORM_TUPLE }}
cache-to: type=gha,mode=min,scope=prek-${{ matrix.image }}-${{ env.PLATFORM_TUPLE }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.PREK_BASE_IMG }},push-by-digest=true,name-canonical=true,push=${{ needs.plan.outputs.publish == 'true' }}
outputs: type=image,name=${{ matrix.image == 'alpine' && env.PREK_ALPINE_IMG || env.PREK_BASE_IMG }},push-by-digest=true,name-canonical=true,push=${{ needs.plan.outputs.publish == 'true' }}

- name: Export digests
env:
Expand All @@ -121,13 +132,13 @@ jobs:
- name: Upload digests
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: digests-${{ env.PLATFORM_TUPLE }}
name: digests-${{ matrix.image }}-${{ env.PLATFORM_TUPLE }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

docker-publish:
name: Publish Docker image (ghcr.io/j178/prek)
name: Publish Docker image (${{ matrix.image }})
runs-on: ubuntu-latest
environment:
name: release
Expand All @@ -140,12 +151,18 @@ jobs:
id-token: write
attestations: write
if: ${{ needs.plan.outputs.publish == 'true' }}
strategy:
fail-fast: false
matrix:
image:
- minimal
- alpine
steps:
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: /tmp/digests
pattern: digests-*
pattern: digests-${{ matrix.image }}-*
merge-multiple: true

- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
Expand All @@ -156,7 +173,7 @@ jobs:
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
with:
images: ${{ env.PREK_BASE_IMG }}
images: ${{ matrix.image == 'alpine' && env.PREK_ALPINE_IMG || env.PREK_BASE_IMG }}
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
tags: |
type=raw,value=${{ fromJson(inputs.plan).announcement_tag }}
Expand All @@ -171,27 +188,26 @@ jobs:
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
- name: Create manifest list and push
working-directory: /tmp/digests
# The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array
# The printf will expand the base image with the `<PREK_BASE_IMG>@sha256:<sha256> ...` for each sha256 in the directory
# The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <PREK_BASE_IMG>@sha256:<sha256_1> <PREK_BASE_IMG>@sha256:<sha256_2> ...`
env:
IMAGE_NAME: ${{ matrix.image == 'alpine' && env.PREK_ALPINE_IMG || env.PREK_BASE_IMG }}
run: |
# shellcheck disable=SC2046
readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done

docker buildx imagetools create \
"${annotations[@]}" \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf "${PREK_BASE_IMG}@sha256:%s " *)
$(printf "${IMAGE_NAME}@sha256:%s " *)

- name: Export manifest digest
id: manifest-digest
env:
IMAGE: ${{ env.PREK_BASE_IMG }}
IMAGE_NAME: ${{ matrix.image == 'alpine' && env.PREK_ALPINE_IMG || env.PREK_BASE_IMG }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
digest="$(
docker buildx imagetools inspect \
"${IMAGE}:${VERSION}" \
"${IMAGE_NAME}:${VERSION}" \
--format '{{json .Manifest}}' \
| jq -r '.digest'
)"
Expand All @@ -200,5 +216,5 @@ jobs:
- name: Generate artifact attestation
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-name: ${{ env.PREK_BASE_IMG }}
subject-name: ${{ matrix.image == 'alpine' && env.PREK_ALPINE_IMG || env.PREK_BASE_IMG }}
subject-digest: ${{ steps.manifest-digest.outputs.digest }}
15 changes: 14 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,20 @@ RUN cp target/$(cat rust_target.txt)/dist/prek /prek
# TODO: Optimize binary size, with a version that also works when cross compiling
# RUN strip --strip-all /prek

FROM scratch
FROM alpine:3.24 AS alpine
RUN apk add --no-cache \
Comment thread
pat-s marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Containerized hooks are also quite common. Would it make sense to add podman here as well?

ca-certificates \
curl \
git \
nodejs \
npm \
python3 \
py3-pip
COPY --from=build /prek /usr/local/bin/prek
WORKDIR /io
ENTRYPOINT ["/usr/local/bin/prek"]

FROM scratch AS minimal
COPY --from=build /prek /
WORKDIR /io
ENTRYPOINT ["/prek"]
29 changes: 22 additions & 7 deletions docs/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ This page documents common ways to integrate prek into CI and container workflow

## Docker

prek is published as a distroless container image at:
prek publishes two container images:

- `ghcr.io/j178/prek`
| Image | Base | Description |
| -- | -- | -- |
| `ghcr.io/j178/prek` | `scratch` | Minimal distroless image containing only the prek binary |
| `ghcr.io/j178/prek-alpine` | `alpine:3.24` | Alpine image with common hook dependencies pre-installed |

The image is based on `scratch` (no shell, no package manager). It contains the prek binary at `/prek`.
### Minimal (scratch)

The default image is based on `scratch` (no shell, no package manager). It contains the prek binary at `/prek`.

A common pattern is to copy the binary into your own image:

Expand All @@ -23,16 +28,26 @@ If you prefer, you can also run the distroless image directly:
docker run --rm ghcr.io/j178/prek:v0.4.8 --version
```

### Alpine

The Alpine image includes `curl`, `git`, `nodejs`, `npm`, `python3`, and `py3-pip`, covering the most common hook runtimes.

```bash
docker run --rm ghcr.io/j178/prek-alpine:v0.4.8 --version
```

### Verifying Images

Docker images are signed with
Both images are signed with
Comment thread
pat-s marked this conversation as resolved.
[GitHub Attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations)
to verify they were built by official prek workflows. Verify using the
to verify they were built by official prek workflows.
Each image is verified separately; the example below verifies the minimal image, and the Alpine image is verified the same way by substituting `prek-alpine` for `prek`.
Verify using the
[GitHub CLI](https://cli.github.com/):

```console
$ gh attestation verify --owner j178 oci://ghcr.io/j178/prek:latest
Loaded digest sha256:xxxx... for oci://ghcr.io/j178/prek:latest
$ gh attestation verify --owner j178 oci://ghcr.io/j178/prek:v0.4.8
Loaded digest sha256:xxxx... for oci://ghcr.io/j178/prek:v0.4.8
Loaded 1 attestation from GitHub API
✓ Verification succeeded!

Expand Down
Loading