Skip to content

Docker Publish

Docker Publish #301

name: Docker Publish
on:
push:
branches: [ "main" ]
paths:
- 'Dockerfile*'
- 'docker-entrypoint.sh'
- 'Makefile'
- '.github/workflows/docker-publish.yml'
tags: [ 'v*' ]
repository_dispatch:
types: [upstream_release]
schedule:
- cron: '0 22 * * *' # 北京时间每天凌晨 6:00 (UTC 前一天 22:00)
pull_request:
branches: [ "main" ]
paths:
- 'Dockerfile*'
- 'docker-entrypoint.sh'
- 'Makefile'
- '.github/workflows/docker-publish.yml'
workflow_dispatch:
inputs:
force_publish:
description: 'Force publish even without version change'
required: false
default: 'false'
env:
REGISTRY: ghcr.io
# Use lower case for image names to satisfy GHCR requirements
IMAGE_BASE: openclaw-runtime
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# Job 1: Check upstream version and determine if publish is needed
check-upstream:
runs-on: ubuntu-latest
permissions:
contents: write
statuses: read
outputs:
has_new_version: ${{ steps.check.outputs.has_new_version }}
latest_version: ${{ steps.check.outputs.latest_version }}
should_publish: ${{ steps.check.outputs.should_publish }}
should_build: ${{ steps.check.outputs.should_build }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Check upstream version
id: check
run: |
# Get latest upstream version
LATEST=$(curl -fsSL "https://api.github.com/repos/openclaw/openclaw/releases/latest" | jq -r '.tag_name')
echo "latest_version=${LATEST}" >> $GITHUB_OUTPUT
# Always publish on tag push (manual release)
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
echo "Tag push detected - will build and publish"
echo "has_new_version=true" >> $GITHUB_OUTPUT
echo "should_publish=true" >> $GITHUB_OUTPUT
echo "should_build=true" >> $GITHUB_OUTPUT
exit 0
fi
# Publish on repository_dispatch (upstream triggered)
if [[ "${{ github.event_name }}" == "repository_dispatch" ]]; then
echo "repository_dispatch triggered - will build and publish"
echo "has_new_version=true" >> $GITHUB_OUTPUT
echo "should_publish=true" >> $GITHUB_OUTPUT
echo "should_build=true" >> $GITHUB_OUTPUT
exit 0
fi
# Publish on workflow_dispatch if force is enabled
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]] && [[ "${{ inputs.force_publish }}" == "true" ]]; then
echo "workflow_dispatch with force_publish - will build and publish"
echo "has_new_version=true" >> $GITHUB_OUTPUT
echo "should_publish=true" >> $GITHUB_OUTPUT
echo "should_build=true" >> $GITHUB_OUTPUT
exit 0
fi
# Schedule or push to main: publish if new version detected
if [[ "${{ github.event_name }}" == "schedule" ]] || \
[[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
if [[ -z "$LATEST" || "$LATEST" == "null" ]]; then
echo "Failed to fetch upstream version - skipping build"
echo "has_new_version=false" >> $GITHUB_OUTPUT
echo "should_publish=false" >> $GITHUB_OUTPUT
echo "should_build=false" >> $GITHUB_OUTPUT
exit 0
fi
CURRENT="${{ secrets.OPENCLAW_VERSION }}"
LATEST_NORM="${LATEST#v}"
CURRENT_NORM="${CURRENT#v}"
if [[ -n "$CURRENT_NORM" && "$LATEST_NORM" == "$CURRENT_NORM" ]]; then
echo "::notice::No version change: ${LATEST} == ${CURRENT} - skipping"
echo "has_new_version=false" >> $GITHUB_OUTPUT
echo "should_publish=false" >> $GITHUB_OUTPUT
echo "should_build=false" >> $GITHUB_OUTPUT
else
echo "New version detected: ${LATEST} (was: ${CURRENT:-none}) - will build and publish"
echo "has_new_version=true" >> $GITHUB_OUTPUT
echo "should_publish=true" >> $GITHUB_OUTPUT
echo "should_build=true" >> $GITHUB_OUTPUT
fi
exit 0
fi
# PR: build only (no publish)
echo "PR build verification - will build but not publish"
echo "has_new_version=false" >> $GITHUB_OUTPUT
echo "should_publish=false" >> $GITHUB_OUTPUT
echo "should_build=true" >> $GITHUB_OUTPUT
# Job 2: Prepare - reuse version from check-upstream
prepare:
needs: check-upstream
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
repo_owner: ${{ steps.repo.outputs.owner }}
steps:
- name: Set version
id: version
run: |
TAG="${{ needs.check-upstream.outputs.latest_version }}"
NPM_VERSION="${TAG#v}"
if [[ -z "$NPM_VERSION" || "$NPM_VERSION" == "null" ]]; then
echo "ERROR: No version from check-upstream, using latest"
NPM_VERSION="latest"
fi
echo "version=${NPM_VERSION}" >> $GITHUB_OUTPUT
echo "::notice::Building with OpenClaw version ${NPM_VERSION}"
- name: Get repo owner
id: repo
run: echo "owner=${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_OUTPUT
# Tier 1: Foundation
build-base:
needs: [check-upstream, prepare]
if: needs.check-upstream.outputs.should_build == 'true'
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GHCR
if: needs.check-upstream.outputs.should_publish == 'true'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push base
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile.base
push: ${{ needs.check-upstream.outputs.should_publish == 'true' }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.repo_owner }}/${{ env.IMAGE_BASE }}:base
# Tier 2: Stacks
build-stacks:
needs: [check-upstream, prepare, build-base]
if: needs.check-upstream.outputs.should_build == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
stack: [go, java, office]
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GHCR
if: needs.check-upstream.outputs.should_publish == 'true'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push stack
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile.stacks
target: stack-${{ matrix.stack }}
push: ${{ needs.check-upstream.outputs.should_publish == 'true' }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BASE_IMAGE=${{ env.REGISTRY }}/${{ needs.prepare.outputs.repo_owner }}/${{ env.IMAGE_BASE }}:base
tags: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.repo_owner }}/${{ env.IMAGE_BASE }}:${{ matrix.stack }}
# Tier 3: Product Images
build-products:
needs: [check-upstream, prepare, build-stacks]
if: needs.check-upstream.outputs.should_build == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
include:
- stack: base
tag: 'latest'
install_ai_tools: 1
- stack: go
tag: 'go'
install_ai_tools: 1
- stack: java
tag: 'java'
install_ai_tools: 1
- stack: office
tag: 'office'
install_ai_tools: 0
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GHCR
if: needs.check-upstream.outputs.should_publish == 'true'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push product
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile
push: ${{ needs.check-upstream.outputs.should_publish == 'true' }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BASE_IMAGE=${{ env.REGISTRY }}/${{ needs.prepare.outputs.repo_owner }}/${{ env.IMAGE_BASE }}:${{ matrix.stack }}
OPENCLAW_VERSION=${{ needs.prepare.outputs.version }}
INSTALL_BROWSER=0
INSTALL_AI_TOOLS=${{ matrix.install_ai_tools }}
tags: |
${{ env.REGISTRY }}/${{ needs.prepare.outputs.repo_owner }}/openclaw-devkit:${{ matrix.tag }}
${{ env.REGISTRY }}/${{ needs.prepare.outputs.repo_owner }}/openclaw-devkit:${{ needs.prepare.outputs.version }}${{ matrix.tag != 'latest' && format('-{0}', matrix.tag) || '' }}
# Job: Update version secret after successful publish
update-secret:
needs: [check-upstream, build-products, prepare]
if: |
always() &&
needs.check-upstream.result == 'success' &&
needs.build-products.result == 'success' &&
needs.check-upstream.outputs.should_publish == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Update OPENCLAW_VERSION secret
env:
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
run: |
TAG="${{ needs.check-upstream.outputs.latest_version }}"
echo "Updating OPENCLAW_VERSION secret to ${TAG}"
gh secret set OPENCLAW_VERSION --body "${TAG}" --repo "${GITHUB_REPOSITORY}"
echo "::notice::OPENCLAW_VERSION updated to ${TAG}"
# Job: Notify on failure
notify-failure:
needs: [check-upstream, build-base, build-stacks, build-products]
if: failure()
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v5
- name: Create issue comment on failure
run: |
gh issue create \
--title "Docker Build Failed - Run ${{ github.run_id }}" \
--body "## Docker Build Failed
**Workflow:** ${{ github.workflow }}
**Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
**Trigger:** ${{ github.event_name }}
**Commit:** ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}
Please check the workflow logs for details." \
--label "bug,ci"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}