diff --git a/.github/workflows/build-images.yaml b/.github/workflows/build-images.yaml index 34f955f..d75f4c1 100644 --- a/.github/workflows/build-images.yaml +++ b/.github/workflows/build-images.yaml @@ -11,6 +11,9 @@ on: branches: - main +env: + IMAGE_NAME: ghcr.io/biomejs/biome + jobs: generate-jobs: name: Generate jobs @@ -31,15 +34,19 @@ jobs: echo "BIOME_VERSIONS=$BIOME_VERSIONS" >> $GITHUB_OUTPUT build-images-arm64: - name: ${{ matrix.versions.patch }} (arm64) + name: ${{ matrix.versions.patch }} (${{ matrix.variant }}, arm64) needs: generate-jobs - concurrency: build-images-${{ matrix.versions.patch }}-arm64 + concurrency: build-images-${{ matrix.versions.patch }}-${{ matrix.variant }}-arm64 permissions: packages: write runs-on: ubuntu-24.04-arm strategy: matrix: versions: ${{ fromJson(needs.generate-jobs.outputs.biome-versions) }} + variant: + - distroless + - alpine + - debian steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -51,6 +58,34 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Prepare variant metadata + id: variant + run: | + case "${{ matrix.variant }}" in + distroless) + TARGET=distroless + INTERNAL_TAG="${IMAGE_NAME}:${{ matrix.versions.patch }}-distroless-arm64" + TAGS="${INTERNAL_TAG}"$'\n'"${IMAGE_NAME}:${{ matrix.versions.patch }}-arm64" + ;; + alpine) + TARGET=alpine + INTERNAL_TAG="${IMAGE_NAME}:${{ matrix.versions.patch }}-alpine-arm64" + TAGS="${INTERNAL_TAG}" + ;; + debian) + TARGET=debian + INTERNAL_TAG="${IMAGE_NAME}:${{ matrix.versions.patch }}-debian-arm64" + TAGS="${INTERNAL_TAG}" + ;; + esac + + { + echo "target=${TARGET}" + echo "internal_tag=${INTERNAL_TAG}" + echo 'tags<> "$GITHUB_OUTPUT" - name: Build and push uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 with: @@ -58,28 +93,36 @@ jobs: load: true provenance: false platforms: linux/arm64 - tags: ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-arm64 + tags: ${{ steps.variant.outputs.tags }} + target: ${{ steps.variant.outputs.target }} file: Dockerfile - cache-from: type=gha,scope=${{ matrix.versions.patch}}-arm64 - cache-to: type=gha,mode=max,scope=${{ matrix.versions.patch}}-arm64 + cache-from: type=gha,scope=${{ matrix.versions.patch}}-${{ matrix.variant }}-arm64 + cache-to: type=gha,mode=max,scope=${{ matrix.versions.patch}}-${{ matrix.variant }}-arm64 build-args: | ARCH=arm64 BIOME_VERSION=${{ matrix.versions.patch }} BIOME_TAG_START=${{ matrix.versions.major == '2' && '@biomejs/biome@' || 'cli/v' }} - name: Verify image run: | - docker run --pull=never --rm ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-arm64 --version + docker run --pull=never --rm ${{ steps.variant.outputs.internal_tag }} --version + docker run --pull=never --rm --entrypoint /usr/bin/git ${{ steps.variant.outputs.internal_tag }} --version + docker run --pull=never --rm -v "$PWD":/code --entrypoint /usr/bin/git ${{ steps.variant.outputs.internal_tag }} status --short + docker run --pull=never --rm -v "$PWD":/code ${{ steps.variant.outputs.internal_tag }} check biome.json build-images-amd64: - name: ${{ matrix.versions.patch }} (amd64) + name: ${{ matrix.versions.patch }} (${{ matrix.variant }}, amd64) needs: generate-jobs - concurrency: build-images-${{ matrix.versions.patch }}-amd64 + concurrency: build-images-${{ matrix.versions.patch }}-${{ matrix.variant }}-amd64 permissions: packages: write runs-on: ubuntu-24.04 strategy: matrix: versions: ${{ fromJson(needs.generate-jobs.outputs.biome-versions) }} + variant: + - distroless + - alpine + - debian steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -91,6 +134,34 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Prepare variant metadata + id: variant + run: | + case "${{ matrix.variant }}" in + distroless) + TARGET=distroless + INTERNAL_TAG="${IMAGE_NAME}:${{ matrix.versions.patch }}-distroless-amd64" + TAGS="${INTERNAL_TAG}"$'\n'"${IMAGE_NAME}:${{ matrix.versions.patch }}-amd64" + ;; + alpine) + TARGET=alpine + INTERNAL_TAG="${IMAGE_NAME}:${{ matrix.versions.patch }}-alpine-amd64" + TAGS="${INTERNAL_TAG}" + ;; + debian) + TARGET=debian + INTERNAL_TAG="${IMAGE_NAME}:${{ matrix.versions.patch }}-debian-amd64" + TAGS="${INTERNAL_TAG}" + ;; + esac + + { + echo "target=${TARGET}" + echo "internal_tag=${INTERNAL_TAG}" + echo 'tags<> "$GITHUB_OUTPUT" - name: Build and push uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7 with: @@ -98,17 +169,21 @@ jobs: load: true provenance: false platforms: linux/amd64 - tags: ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-amd64 + tags: ${{ steps.variant.outputs.tags }} + target: ${{ steps.variant.outputs.target }} file: Dockerfile - cache-from: type=gha,scope=${{ matrix.versions.patch}}-amd64 - cache-to: type=gha,mode=max,scope=${{ matrix.versions.patch}}-amd64 + cache-from: type=gha,scope=${{ matrix.versions.patch}}-${{ matrix.variant }}-amd64 + cache-to: type=gha,mode=max,scope=${{ matrix.versions.patch}}-${{ matrix.variant }}-amd64 build-args: | ARCH=x64 BIOME_VERSION=${{ matrix.versions.patch }} BIOME_TAG_START=${{ matrix.versions.major == '2' && '@biomejs/biome@' || 'cli/v' }} - name: Verify image run: | - docker run --pull=never --rm ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-amd64 --version + docker run --pull=never --rm ${{ steps.variant.outputs.internal_tag }} --version + docker run --pull=never --rm --entrypoint /usr/bin/git ${{ steps.variant.outputs.internal_tag }} --version + docker run --pull=never --rm -v "$PWD":/code --entrypoint /usr/bin/git ${{ steps.variant.outputs.internal_tag }} status --short + docker run --pull=never --rm -v "$PWD":/code ${{ steps.variant.outputs.internal_tag }} check biome.json stitch-images: name: Stitch images (${{ matrix.versions.patch }}) @@ -131,36 +206,48 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create major version (${{ matrix.versions.major }}) manifest - if: ${{ matrix.versions.createMajor }} + - name: Create image manifests + env: + PATCH: ${{ matrix.versions.patch }} + MAJOR: ${{ matrix.versions.major }} + MINOR: ${{ matrix.versions.minor }} + CREATE_MAJOR: ${{ matrix.versions.createMajor }} + CREATE_MINOR: ${{ matrix.versions.createMinor }} + IS_LATEST: ${{ matrix.versions.patch == fromJson(needs.generate-jobs.outputs.biome-versions)[0].patch }} run: | - docker manifest create ghcr.io/biomejs/biome:${{ matrix.versions.major }} \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-arm64 \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-amd64 - docker manifest push ghcr.io/biomejs/biome:${{ matrix.versions.major }} + create_manifest() { + local public_tag="$1" + local variant="$2" + local destination="${IMAGE_NAME}:${public_tag}" - - name: Create minor version (${{ matrix.versions.minor }}) manifest - if: ${{ matrix.versions.createMinor }} - run: | - docker manifest create ghcr.io/biomejs/biome:${{ matrix.versions.minor }} \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-arm64 \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-amd64 - docker manifest push ghcr.io/biomejs/biome:${{ matrix.versions.minor }} + if [ "${variant}" != "distroless" ]; then + destination="${destination}-${variant}" + fi - - name: Create patch version (${{ matrix.versions.patch }}) manifest - run: | - docker manifest create ghcr.io/biomejs/biome:${{ matrix.versions.patch }} \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-arm64 \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-amd64 - docker manifest push ghcr.io/biomejs/biome:${{ matrix.versions.patch }} + docker manifest create "${destination}" \ + "${IMAGE_NAME}:${PATCH}-${variant}-arm64" \ + "${IMAGE_NAME}:${PATCH}-${variant}-amd64" + docker manifest push "${destination}" + } - - name: Create latest (${{ matrix.versions.patch }}) manifest - if: - ${{ matrix.versions.patch == - fromJson(needs.generate-jobs.outputs.biome-versions)[0].patch }} - run: | - docker manifest create ghcr.io/biomejs/biome:latest \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-arm64 \ - ghcr.io/biomejs/biome:${{ matrix.versions.patch }}-amd64 - docker manifest push ghcr.io/biomejs/biome:latest + if [ "${CREATE_MAJOR}" = "true" ]; then + create_manifest "${MAJOR}" distroless + create_manifest "${MAJOR}" alpine + create_manifest "${MAJOR}" debian + fi + + if [ "${CREATE_MINOR}" = "true" ]; then + create_manifest "${MINOR}" distroless + create_manifest "${MINOR}" alpine + create_manifest "${MINOR}" debian + fi + + create_manifest "${PATCH}" distroless + create_manifest "${PATCH}" alpine + create_manifest "${PATCH}" debian + + if [ "${IS_LATEST}" = "true" ]; then + create_manifest latest distroless + create_manifest latest alpine + create_manifest latest debian + fi diff --git a/Dockerfile b/Dockerfile index cf65a4f..72f38c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,130 @@ ARG ALPINE_VERSION=3.23@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 - -FROM alpine:${ALPINE_VERSION} AS biome - +ARG DEBIAN_VERSION=bookworm-slim +ARG DISTROLESS_BASE=gcr.io/distroless/base-debian12:latest ARG ARCH="x64" ARG BIOME_VERSION=2.2.6 ARG BIOME_TAG_START="@biomejs/biome@" +ARG BIOME_RELEASE_BASE_URL="https://github.com/biomejs/biome/releases/download" + +# ============================================================================ +# Biome glibc binary and runtime libraries for Debian and distroless +# ============================================================================ + +FROM debian:${DEBIAN_VERSION} AS biome-glibc + +ARG ARCH +ARG BIOME_VERSION +ARG BIOME_TAG_START +ARG BIOME_RELEASE_BASE_URL -ENV BIOME_VERSION=${BIOME_VERSION} -ENV BIOME_TAG_START=${BIOME_TAG_START} +ADD ${BIOME_RELEASE_BASE_URL}/${BIOME_TAG_START}${BIOME_VERSION}/biome-linux-${ARCH} /usr/local/bin/biome -ADD https://github.com/biomejs/biome/releases/download/${BIOME_TAG_START}${BIOME_VERSION}/biome-linux-${ARCH}-musl /usr/local/bin/biome +RUN chmod +x /usr/local/bin/biome \ + && mkdir -p /runtime-libs \ + && ldd /usr/local/bin/biome \ + | awk '/=>/ { print $3 } /^\// { print $1 }' \ + | sort -u \ + | xargs -r -I '{}' cp --parents -L '{}' /runtime-libs + +# ============================================================================ +# Biome musl binary for Alpine +# ============================================================================ + +FROM alpine:${ALPINE_VERSION} AS biome-musl + +ARG ARCH +ARG BIOME_VERSION +ARG BIOME_TAG_START +ARG BIOME_RELEASE_BASE_URL + +ADD ${BIOME_RELEASE_BASE_URL}/${BIOME_TAG_START}${BIOME_VERSION}/biome-linux-${ARCH}-musl /usr/local/bin/biome -# Install git and flag to repo safe RUN chmod +x /usr/local/bin/biome -RUN apk add --no-cache git -RUN git config --global --add safe.directory /code + +# ============================================================================ +# Shared git safe.directory configuration +# ============================================================================ + +FROM debian:${DEBIAN_VERSION} AS git-config + +ARG SAFE_DIRECTORY="/code" + +RUN printf '[safe]\n\tdirectory = %s\n' "${SAFE_DIRECTORY}" > /etc/gitconfig + +# ============================================================================ +# Git runtime bundle for the distroless image +# ============================================================================ + +FROM debian:${DEBIAN_VERSION} AS git-distroless + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=git-config /etc/gitconfig /etc/gitconfig + +RUN mkdir -p /distroless-git \ + && cp --parents /usr/bin/git /distroless-git \ + && cp -a --parents /usr/lib/git-core /distroless-git \ + && cp -a --parents /usr/share/git-core /distroless-git \ + && cp -a --parents /etc/gitconfig /distroless-git \ + && cp -a --parents /etc/ssl/certs /distroless-git \ + && { \ + ldd /usr/bin/git; \ + find /usr/lib/git-core -type f -executable -exec sh -c 'for file do ldd "$file" 2>/dev/null || true; done' sh '{}' +; \ + } \ + | awk '/=>/ { print $3 } /^\// { print $1 }' \ + | sort -u \ + | xargs -r -I '{}' cp --parents -L '{}' /distroless-git + +# ============================================================================ +# Alpine variant +# ============================================================================ + +FROM alpine:${ALPINE_VERSION} AS alpine + +RUN apk add --no-cache ca-certificates git + +COPY --from=biome-musl /usr/local/bin/biome /usr/local/bin/biome +COPY --from=git-config /etc/gitconfig /etc/gitconfig + +ENV GIT_PAGER=cat + +WORKDIR /code + +ENTRYPOINT ["/usr/local/bin/biome"] + +# ============================================================================ +# Debian variant +# ============================================================================ + +FROM debian:${DEBIAN_VERSION} AS debian + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=biome-glibc /usr/local/bin/biome /usr/local/bin/biome +COPY --from=git-config /etc/gitconfig /etc/gitconfig + +ENV GIT_PAGER=cat + +WORKDIR /code + +ENTRYPOINT ["/usr/local/bin/biome"] + +# ============================================================================ +# Distroless default variant +# ============================================================================ + +FROM ${DISTROLESS_BASE} AS distroless + +COPY --from=biome-glibc /usr/local/bin/biome /usr/local/bin/biome +COPY --from=biome-glibc /runtime-libs/ / +COPY --from=git-distroless /distroless-git/ / + +ENV GIT_PAGER=cat WORKDIR /code -ENTRYPOINT [ "/usr/local/bin/biome" ] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/biome"] diff --git a/README.md b/README.md index 0172a49..413136d 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,20 @@ Supported architectures: `amd64`, `arm64` Docker images are available for all versions of Biome starting from `1.7.0`. -Images are tagged with the following format: +The default image variant is distroless. + +Images are tagged with the following formats: ```sh ghcr.io/biomejs/biome:{major} ghcr.io/biomejs/biome:{major}.{minor} ghcr.io/biomejs/biome:{major}.{minor}.{patch} +ghcr.io/biomejs/biome:{major}-alpine +ghcr.io/biomejs/biome:{major}.{minor}-alpine +ghcr.io/biomejs/biome:{major}.{minor}.{patch}-alpine +ghcr.io/biomejs/biome:{major}-debian +ghcr.io/biomejs/biome:{major}.{minor}-debian +ghcr.io/biomejs/biome:{major}.{minor}.{patch}-debian ``` ### Examples @@ -22,6 +30,18 @@ ghcr.io/biomejs/biome:{major}.{minor}.{patch} - `ghcr.io/biomejs/biome:1.9` - `ghcr.io/biomejs/biome:1.9.4` - `ghcr.io/biomejs/biome:latest` +- `ghcr.io/biomejs/biome:latest-alpine` +- `ghcr.io/biomejs/biome:latest-debian` +- `ghcr.io/biomejs/biome:1.9.4-alpine` +- `ghcr.io/biomejs/biome:1.9.4-debian` + +## Variants + +- `distroless` (default): minimal runtime image with no shell or package manager +- `alpine`: Alpine-based image using Biome's musl Linux binaries +- `debian`: Debian-based image using Biome's glibc Linux binaries + +All variants include the `git` CLI and ship `/etc/gitconfig` with `/code` marked as a safe directory. ## Usage @@ -43,4 +63,4 @@ docker run -v $(pwd):/code ghcr.io/biomejs/biome:1.9.4 format --write ## License -This project is licensed under the [MIT License](LICENSE.md). \ No newline at end of file +This project is licensed under the [MIT License](LICENSE.md).