Skip to content

feat: add workflow to publish templates to ghcr.io #4

feat: add workflow to publish templates to ghcr.io

feat: add workflow to publish templates to ghcr.io #4

name: Build and Push Dev Containers
on:
push:
branches:
- main
paths:
- '.devcontainer/**'
- '.github/workflows/build-container.yml'
schedule:
# Every 24h at 2am UTC
- cron: '0 2 * * *'
workflow_dispatch:
env:
REGISTRY: ghcr.io
jobs:
discover-containers:
runs-on: ubuntu-latest
outputs:
containers: ${{ steps.find.outputs.containers }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Find all Dockerfiles
id: find
run: |
# Find all directories containing a Dockerfile
CONTAINERS=$(find .devcontainer -mindepth 2 -name "Dockerfile" -printf '%h\n' | \
sed 's|.devcontainer/||' | \
jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "containers=$CONTAINERS" >> $GITHUB_OUTPUT
echo "Found containers: $CONTAINERS"
check-updates:
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.check.outputs.should_build }}
claude_version: ${{ steps.versions.outputs.claude_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Restore cache
uses: actions/cache@v4
with:
path: |
.claude-version-cache
.dockerfile-hash-cache
key: build-cache-${{ github.run_id }}
restore-keys: |
build-cache-
- name: Get current versions
id: versions
run: |
# Get latest Claude Code version
CLAUDE_VERSION=$(npm view @anthropic-ai/claude-code version 2>/dev/null || echo "unknown")
echo "claude_version=$CLAUDE_VERSION" >> $GITHUB_OUTPUT
echo "Claude Code version: $CLAUDE_VERSION"
- name: Check if rebuild is needed
id: check
run: |
# For push events, always rebuild
if [[ "${{ github.event_name }}" == "push" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
echo "Push event - will rebuild"
exit 0
fi
# For workflow_dispatch, always rebuild
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
echo "Manual trigger - will rebuild"
exit 0
fi
# For cron, check versions and hashes
echo "Checking for changes..."
CURRENT_CLAUDE_VERSION="${{ steps.versions.outputs.claude_version }}"
CACHE_FILE=".claude-version-cache"
# Check if Claude Code version changed
if [[ -f "$CACHE_FILE" ]]; then
CACHED_VERSION=$(cat "$CACHE_FILE")
if [[ "$CACHED_VERSION" != "$CURRENT_CLAUDE_VERSION" ]]; then
echo "Claude Code version changed: $CACHED_VERSION -> $CURRENT_CLAUDE_VERSION"
echo "should_build=true" >> $GITHUB_OUTPUT
exit 0
fi
else
echo "No version cache found - will build"
echo "should_build=true" >> $GITHUB_OUTPUT
exit 0
fi
# Check hash of all Dockerfiles
DOCKERFILE_HASH=$(find .devcontainer -name "Dockerfile" -exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1)
CACHE_DOCKERFILE=".dockerfile-hash-cache"
if [[ -f "$CACHE_DOCKERFILE" ]]; then
CACHED_HASH=$(cat "$CACHE_DOCKERFILE")
if [[ "$CACHED_HASH" != "$DOCKERFILE_HASH" ]]; then
echo "Dockerfiles changed"
echo "should_build=true" >> $GITHUB_OUTPUT
exit 0
fi
else
echo "No Dockerfile cache found - will build"
echo "should_build=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "No changes detected - skipping build"
echo "should_build=false" >> $GITHUB_OUTPUT
build-and-push:
needs: [discover-containers, check-updates]
if: needs.check-updates.outputs.should_build == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
container: ${{ fromJson(needs.discover-containers.outputs.containers) }}
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set image name
id: image
run: |
CONTAINER="${{ matrix.container }}"
OWNER="${{ github.repository_owner }}"
# Generate image name based on folder
case "$CONTAINER" in
"base")
IMAGE_NAME="devcontainer-claude-code"
;;
"base-with-bmad")
IMAGE_NAME="devcontainer-claude-code-bmad"
;;
*)
IMAGE_NAME="devcontainer-claude-code-${CONTAINER}"
;;
esac
echo "name=${OWNER}/${IMAGE_NAME}" >> $GITHUB_OUTPUT
echo "Image name: ${OWNER}/${IMAGE_NAME}"
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
tags: |
type=raw,value=latest
type=raw,value={{date 'YYYYMMDD'}}
type=sha,prefix=
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .devcontainer/${{ matrix.container }}
file: .devcontainer/${{ matrix.container }}/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: |
${{ steps.meta.outputs.labels }}
org.opencontainers.image.claude-code-version=${{ needs.check-updates.outputs.claude_version }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha,scope=${{ matrix.container }}
cache-to: type=gha,mode=max,scope=${{ matrix.container }}
update-cache:
needs: [check-updates, build-and-push]
if: needs.check-updates.outputs.should_build == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Update version cache
run: |
echo "${{ needs.check-updates.outputs.claude_version }}" > .claude-version-cache
find .devcontainer -name "Dockerfile" -exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1 > .dockerfile-hash-cache
- name: Save cache
uses: actions/cache@v4
with:
path: |
.claude-version-cache
.dockerfile-hash-cache
key: build-cache-${{ github.run_id }}