Skip to content

ci: skip unchanged images on main pushes #16

ci: skip unchanged images on main pushes

ci: skip unchanged images on main pushes #16

Workflow file for this run

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 }}