name: Publish Docker Image on: push: tags: - 'v*' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false env: BUILDKIT_PROGRESS: plain IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/pi-devbox jobs: smoke: runs-on: ubuntu-latest container: image: catthehacker/ubuntu:act-latest steps: - uses: actions/checkout@v4 - run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf - run: | rm -rf /opt/hostedtoolcache /opt/microsoft /opt/az /opt/ghc \ /usr/local/.ghcup /usr/share/dotnet /usr/share/swift \ /usr/local/lib/android /usr/local/share/powershell \ /usr/local/share/chromium /usr/local/share/boost \ /usr/lib/jvm 2>/dev/null || true docker system prune -af --volumes || true docker builder prune -af || true - uses: docker/setup-buildx-action@v4 with: {driver-opts: network=host} # Derive PI_VERSION from the tag (e.g. v0.78.0 -> 0.78.0; v0.78.0b -> 0.78.0). # Since the refactor to FROM opencode-devbox:latest-with-pi, this repo no # longer installs pi itself — pi comes from the base image. We still resolve # the tag version and feed it to the smoke test as EXPECTED_PI_VERSION: the # smoke asserts the inherited base actually carries this pi version, which # turns the version coupling into an enforced publish-ordering guard (it # fails loudly if latest-with-pi is stale relative to this tag). - name: Resolve PI_VERSION from tag id: resolve run: | TAG="${{ github.ref_name }}" PI_VERSION="${TAG#v}" PI_VERSION=$(echo "$PI_VERSION" | sed 's/[a-z]*$//') echo "pi_version=${PI_VERSION}" >> "$GITHUB_OUTPUT" echo "Resolved PI_VERSION=${PI_VERSION} from tag ${TAG}" - name: Build (amd64, load to local daemon) uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64 push: false load: true tags: pi-devbox:smoke - name: Smoke test env: EXPECTED_PI_VERSION: ${{ steps.resolve.outputs.pi_version }} run: bash scripts/smoke-test.sh pi-devbox:smoke publish: needs: smoke runs-on: ubuntu-latest container: image: catthehacker/ubuntu:act-latest steps: - uses: actions/checkout@v4 - run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf - run: | rm -rf /opt/hostedtoolcache /opt/microsoft /opt/az /opt/ghc \ /usr/local/.ghcup /usr/share/dotnet /usr/share/swift \ /usr/local/lib/android /usr/local/share/powershell \ /usr/local/share/chromium /usr/local/share/boost \ /usr/lib/jvm 2>/dev/null || true docker system prune -af --volumes || true docker builder prune -af || true - uses: docker/setup-qemu-action@v3 with: {platforms: arm64} - uses: docker/setup-buildx-action@v4 with: {driver-opts: network=host} - uses: docker/login-action@v3 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Compute tags id: tags run: | VERSION="${{ github.ref_name }}" { echo "tags<> "$GITHUB_OUTPUT" # See the smoke job for why the tag version is resolved (now used only for # the base-freshness smoke guard; pi is no longer installed in this repo). - name: Resolve PI_VERSION from tag id: resolve run: | TAG="${{ github.ref_name }}" PI_VERSION="${TAG#v}" PI_VERSION=$(echo "$PI_VERSION" | sed 's/[a-z]*$//') echo "pi_version=${PI_VERSION}" >> "$GITHUB_OUTPUT" echo "Resolved PI_VERSION=${PI_VERSION} from tag ${TAG}" - name: Build and push (amd64 + arm64) — with retry shell: bash env: TAGS: ${{ steps.tags.outputs.tags }} run: | set -euo pipefail # Convert newline-delimited TAGS env var (build-push-action's native # format from the `Compute tags` step) into a bash array of -t flags. TAG_FLAGS=() while IFS= read -r t; do [[ -n "$t" ]] && TAG_FLAGS+=( -t "$t" ); done <<< "${TAGS}" # 3-attempt retry around `docker buildx build --push` for transient # registry-1.docker.io blips (rate limits, CDN flap, brief 5xx). # The build itself is now trivial (FROM opencode-devbox:latest-with-pi # + an empty layer) so it is fast even without registry cache. # Registry cache stays disabled (buildkit mode=max cache-export hits a # reproducible HTTP 400 from Hub CDN since ~2026-05-23; image push is # unaffected). See opencode-devbox CHANGELOG v1.15.12. for attempt in 1 2 3; do echo "==> Build+push attempt ${attempt}/3" if docker buildx build \ --platform linux/amd64,linux/arm64 \ --push \ "${TAG_FLAGS[@]}" \ .; then echo "==> Attempt ${attempt} succeeded" exit 0 fi if [[ "${attempt}" -lt 3 ]]; then backoff=$(( attempt * 15 )) echo "==> Attempt ${attempt} failed, sleeping ${backoff}s before retry" sleep "${backoff}" fi done echo "==> All 3 build+push attempts failed" exit 1 update-description: needs: publish runs-on: ubuntu-latest container: image: catthehacker/ubuntu:act-latest steps: - uses: actions/checkout@v4 - name: Update Docker Hub description run: | PAYLOAD=$(jq -n --rawfile desc DOCKER_HUB.md '{"full_description": $desc}') TOKEN=$(curl -s -X POST "https://hub.docker.com/v2/auth/token" \ -H "Content-Type: application/json" \ -d "{\"username\":\"${{ vars.DOCKERHUB_USERNAME }}\",\"password\":\"${{ secrets.DOCKERHUB_TOKEN }}\"}" \ | jq -r '.token') curl -s -X PATCH "https://hub.docker.com/v2/repositories/${{ vars.DOCKERHUB_USERNAME }}/pi-devbox/" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d "${PAYLOAD}" | jq -r '.full_description | if . then "✅ description updated (\(. | length) chars)" else "❌ update failed" end'