From 5516f07f5c210ac63653131c047a716214a9278d Mon Sep 17 00:00:00 2001 From: Greg Pstrucha <875316+Gricha@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:35:36 +0000 Subject: [PATCH 1/2] Fix multiarch Docker build and optimize CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Tectonic arm64: use aarch64-unknown-linux-musl (not gnu) - Fix Tectonic download URL to use proper versioned path - Add BuildKit syntax directive and apt cache mounts - Combine apt install layers for build deps - Add --no-install-recommends to reduce image size - Use native arm64 runner (ubuntu-24.04-arm) instead of QEMU - Build archs in parallel on native runners, then merge manifests - Use registry-based caching for faster rebuilds The arm64 build was failing because Tectonic only provides musl-based binaries for aarch64, not glibc ones. Build time should improve significantly: - Native arm64 runner vs QEMU emulation (~10x faster) - Registry caching for layer reuse across builds - BuildKit apt cache mounts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/release.yml | 97 +++++++++++++++++++++++++++++------ perry/Dockerfile | 95 +++++++++++++++++----------------- 2 files changed, 130 insertions(+), 62 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 666a327d..85111c20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,18 +16,86 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: - docker: - runs-on: ubuntu-latest + build: + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + + runs-on: ${{ matrix.runner }} permissions: contents: read packages: write + outputs: + digest-amd64: ${{ steps.build.outputs.digest }} + digest-arm64: ${{ steps.build.outputs.digest }} + steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: ./perry + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }} + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }},mode=max + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: build + permissions: + contents: read + packages: write + + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -51,19 +119,18 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} - - name: Build and push Docker image - uses: docker/build-push-action@v6 - with: - context: ./perry - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} npm: - needs: docker + needs: merge runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: diff --git a/perry/Dockerfile b/perry/Dockerfile index 811ac44a..f1f817c2 100644 --- a/perry/Dockerfile +++ b/perry/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 # Workspace Ubuntu Noble Dockerfile # Provides a reusable Docker-in-Docker environment with SSH access. # Migrated from Alpine to Ubuntu Noble (24.04 LTS) @@ -8,11 +9,13 @@ FROM ubuntu:noble ENV DEBIAN_FRONTEND=noninteractive # Install prerequisites for adding Docker repository -RUN apt-get update && apt-get install -y \ - ca-certificates \ - curl \ - gnupg \ - lsb-release +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + gnupg \ + lsb-release # Add Docker's official GPG key and repository RUN install -m 0755 -d /etc/apt/keyrings \ @@ -22,46 +25,44 @@ RUN install -m 0755 -d /etc/apt/keyrings \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker Engine, CLI, and development tools -RUN apt-get update && apt-get install -y \ - docker-ce \ - docker-ce-cli \ - containerd.io \ - docker-buildx-plugin \ - docker-compose-plugin \ - bash \ - sudo \ - openssh-server \ - git \ - curl \ - wget \ - tzdata \ - python3 \ - python3-pip \ - jq \ - rsync \ - unzip \ - zip \ - nano \ - vim \ - iproute2 \ - iptables \ - kmod \ - openssl \ - procps \ - ripgrep \ - fd-find \ - fzf \ - zsh \ - luarocks \ - imagemagick - -# Install build dependencies for Neovim -RUN apt-get update && apt-get install -y \ - ninja-build \ - gettext \ - cmake \ - unzip \ - build-essential +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + docker-ce \ + docker-ce-cli \ + containerd.io \ + docker-buildx-plugin \ + docker-compose-plugin \ + bash \ + sudo \ + openssh-server \ + git \ + curl \ + wget \ + tzdata \ + python3 \ + python3-pip \ + jq \ + rsync \ + unzip \ + zip \ + nano \ + vim \ + iproute2 \ + iptables \ + kmod \ + openssl \ + procps \ + ripgrep \ + fd-find \ + fzf \ + zsh \ + luarocks \ + imagemagick \ + ninja-build \ + gettext \ + cmake \ + build-essential # Build Neovim v0.11.4 from source RUN git clone --depth 1 --branch v0.11.4 https://github.com/neovim/neovim.git /tmp/neovim \ @@ -145,9 +146,9 @@ RUN ARCH=$(dpkg --print-architecture) \ && lazygit --version RUN ARCH=$(dpkg --print-architecture) \ - && if [ "$ARCH" = "amd64" ]; then TECTONIC_ARCH="x86_64-unknown-linux-gnu"; elif [ "$ARCH" = "arm64" ]; then TECTONIC_ARCH="aarch64-unknown-linux-gnu"; else TECTONIC_ARCH="$ARCH"; fi \ + && if [ "$ARCH" = "amd64" ]; then TECTONIC_ARCH="x86_64-unknown-linux-gnu"; elif [ "$ARCH" = "arm64" ]; then TECTONIC_ARCH="aarch64-unknown-linux-musl"; else TECTONIC_ARCH="$ARCH"; fi \ && TECTONIC_VERSION=$(curl -s "https://api.github.com/repos/tectonic-typesetting/tectonic/releases/latest" | grep -Po '"tag_name": "tectonic@\K[^"]*') \ - && curl -fsSL "https://github.com/tectonic-typesetting/tectonic/releases/latest/download/tectonic-${TECTONIC_VERSION}-${TECTONIC_ARCH}.tar.gz" -o /tmp/tectonic.tar.gz \ + && curl -fsSL "https://github.com/tectonic-typesetting/tectonic/releases/download/tectonic%40${TECTONIC_VERSION}/tectonic-${TECTONIC_VERSION}-${TECTONIC_ARCH}.tar.gz" -o /tmp/tectonic.tar.gz \ && tar -C /usr/local/bin -xzf /tmp/tectonic.tar.gz \ && rm /tmp/tectonic.tar.gz \ && tectonic --version From 58e115c4a86011d26fb68ba261ac0c92b0daeaa8 Mon Sep 17 00:00:00 2001 From: Greg Pstrucha <875316+Gricha@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:45:01 +0000 Subject: [PATCH 2/2] Use musl tectonic binaries for both architectures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switched from glibc to musl binaries for amd64 as well since --no-install-recommends doesn't include libgraphite2 and other libraries that the glibc binary needs. musl binaries are statically linked so they work without additional runtime dependencies. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- perry/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perry/Dockerfile b/perry/Dockerfile index f1f817c2..ee32249b 100644 --- a/perry/Dockerfile +++ b/perry/Dockerfile @@ -146,7 +146,7 @@ RUN ARCH=$(dpkg --print-architecture) \ && lazygit --version RUN ARCH=$(dpkg --print-architecture) \ - && if [ "$ARCH" = "amd64" ]; then TECTONIC_ARCH="x86_64-unknown-linux-gnu"; elif [ "$ARCH" = "arm64" ]; then TECTONIC_ARCH="aarch64-unknown-linux-musl"; else TECTONIC_ARCH="$ARCH"; fi \ + && if [ "$ARCH" = "amd64" ]; then TECTONIC_ARCH="x86_64-unknown-linux-musl"; elif [ "$ARCH" = "arm64" ]; then TECTONIC_ARCH="aarch64-unknown-linux-musl"; else TECTONIC_ARCH="$ARCH"; fi \ && TECTONIC_VERSION=$(curl -s "https://api.github.com/repos/tectonic-typesetting/tectonic/releases/latest" | grep -Po '"tag_name": "tectonic@\K[^"]*') \ && curl -fsSL "https://github.com/tectonic-typesetting/tectonic/releases/download/tectonic%40${TECTONIC_VERSION}/tectonic-${TECTONIC_VERSION}-${TECTONIC_ARCH}.tar.gz" -o /tmp/tectonic.tar.gz \ && tar -C /usr/local/bin -xzf /tmp/tectonic.tar.gz \