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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
ci: publish pilot images to 508-dev GHCR by michaelmwu · Pull Request #4 · 508-dev/centaur · GitHub
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci: publish pilot images to 508-dev GHCR by michaelmwu · Pull Request #4 · 508-dev/centaur · GitHub
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci: publish pilot images to 508-dev GHCR by michaelmwu · Pull Request #4 · 508-dev/centaur · GitHub
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' ci: publish pilot images to 508-dev GHCR by michaelmwu · Pull Request #4 · 508-dev/centaur · GitHub
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci: publish pilot images to 508-dev GHCR by michaelmwu · Pull Request #4 · 508-dev/centaur · GitHub
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci: publish pilot images to 508-dev GHCR by michaelmwu · Pull Request #4 · 508-dev/centaur · GitHub
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); ci: publish pilot images to 508-dev GHCR by michaelmwu · Pull Request #4 · 508-dev/centaur · GitHub
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 numberDiff line numberDiff 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