From 37712c6e0f266f48fcc049251bad9eb7e93a11a5 Mon Sep 17 00:00:00 2001 From: Peter Rounce Date: Wed, 17 Jun 2026 12:47:54 +0000 Subject: [PATCH] CI: cache Docker layers and parallelize image builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `docker` job rebuilt both images from scratch every run on ephemeral runners with no persistent layer cache. The webproxy xcaddy build (~11 min) and the card image's go mod download / npm ci / apt layers were redone on every PR. - Split into parallel `docker-card` and `docker-webproxy` jobs. - Use docker/build-push-action@v6 with GHA layer cache (type=gha) under distinct scopes so the two images don't evict each other. - docker-webproxy drops `needs:` — it's pure Caddy, independent of the Go and frontend gates, so it starts at t=0. - Pin webproxy base images to caddy:2.11.4(-builder) for stable cache keys. - Bump version 0.22.1 -> 0.22.2. Effect: unchanged webproxy is a near-instant cache hit; a typical PR's Docker phase drops from ~15 min to roughly the card compile alone. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 53 ++++++++++++++++++++++++++++---------- CLAUDE.md | 2 +- docker/card/build/build.go | 2 +- docker/webproxy/Dockerfile | 4 +-- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6afc11b..3f63d2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,10 @@ jobs: - run: npm ci - run: npm run build - docker: + # Card image: depends on the Go + frontend gates. Cached layers (go mod + # download, npm ci, apt) are reused across runs via the GHA cache backend, + # so a typical PR only recompiles changed Go/frontend source. + docker-card: runs-on: ubuntu-latest needs: [build, frontend] steps: @@ -61,13 +64,7 @@ jobs: id: version run: echo "version=$(grep -oP 'Version string = "\K[0-9]+\.[0-9]+\.[0-9]+' docker/card/build/build.go)" >> "$GITHUB_OUTPUT" - - name: Build card image - env: - APP_VERSION: ${{ steps.version.outputs.version }} - run: docker build --build-arg APP_VERSION="$APP_VERSION" -t boltcard/card:latest docker/card - - - name: Build webproxy image - run: docker build -t boltcard/webproxy:latest docker/webproxy + - uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -76,17 +73,45 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Push card image - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: docker push boltcard/card:latest + - name: Build and push card image + uses: docker/build-push-action@v6 + with: + context: docker/card + build-args: APP_VERSION=${{ steps.version.outputs.version }} + tags: boltcard/card:latest + push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + cache-from: type=gha,scope=card + cache-to: type=gha,mode=max,scope=card + + # Webproxy image: pure Caddy (xcaddy compiles the ratelimit plugin), so it + # depends on neither Go nor frontend — no `needs:`, runs in parallel from t=0. + # The expensive xcaddy build is a cache hit whenever docker/webproxy is unchanged. + docker-webproxy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 - - name: Push webproxy image + - name: Login to Docker Hub if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: docker push boltcard/webproxy:latest + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push webproxy image + uses: docker/build-push-action@v6 + with: + context: docker/webproxy + tags: boltcard/webproxy:latest + push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + cache-from: type=gha,scope=webproxy + cache-to: type=gha,mode=max,scope=webproxy release: runs-on: ubuntu-latest - needs: [docker] + needs: [docker-card, docker-webproxy] if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: write diff --git a/CLAUDE.md b/CLAUDE.md index 9d82644..dee9bd5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,7 +93,7 @@ Entry point: `main.go` → opens SQLite DB → runs CLI or starts HTTP server on - `phoenix/` — HTTP client for Phoenix Server API (invoices, payments, balance, channels). Uses basic auth from phoenix config (password cached at startup with `sync.Once`) - `crypto/` — AES-CMAC authentication and AES decryption for Bolt Card NFC protocol - `util/` — Error handling helpers (`CheckAndLog`), random hex generation, QR code encoding -- `build/` — Version string (currently "0.22.1"), date/time injected at build +- `build/` — Version string (currently "0.22.2"), date/time injected at build - `web-content/` — Static assets under `public/`, SPA build output under `admin/spa/` ### Route Groups (`web/app.go`) diff --git a/docker/card/build/build.go b/docker/card/build/build.go index 5fac32c..625d8b1 100644 --- a/docker/card/build/build.go +++ b/docker/card/build/build.go @@ -1,5 +1,5 @@ package build -var Version string = "0.22.1" +var Version string = "0.22.2" var Date string var Time string diff --git a/docker/webproxy/Dockerfile b/docker/webproxy/Dockerfile index 1fd8bd9..d51ceac 100644 --- a/docker/webproxy/Dockerfile +++ b/docker/webproxy/Dockerfile @@ -1,8 +1,8 @@ -FROM caddy:builder AS builder +FROM caddy:2.11.4-builder AS builder RUN xcaddy build \ --with github.com/mholt/caddy-ratelimit -FROM caddy:latest +FROM caddy:2.11.4 COPY --from=builder /usr/bin/caddy /usr/bin/caddy