name: Publish Docker image (recovery)
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. v0.9.10)"
required: true
type: string
jobs:
publish-docker:
name: Publish Docker image
if: github.repository == 'nubo-db/dynoxide'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- name: Validate and resolve tag
id: tag
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if ! [[ "$INPUT_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Invalid tag format: $INPUT_TAG (expected vX.Y.Z or vX.Y.Z-pre.N)"
exit 1
fi
echo "tag=$INPUT_TAG" >> "$GITHUB_OUTPUT"
echo "version=${INPUT_TAG#v}" >> "$GITHUB_OUTPUT"
- name: Stage prebuilt linux-musl binaries
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
set -euo pipefail
rm -rf dist staging
mkdir -p dist/amd64 dist/arm64 staging
gh release download "$TAG" \
--repo nubo-db/dynoxide \
--pattern '*linux-musl.tar.gz' \
--dir staging/
tar -xzf staging/dynoxide-x86_64-unknown-linux-musl.tar.gz -C dist/amd64/
tar -xzf staging/dynoxide-aarch64-unknown-linux-musl.tar.gz -C dist/arm64/
chmod +x dist/amd64/dynoxide dist/arm64/dynoxide
file dist/amd64/dynoxide
file dist/arm64/dynoxide
file dist/amd64/dynoxide | grep -q 'x86-64'
file dist/arm64/dynoxide | grep -q 'aarch64'
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Login to GHCR
id: ghcr-login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
id: dockerhub-login
continue-on-error: true
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Compute image tags and labels
id: meta
uses: docker/metadata-action@v5
env:
INPUT_TAG: ${{ inputs.tag }}
with:
images: ghcr.io/${{ github.repository }}
flavor: |
latest=auto
tags: |
type=semver,pattern={{version}},value=${{ inputs.tag }}
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag }}
type=semver,pattern={{major}},value=${{ inputs.tag }},enable=${{ !startsWith(inputs.tag, 'v0.') }}
- name: Build and push to GHCR
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
provenance: mode=max
sbom: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Verify published image runs on both architectures
env:
IMAGE: ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.version }}
EXPECTED: ${{ steps.tag.outputs.version }}
run: |
set -euo pipefail
for arch in linux/amd64 linux/arm64; do
actual=$(docker run --rm --pull=always --platform "$arch" "$IMAGE" --version | awk '{print $NF}')
if [ "$actual" != "$EXPECTED" ]; then
echo "::error::$arch reported version $actual, expected $EXPECTED"
exit 1
fi
done
- name: Attest build provenance
id: attest
continue-on-error: true
uses: actions/attest-build-provenance@v2
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Mirror to Docker Hub
id: mirror-dockerhub
if: ${{ !cancelled() && steps.dockerhub-login.outcome == 'success' }}
continue-on-error: true
env:
META_JSON: ${{ steps.meta.outputs.json }}
run: |
set -euo pipefail
echo "$META_JSON" | jq -r '.tags[]' | while read -r src_tag; do
tag="${src_tag##*:}"
docker buildx imagetools create \
-t "docker.io/nubodb/dynoxide:${tag}" \
"$src_tag"
done
- name: Publish summary
if: ${{ !cancelled() }}
env:
META_JSON: ${{ steps.meta.outputs.json }}
GHCR_OUTCOME: ${{ steps.build.outcome }}
ATTEST_OUTCOME: ${{ steps.attest.outcome }}
DOCKERHUB_OUTCOME: ${{ steps.mirror-dockerhub.outcome }}
run: |
{
echo "## Docker recovery publish summary"
echo
echo "| Surface | Outcome |"
echo "| --- | --- |"
echo "| GHCR push | ${GHCR_OUTCOME} |"
echo "| Provenance attestation | ${ATTEST_OUTCOME} |"
echo "| Docker Hub mirror | ${DOCKERHUB_OUTCOME:-skipped} |"
echo "| ECR Public mirror | not run from docker.yml |"
echo
echo "### Pushed tags"
echo
echo "$META_JSON" | jq -r '.tags[] | "- `\(.)`"'
} >> "$GITHUB_STEP_SUMMARY"