Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 106 additions & 142 deletions .github/workflows/publish-images.yml
Original file line number Diff line number Diff line change
@@ -1,100 +1,85 @@
name: Publish Images
name: Build and Publish Images

on:
push:
branches: [main]
tags: [v*]
paths:
- .github/workflows/publish-images.yml
- services/**
- crates/harness-server/**
- harness/**
- centaur_sdk/**
- packages/**
- patches/**
- tools/**
- workflows/**
- scripts/bootstrap-k8s-secrets.sh
- package.json
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .agents/skills/**
pull_request:
branches: [main]
paths:
Comment thread
michaelmwu marked this conversation as resolved.
- .github/workflows/publish-images.yml
- services/**
- crates/harness-server/**
- harness/**
- centaur_sdk/**
- packages/**
- patches/**
- tools/**
- workflows/**
- scripts/bootstrap-k8s-secrets.sh
- package.json
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .agents/skills/**
workflow_dispatch:

concurrency:
group: publish-images-${{ github.ref }}
group: publish-images-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

permissions:
contents: read
id-token: write
packages: write
# Keep the default token inert. Pull requests only receive read access, while
# the protected-main publish job is the sole place that can write packages.
permissions: {}

env:
REGISTRY: ghcr.io
IMAGE_NAMESPACE: paradigmxyz/centaur
IMAGE_SOURCE: https://github.com/paradigmxyz/centaur
# Main/tags keep optimized release images. PRs and manual branch publishes
# use debug builds so staging/dev iteration does not spend minutes optimizing
# Rust binaries that are immediately replaced by the next test build.
RUST_BUILD_PROFILE: ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && 'debug' || 'release' }}
IMAGE_NAMESPACE: ${{ github.repository_owner }}/centaur
IMAGE_SOURCE: ${{ github.server_url }}/${{ github.repository }}

jobs:
# Build each image with Depot's hosted builders, push by digest, and hand
# the digests to the merge job below which assembles the multi-arch manifest.
# arm64 is only built on pushes to main and release tags — PR and manual
# dispatch builds stay amd64-only to keep iteration fast. Fork PRs skip both
# jobs so untrusted changes do not run on the hosted image builders.
build:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}
runs-on: ${{ matrix.platform == 'linux/arm64' && 'depot-ubuntu-24.04-arm-16' || 'depot-ubuntu-24.04-16' }}
validate:
name: Validate ${{ matrix.image }}
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-24.04
timeout-minutes: 90
permissions:
contents: read
strategy:
fail-fast: false
max-parallel: 5
matrix:
service: [api-rs, slackbotv2, linearbot, discordbot, githubbot, teamsbot, agent, iron-proxy, console]
platform: ${{ github.event_name == 'push' && fromJSON('["linux/amd64", "linux/arm64"]') || fromJSON('["linux/amd64"]') }}
include:
- service: api-rs
image: centaur-api-rs
- image: centaur-api-rs
context: .
dockerfile: services/api-rs/Dockerfile
target: ""
- service: slackbotv2
image: centaur-slackbotv2
context: .
dockerfile: services/slackbotv2/Dockerfile
target: ""
- service: linearbot
image: centaur-linearbot
context: .
dockerfile: services/linearbot/Dockerfile
target: ""
- service: discordbot
image: centaur-discordbot
- image: centaur-discordbot
context: .
dockerfile: services/discordbot/Dockerfile
target: ""
- service: githubbot
image: centaur-githubbot
context: .
dockerfile: services/githubbot/Dockerfile
target: ""
- service: teamsbot
image: centaur-teamsbot
context: .
dockerfile: services/teamsbot/Dockerfile
target: ""
- service: agent
image: centaur-agent
- image: centaur-agent
context: .
dockerfile: services/sandbox/Dockerfile
target: sandbox
- service: iron-proxy
image: centaur-iron-proxy
- image: centaur-iron-proxy
context: .
dockerfile: services/iron-proxy/Dockerfile
target: ""
- service: console
image: centaur-console
- image: centaur-console
context: services/console
dockerfile: services/console/Dockerfile
target: ""
Expand All @@ -105,90 +90,65 @@ jobs:
with:
persist-credentials: false

- name: Derive platform slug
run: |
platform="${{ matrix.platform }}"
echo "PLATFORM_SLUG=${platform//\//-}" >> "$GITHUB_ENV"

- name: Set up Depot
uses: depot/setup-action@91bc8495a33ebfc504ffc89e5674379ccf23c29c # v1.7.2

- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata for ${{ matrix.image }}
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}
labels: |
org.opencontainers.image.title=${{ matrix.image }}
org.opencontainers.image.source=${{ env.IMAGE_SOURCE }}
env:
DOCKER_METADATA_PR_HEAD_SHA: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0

- name: Build and push ${{ matrix.image }} (${{ matrix.platform }})
id: build
uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.18.0
- name: Build ${{ matrix.image }} without publishing
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
project: d8qqlh1bmq
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
target: ${{ matrix.target }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
# Tags are applied by the merge job on the multi-arch manifest;
# per-arch builds are pushed by digest only. The fork check is also
# enforced at the job level so external PRs do not build.
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }},push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }}
platforms: linux/amd64
push: false
build-args: |
RUST_BUILD_PROFILE=${{ env.RUST_BUILD_PROFILE }}

- name: Export digest
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"

- name: Upload digest
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: digests-${{ matrix.image }}-${{ env.PLATFORM_SLUG }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1

merge:
runs-on: depot-ubuntu-24.04-16
needs: build
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}
RUST_BUILD_PROFILE=debug
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,mode=max,scope=${{ matrix.image }}

publish:
name: Publish ${{ matrix.image }}
if: >-
${{
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
}}
runs-on: ubuntu-24.04
timeout-minutes: 90
permissions:
contents: read
packages: write
strategy:
fail-fast: false
max-parallel: 5
matrix:
include:
- image: centaur-api-rs
- image: centaur-slackbotv2
- image: centaur-linearbot
context: .
dockerfile: services/api-rs/Dockerfile
target: ""
- image: centaur-discordbot
context: .
dockerfile: services/discordbot/Dockerfile
target: ""
- image: centaur-agent
context: .
dockerfile: services/sandbox/Dockerfile
target: sandbox
- image: centaur-iron-proxy
context: .
dockerfile: services/iron-proxy/Dockerfile
target: ""
- image: centaur-console
- image: centaur-discordbot
- image: centaur-githubbot
- image: centaur-teamsbot
context: services/console
dockerfile: services/console/Dockerfile
target: ""

steps:
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
pattern: digests-${{ matrix.image }}-*
path: ${{ runner.temp }}/digests
merge-multiple: true
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
Expand All @@ -200,34 +160,38 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata for ${{ matrix.image }}
- name: Generate image metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}
flavor: |
latest=false
flavor: latest=false
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=main,enable={{is_default_branch}}
type=raw,value=edge,enable={{is_default_branch}}
type=sha,prefix=main-sha-,enable={{is_default_branch}}
type=semver,pattern={{raw}}
type=ref,event=pr
type=sha
type=raw,value=main
type=raw,value=sha-${{ github.sha }}
labels: |
org.opencontainers.image.title=${{ matrix.image }}
org.opencontainers.image.source=${{ env.IMAGE_SOURCE }}
env:
DOCKER_METADATA_PR_HEAD_SHA: true

- name: Create multi-arch manifest for ${{ matrix.image }}
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}@sha256:%s ' *)

- name: Inspect manifest
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}:${{ steps.meta.outputs.version }}
- name: Build and publish ${{ matrix.image }}
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
target: ${{ matrix.target }}
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
push: true
provenance: mode=max
sbom: true
build-args: |
RUST_BUILD_PROFILE=release
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,mode=max,scope=${{ matrix.image }}

- name: Record immutable image
run: >-
echo '${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}@${{ steps.build.outputs.digest }}'
>> "$GITHUB_STEP_SUMMARY"
Loading