Build and Publish OpenClaw Image #68
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Publish OpenClaw Image | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'internal/openclaw/OPENCLAW_VERSION' | |
| - 'internal/openclaw/FOUNDRY_VERSION' | |
| - 'docker/openclaw/**' | |
| schedule: | |
| # Daily at 06:00 UTC — detects new upstream OpenClaw releases and rebuilds. | |
| - cron: '0 6 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'OpenClaw version to build (e.g. v2026.2.3). Defaults to pinned version.' | |
| required: false | |
| type: string | |
| foundry_version: | |
| description: 'Foundry version tag (e.g. v1.5.1). Defaults to pinned version.' | |
| required: false | |
| type: string | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: obolnetwork/openclaw | |
| BASE_IMAGE_NAME: obolnetwork/openclaw-base | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Job 1: Resolve versions and decide whether to build. | |
| # On cron: checks if upstream has a newer release than pinned. | |
| # On push/dispatch: always builds with the pinned (or input-overridden) version. | |
| # --------------------------------------------------------------------------- | |
| check-upstream: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.check.outputs.should_build }} | |
| openclaw_version: ${{ steps.check.outputs.openclaw_version }} | |
| foundry_version: ${{ steps.check.outputs.foundry_version }} | |
| steps: | |
| - name: Checkout obol-stack | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Resolve versions and check for updates | |
| id: check | |
| run: | | |
| # --- OpenClaw version --- | |
| if [ -n "${{ github.event.inputs.version }}" ]; then | |
| OC_VERSION="${{ github.event.inputs.version }}" | |
| else | |
| OC_VERSION=$(grep -v '^#' internal/openclaw/OPENCLAW_VERSION | tr -d '[:space:]') | |
| fi | |
| # --- Foundry version --- | |
| if [ -n "${{ github.event.inputs.foundry_version }}" ]; then | |
| FOUNDRY_VERSION="${{ github.event.inputs.foundry_version }}" | |
| else | |
| FOUNDRY_VERSION=$(grep -v '^#' internal/openclaw/FOUNDRY_VERSION | tr -d '[:space:]') | |
| fi | |
| echo "foundry_version=$FOUNDRY_VERSION" >> "$GITHUB_OUTPUT" | |
| # --- Cron: check for new upstream release --- | |
| if [ "${{ github.event_name }}" = "schedule" ]; then | |
| LATEST=$(curl -sS https://api.github.com/repos/openclaw/openclaw/releases/latest \ | |
| | jq -r '.tag_name') | |
| if [ -z "$LATEST" ] || [ "$LATEST" = "null" ]; then | |
| echo "::warning::Failed to fetch latest upstream release" | |
| echo "should_build=false" >> "$GITHUB_OUTPUT" | |
| echo "openclaw_version=$OC_VERSION" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$LATEST" = "$OC_VERSION" ]; then | |
| echo "No new upstream release. Pinned: $OC_VERSION" | |
| echo "should_build=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "New upstream release detected: $LATEST (pinned: $OC_VERSION)" | |
| OC_VERSION="$LATEST" | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| else | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "openclaw_version=$OC_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Resolved: OpenClaw=$OC_VERSION Foundry=$FOUNDRY_VERSION" | |
| # --------------------------------------------------------------------------- | |
| # Job 2: Build upstream OpenClaw from source and push as base image. | |
| # --------------------------------------------------------------------------- | |
| build-base: | |
| needs: check-upstream | |
| if: needs.check-upstream.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout obol-stack | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Checkout upstream OpenClaw | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| repository: openclaw/openclaw | |
| ref: ${{ needs.check-upstream.outputs.openclaw_version }} | |
| path: openclaw-src | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract base image metadata | |
| id: meta | |
| uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }} | |
| tags: | | |
| type=raw,value=${{ needs.check-upstream.outputs.openclaw_version }} | |
| type=sha,prefix= | |
| type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} | |
| labels: | | |
| org.opencontainers.image.title=OpenClaw Base | |
| org.opencontainers.image.description=Upstream OpenClaw build (without Foundry tools) | |
| org.opencontainers.image.vendor=Obol Network | |
| org.opencontainers.image.source=https://github.com/openclaw/openclaw | |
| org.opencontainers.image.version=${{ needs.check-upstream.outputs.openclaw_version }} | |
| - name: Build and push base image | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| with: | |
| context: openclaw-src | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=openclaw-base | |
| cache-to: type=gha,scope=openclaw-base,mode=max | |
| # --------------------------------------------------------------------------- | |
| # Job 3: Layer Foundry tools onto the base image and publish final image. | |
| # --------------------------------------------------------------------------- | |
| build-final: | |
| needs: [check-upstream, build-base] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout obol-stack | |
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract final image metadata | |
| id: meta | |
| uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=semver,pattern={{version}},value=${{ needs.check-upstream.outputs.openclaw_version }} | |
| type=semver,pattern={{major}}.{{minor}},value=${{ needs.check-upstream.outputs.openclaw_version }} | |
| type=sha,prefix= | |
| type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} | |
| labels: | | |
| org.opencontainers.image.title=OpenClaw | |
| org.opencontainers.image.description=AI agent gateway for Obol Stack (with Foundry tools) | |
| org.opencontainers.image.vendor=Obol Network | |
| org.opencontainers.image.source=https://github.com/ObolNetwork/obol-stack | |
| org.opencontainers.image.version=${{ needs.check-upstream.outputs.openclaw_version }} | |
| - name: Build and push final image | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| with: | |
| context: . | |
| file: docker/openclaw/Dockerfile | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| FOUNDRY_TAG=${{ needs.check-upstream.outputs.foundry_version }} | |
| BASE_TAG=${{ needs.check-upstream.outputs.openclaw_version }} | |
| cache-from: type=gha,scope=openclaw-final | |
| cache-to: type=gha,scope=openclaw-final,mode=max | |
| provenance: true | |
| sbom: true | |
| # --------------------------------------------------------------------------- | |
| # Job 4: Security scan the final published image. | |
| # --------------------------------------------------------------------------- | |
| security-scan: | |
| needs: build-final | |
| runs-on: ubuntu-latest | |
| permissions: | |
| security-events: write | |
| steps: | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0 | |
| with: | |
| image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| severity: 'CRITICAL,HIGH' | |
| - name: Upload Trivy scan results to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@b13d724d35ff0a814e21683638ed68ed34cf53d1 # main | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| if: always() |