23894bc19f
Validate / docs-check (push) Successful in 22s
Validate / validate-base (push) Successful in 18m10s
Validate / validate-omos (push) Failing after 25m54s
Publish Docker Image / smoke-base (push) Successful in 11m50s
Publish Docker Image / build-base (linux/amd64) (push) Failing after 38s
Publish Docker Image / build-base (linux/arm64) (push) Failing after 21s
Publish Docker Image / merge-base (push) Has been skipped
Publish Docker Image / smoke-omos (push) Failing after 19m18s
Publish Docker Image / build-omos (linux/amd64) (push) Has been skipped
Publish Docker Image / build-omos (linux/arm64) (push) Has been skipped
Publish Docker Image / merge-omos (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
v1.14.31 publish and validate both hit 'No space left on device' on single-arch amd64 smoke/validate builds. The image has crossed ~3 GB and the runner's ~40 GB overlay starts ~70% full, so 'load: true' peak disk (tarball + unpacked image + buildx cache) no longer fits. Add a 'Reclaim runner disk' step to validate-base, validate-omos, smoke-base, smoke-omos. Strips catthehacker-resident toolchains we never use (hosted-tool-cache, dotnet, android, powershell, swift, ghc, jvm, microsoft, chromium, boost), then runs 'docker system prune -af --volumes' + 'docker builder prune -af' against the runner's dockerd before setup-buildx-action. Expected reclaim is 6-12 GB depending on what's resident. Deliberately NOT in the per-arch matrix build jobs — push-by-digest doesn't need it and pruning in parallel jobs risks one job nuking another's in-flight buildx cache. Also add workflow-level concurrency on docker-publish.yml so concurrent tag pushes serialize cleanly.
389 lines
13 KiB
YAML
389 lines
13 KiB
YAML
name: Publish Docker Image
|
||
|
||
on:
|
||
push:
|
||
tags:
|
||
- 'v*'
|
||
|
||
# Serialize concurrent runs of the same workflow on the same ref so the
|
||
# matrix build jobs can't race `docker system prune` in the smoke gates
|
||
# (pruning from one job can nuke another job's in-flight buildx cache).
|
||
# cancel-in-progress: false — tag pushes are release events, we never
|
||
# want to silently drop one.
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: false
|
||
|
||
# Runner disk pressure notes:
|
||
# Gitea Actions runners use `catthehacker/ubuntu:act-latest` on a shared host
|
||
# with limited overlay space (~40 GB, often 70%+ used at start). Building both
|
||
# architectures of both variants on a single runner exhausted disk around the
|
||
# nodejs dpkg unpack / git-lfs layer export. To fix this:
|
||
# * smoke test (amd64 only, load into daemon) runs on its own runner
|
||
# * each push target (variant × arch) runs on its own runner, pushes by
|
||
# digest (no local image store), uploads digest as an artifact
|
||
# * a merge job composes the multi-arch manifest with `imagetools create`
|
||
# Per-runner disk pressure is now one-quarter of the old single-job peak.
|
||
|
||
jobs:
|
||
# ── Smoke test (amd64 only, gates the push jobs) ────────────────────
|
||
smoke-base:
|
||
runs-on: ubuntu-latest
|
||
container:
|
||
image: catthehacker/ubuntu:act-latest
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Force IPv4 for Docker Hub
|
||
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||
|
||
# See docker-publish.yml preamble. `load: true` peak disk = tarball
|
||
# + unpacked image + buildx cache; the image now crosses the 40 GB
|
||
# runner overlay's starting headroom. Strip catthehacker-resident
|
||
# toolchains and any stale docker state up front.
|
||
- name: Reclaim runner disk
|
||
run: |
|
||
set -x
|
||
df -h / || true
|
||
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
|
||
apt-get clean || true
|
||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* || true
|
||
docker system df || true
|
||
docker system prune -af --volumes || true
|
||
docker builder prune -af || true
|
||
df -h / || true
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v4
|
||
with:
|
||
driver-opts: network=host
|
||
|
||
- name: Build and load amd64 image for smoke test
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: .
|
||
platforms: linux/amd64
|
||
push: false
|
||
load: true
|
||
tags: opencode-devbox:smoke-base
|
||
|
||
- name: Smoke test (amd64)
|
||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-base --variant base
|
||
|
||
smoke-omos:
|
||
runs-on: ubuntu-latest
|
||
container:
|
||
image: catthehacker/ubuntu:act-latest
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Force IPv4 for Docker Hub
|
||
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||
|
||
- name: Reclaim runner disk
|
||
run: |
|
||
set -x
|
||
df -h / || true
|
||
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
|
||
apt-get clean || true
|
||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* || true
|
||
docker system df || true
|
||
docker system prune -af --volumes || true
|
||
docker builder prune -af || true
|
||
df -h / || true
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v4
|
||
with:
|
||
driver-opts: network=host
|
||
|
||
- name: Build and load amd64 image for smoke test
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: .
|
||
platforms: linux/amd64
|
||
push: false
|
||
load: true
|
||
build-args: |
|
||
INSTALL_OMOS=true
|
||
tags: opencode-devbox:smoke-omos
|
||
|
||
- name: Smoke test (amd64)
|
||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos --variant omos
|
||
|
||
# ── Per-arch push (by digest, no local image) ───────────────────────
|
||
build-base:
|
||
runs-on: ubuntu-latest
|
||
needs: smoke-base
|
||
container:
|
||
image: catthehacker/ubuntu:act-latest
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
platform:
|
||
- linux/amd64
|
||
- linux/arm64
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Force IPv4 for Docker Hub
|
||
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||
|
||
- name: Derive platform slug
|
||
id: platform
|
||
run: |
|
||
PLATFORM_PAIR="${{ matrix.platform }}"
|
||
echo "pair=${PLATFORM_PAIR//\//-}" >> $GITHUB_OUTPUT
|
||
|
||
- name: Set up QEMU
|
||
if: matrix.platform != 'linux/amd64'
|
||
uses: docker/setup-qemu-action@v4
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v4
|
||
with:
|
||
driver-opts: network=host
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v4
|
||
with:
|
||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Build and push by digest
|
||
id: build
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: .
|
||
platforms: ${{ matrix.platform }}
|
||
outputs: type=image,name=${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox,push-by-digest=true,name-canonical=true,push=true
|
||
|
||
- 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-base-${{ steps.platform.outputs.pair }}
|
||
path: /tmp/digests/*
|
||
if-no-files-found: error
|
||
retention-days: 1
|
||
|
||
build-omos:
|
||
runs-on: ubuntu-latest
|
||
needs: smoke-omos
|
||
container:
|
||
image: catthehacker/ubuntu:act-latest
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
platform:
|
||
- linux/amd64
|
||
- linux/arm64
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Force IPv4 for Docker Hub
|
||
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||
|
||
- name: Derive platform slug
|
||
id: platform
|
||
run: |
|
||
PLATFORM_PAIR="${{ matrix.platform }}"
|
||
echo "pair=${PLATFORM_PAIR//\//-}" >> $GITHUB_OUTPUT
|
||
|
||
- name: Set up QEMU
|
||
if: matrix.platform != 'linux/amd64'
|
||
uses: docker/setup-qemu-action@v4
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v4
|
||
with:
|
||
driver-opts: network=host
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v4
|
||
with:
|
||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Build and push by digest
|
||
id: build
|
||
uses: docker/build-push-action@v7
|
||
with:
|
||
context: .
|
||
platforms: ${{ matrix.platform }}
|
||
build-args: |
|
||
INSTALL_OMOS=true
|
||
outputs: type=image,name=${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox,push-by-digest=true,name-canonical=true,push=true
|
||
|
||
- 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-omos-${{ steps.platform.outputs.pair }}
|
||
path: /tmp/digests/*
|
||
if-no-files-found: error
|
||
retention-days: 1
|
||
|
||
# ── Merge per-arch digests into multi-arch tags ─────────────────────
|
||
merge-base:
|
||
runs-on: ubuntu-latest
|
||
needs: build-base
|
||
container:
|
||
image: catthehacker/ubuntu:act-latest
|
||
steps:
|
||
- name: Force IPv4 for Docker Hub
|
||
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||
|
||
- name: Download digests
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: /tmp/digests
|
||
pattern: digests-base-*
|
||
merge-multiple: true
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v4
|
||
with:
|
||
driver-opts: network=host
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v4
|
||
with:
|
||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Extract version from tag
|
||
id: version
|
||
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||
|
||
- name: Create manifest list and push
|
||
working-directory: /tmp/digests
|
||
run: |
|
||
docker buildx imagetools create \
|
||
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }} \
|
||
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest \
|
||
$(printf '${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox@sha256:%s ' *)
|
||
|
||
- name: Inspect image
|
||
run: |
|
||
docker buildx imagetools inspect \
|
||
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}
|
||
|
||
merge-omos:
|
||
runs-on: ubuntu-latest
|
||
needs: build-omos
|
||
container:
|
||
image: catthehacker/ubuntu:act-latest
|
||
steps:
|
||
- name: Force IPv4 for Docker Hub
|
||
run: echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
|
||
|
||
- name: Download digests
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: /tmp/digests
|
||
pattern: digests-omos-*
|
||
merge-multiple: true
|
||
|
||
- name: Set up Docker Buildx
|
||
uses: docker/setup-buildx-action@v4
|
||
with:
|
||
driver-opts: network=host
|
||
|
||
- name: Login to Docker Hub
|
||
uses: docker/login-action@v4
|
||
with:
|
||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
|
||
- name: Extract version from tag
|
||
id: version
|
||
run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||
|
||
- name: Create manifest list and push
|
||
working-directory: /tmp/digests
|
||
run: |
|
||
docker buildx imagetools create \
|
||
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos \
|
||
-t ${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:latest-omos \
|
||
$(printf '${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox@sha256:%s ' *)
|
||
|
||
- name: Inspect image
|
||
run: |
|
||
docker buildx imagetools inspect \
|
||
${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox:${{ steps.version.outputs.version }}-omos
|
||
|
||
update-description:
|
||
runs-on: ubuntu-latest
|
||
needs: [merge-base, merge-omos]
|
||
container:
|
||
image: catthehacker/ubuntu:act-latest
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Update Docker Hub description
|
||
run: |
|
||
TOKEN=$(curl -s -X POST https://hub.docker.com/v2/auth/token \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"identifier":"${{ vars.DOCKERHUB_USERNAME }}","secret":"${{ secrets.DOCKERHUB_TOKEN }}"}' \
|
||
| jq -r .access_token)
|
||
if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then
|
||
echo "::error::Failed to authenticate with Docker Hub API"
|
||
exit 1
|
||
fi
|
||
HTTP_CODE=$(jq -n \
|
||
--rawfile full DOCKER_HUB.md \
|
||
--arg short "Portable AI dev environment for opencode. Debian-based with git, Node.js, AWS CLI, and SSH support." \
|
||
'{"full_description": $full, "description": $short}' | \
|
||
curl -s -o /tmp/hub-response.txt -w "%{http_code}" -X PATCH \
|
||
"https://hub.docker.com/v2/repositories/${{ vars.DOCKERHUB_USERNAME }}/opencode-devbox/" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d @-)
|
||
echo "Docker Hub API returned: $HTTP_CODE"
|
||
if [ "$HTTP_CODE" != "200" ]; then
|
||
echo "Response body:"
|
||
cat /tmp/hub-response.txt
|
||
echo "::error::Docker Hub description update failed with HTTP $HTTP_CODE"
|
||
exit 1
|
||
fi
|