ci: skip unchanged images on main pushes #16
Workflow file for this run
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: Release Sandbox Images | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| claude_code_version: | |
| description: "Pin @anthropic-ai/claude-code (e.g. 2.1.112). Empty = use Dockerfile default." | |
| required: false | |
| type: string | |
| codex_version: | |
| description: "Pin @openai/codex (e.g. 0.121.0). Empty = use Dockerfile default." | |
| required: false | |
| type: string | |
| image_tag: | |
| description: "Extra tag to publish (e.g. v2.1.112-rebuild). Empty = use semver/latest only." | |
| required: false | |
| type: string | |
| rebuild_base: | |
| description: "Also rebuild the base image." | |
| required: false | |
| type: boolean | |
| default: false | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*" | |
| release: | |
| types: [published] | |
| # Publish to GHCR only on release-producing events (tag push, GitHub release, | |
| # or manual dispatch). Pushes to main run the same build matrix as a CI | |
| # smoke check but do not push the resulting images. | |
| permissions: | |
| contents: read | |
| packages: write | |
| jobs: | |
| # Detect which image dirs changed on branch pushes so we can skip | |
| # matrix entries whose inputs are unchanged. Release-producing events | |
| # (tag push, release, dispatch) always build everything. | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| base: ${{ steps.compute.outputs.base }} | |
| sandboxes: ${{ steps.compute.outputs.sandboxes }} | |
| any_sandbox: ${{ steps.compute.outputs.any_sandbox }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/') | |
| with: | |
| filters: | | |
| base: | |
| - 'base/**' | |
| claude: | |
| - 'claude/**' | |
| codex: | |
| - 'codex/**' | |
| agents: | |
| - 'agents/**' | |
| - id: compute | |
| shell: bash | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| REF: ${{ github.ref }} | |
| BASE: ${{ steps.filter.outputs.base }} | |
| CLAUDE: ${{ steps.filter.outputs.claude }} | |
| CODEX: ${{ steps.filter.outputs.codex }} | |
| AGENTS: ${{ steps.filter.outputs.agents }} | |
| run: | | |
| set -e | |
| if [[ "$EVENT" == "push" && "$REF" == refs/heads/* ]]; then | |
| base=$BASE; claude=$CLAUDE; codex=$CODEX; agents=$AGENTS | |
| else | |
| base=true; claude=true; codex=true; agents=true | |
| fi | |
| # Base change propagates to all sandboxes (they FROM it). | |
| if [[ "$base" == "true" ]]; then | |
| claude=true; codex=true; agents=true | |
| fi | |
| entries=() | |
| [[ "$claude" == "true" ]] && entries+=('{"name":"sandbox-claude","context":"claude"}') | |
| [[ "$codex" == "true" ]] && entries+=('{"name":"sandbox-codex","context":"codex"}') | |
| [[ "$agents" == "true" ]] && entries+=('{"name":"sandbox-agents","context":"agents"}') | |
| joined=$(IFS=,; echo "${entries[*]}") | |
| echo "base=$base" >> "$GITHUB_OUTPUT" | |
| echo "sandboxes={\"image\":[${joined}]}" >> "$GITHUB_OUTPUT" | |
| if [[ ${#entries[@]} -gt 0 ]]; then | |
| echo "any_sandbox=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "any_sandbox=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| build-base: | |
| needs: changes | |
| if: >- | |
| (github.event_name != 'workflow_dispatch' || inputs.rebuild_base) | |
| && ( | |
| github.event_name != 'push' | |
| || !startsWith(github.ref, 'refs/heads/') | |
| || needs.changes.outputs.base == 'true' | |
| ) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-qemu-action@v3 | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute base image tags | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/sandbox-base | |
| tags: | | |
| type=semver,pattern=v{{version}} | |
| type=semver,pattern=v{{major}}.{{minor}} | |
| type=raw,value=latest | |
| - name: Build and push base | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: base | |
| platforms: linux/amd64,linux/arm64 | |
| push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'release' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=sandbox-base | |
| cache-to: type=gha,mode=max,scope=sandbox-base | |
| build-sandboxes: | |
| needs: [changes, build-base] | |
| if: >- | |
| always() | |
| && needs.changes.outputs.any_sandbox == 'true' | |
| && (needs.build-base.result == 'success' || needs.build-base.result == 'skipped') | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.changes.outputs.sandboxes) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-qemu-action@v3 | |
| - uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute image tags | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/${{ matrix.image.name }} | |
| tags: | | |
| type=semver,pattern=v{{version}} | |
| type=semver,pattern=v{{major}}.{{minor}} | |
| type=raw,value=latest | |
| type=raw,value=${{ inputs.image_tag }},enable=${{ inputs.image_tag != '' }} | |
| - name: Build and push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ${{ matrix.image.context }} | |
| platforms: linux/amd64,linux/arm64 | |
| push: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'release' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/sandbox-base:latest | |
| ${{ inputs.claude_code_version != '' && format('CLAUDE_CODE_VERSION={0}', inputs.claude_code_version) || '' }} | |
| ${{ inputs.codex_version != '' && format('CODEX_VERSION={0}', inputs.codex_version) || '' }} | |
| cache-from: type=gha,scope=${{ matrix.image.name }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.image.name }} |