feat(ci): publish omnigraph-server image to Docker Hub alongside GHCR (#341)
Some checks failed
CI / Classify Changes (push) Has been cancelled
CI / Check AGENTS.md Links (push) Has been cancelled
CI / Container Entrypoint (push) Has been cancelled
Release Edge / Prepare edge release (push) Has been cancelled
CI / Test Workspace (push) Has been cancelled
CI / Test omnigraph-server --features aws (push) Has been cancelled
CI / RustFS S3 Integration (cli) (push) Has been cancelled
CI / RustFS S3 Integration (cluster) (push) Has been cancelled
CI / RustFS S3 Integration (engine) (push) Has been cancelled
CI / RustFS S3 Integration (failpoints) (push) Has been cancelled
CI / RustFS S3 Integration (server) (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-linux-x86_64 (push) Has been cancelled
Release Edge / Build edge omnigraph-macos-arm64 (push) Has been cancelled
Release Edge / Build edge omnigraph-windows-x86_64 (push) Has been cancelled
Release Edge / Smoke Windows installer (push) Has been cancelled

* feat(ci): publish omnigraph-server image to Docker Hub alongside GHCR

Docker Hub (docker.io/modernrelay/omnigraph-server) becomes the primary
anonymous-pull distribution channel; the step no-ops when the
DOCKERHUB_* secrets are unset so forks and the GHCR-only path keep
working. Same latest-tag policy as GHCR (real tag pushes only, never
dispatch backfills). Docs updated in the same PR (ci.md, deployment.md).

* review fixes: preserve digest summary on Hub failure, tolerate unprefixed RepoDigests

- Docker Hub push is continue-on-error with an explicit fail-gate AFTER
  the digest report, so a Hub failure still fails the run but can no
  longer suppress the summary for the already-pushed GHCR image.
- RepoDigests may store Hub images without the docker.io/ prefix
  depending on Docker version; the digest grep now accepts both forms.
This commit is contained in:
Andrew Altshuler 2026-07-08 20:28:28 +03:00 committed by GitHub
parent d2f9b5c5fb
commit 0c8d769501
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 58 additions and 8 deletions

View file

@ -86,18 +86,67 @@ jobs:
docker push "${image}:latest"
fi
- name: Report pinned digest
# Docker Hub is the primary public distribution channel (anonymous pulls
# for deployments). Skipped when the DOCKERHUB_* secrets are not
# configured, so forks and the GHCR-only path keep working. With an org
# access token, DOCKERHUB_USERNAME is the ORG name — which is also the
# image namespace.
# continue-on-error so a Hub failure can't suppress the digest summary
# for the already-pushed GHCR image; the fail-gate step below still
# fails the run afterwards.
- name: Push to Docker Hub
id: dockerhub
continue-on-error: true
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
if [[ -z "${DOCKERHUB_USERNAME}" || -z "${DOCKERHUB_TOKEN}" ]]; then
echo "DOCKERHUB_* secrets not configured; skipping Docker Hub publish."
exit 0
fi
owner="${GITHUB_REPOSITORY_OWNER,,}"
ghcr_image="ghcr.io/${owner}/omnigraph-server"
hub_image="docker.io/${DOCKERHUB_USERNAME}/omnigraph-server"
echo "${DOCKERHUB_TOKEN}" \
| docker login docker.io --username "${DOCKERHUB_USERNAME}" --password-stdin
docker tag "${ghcr_image}:${RELEASE_TAG}" "${hub_image}:${RELEASE_TAG}"
docker push "${hub_image}:${RELEASE_TAG}"
# Same `latest` policy as GHCR: real tag pushes only, never backfills.
if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
docker tag "${ghcr_image}:${RELEASE_TAG}" "${hub_image}:latest"
docker push "${hub_image}:latest"
fi
- name: Report pinned digests
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
run: |
owner="${GITHUB_REPOSITORY_OWNER,,}"
image="ghcr.io/${owner}/omnigraph-server"
digest=$(docker inspect --format='{{index .RepoDigests 0}}' "${image}:${RELEASE_TAG}")
{
echo "## Container image published"
echo
echo '```'
echo "${image}:${RELEASE_TAG}"
echo "${digest}"
for image in "ghcr.io/${owner}/omnigraph-server" \
${DOCKERHUB_USERNAME:+"docker.io/${DOCKERHUB_USERNAME}/omnigraph-server"}; do
echo "${image}:${RELEASE_TAG}"
# RepoDigests may store Hub images with or without the
# docker.io/ prefix depending on the Docker version.
docker inspect --format='{{range .RepoDigests}}{{.}}{{"\n"}}{{end}}' \
"${image}:${RELEASE_TAG}" | grep -E "^(docker\.io/)?${image#docker.io/}@" || true
done
echo '```'
echo
echo "Pin deployments to the digest form."
} >> "$GITHUB_STEP_SUMMARY"
# The summary above always runs; a Docker Hub failure still fails the
# run here so operators notice.
- name: Fail if Docker Hub push failed
if: steps.dockerhub.outcome == 'failure'
run: |
echo "Docker Hub push failed (GHCR image was pushed; see summary)." >&2
exit 1

View file

@ -12,4 +12,4 @@
- **release-edge.yml**: on every push to main, retags `edge`, builds Linux x86_64 / Linux arm64 / macOS arm64 archives and Windows x86_64 zip + sha256, publishes a rolling prerelease, then smoke-tests the Windows PowerShell installer against `edge`.
- **release.yml**: on `v*` tags, builds the Linux x86_64 / Linux arm64 / macOS arm64 archives and Windows x86_64 zip release matrix, updates the Homebrew tap (`scripts/update-homebrew-formula.sh`) by pushing the regenerated formula to `ModernRelay/homebrew-tap`, and smoke-tests the Windows PowerShell installer against the tag.
- **package.yml**: manual ECR image build; emits two image tags per commit (`<sha>`, `<sha>-aws`) via CodeBuild.
- **publish-image.yml**: builds and pushes the public `omnigraph-server` container image to GHCR (`ghcr.io/modernrelay/omnigraph-server:<tag>`) on `v*` tag pushes, plus manual `workflow_dispatch` with an explicit existing `v*` tag to backfill an image for a past release. Compiles the binaries inside a `rust:1-bookworm` builder container to match the Dockerfile's bookworm-slim runtime glibc (host-built ubuntu binaries do not run there), builds with `--features omnigraph-server/aws`, and moves `latest` only on real tag pushes — never on backfill dispatches. Separate from release.yml so an image-publish failure cannot block the binary release / Homebrew chain.
- **publish-image.yml**: builds and pushes the public `omnigraph-server` container image to GHCR (`ghcr.io/modernrelay/omnigraph-server:<tag>`) and to Docker Hub (`docker.io/modernrelay/omnigraph-server:<tag>`, the primary anonymous-pull channel; skipped when the `DOCKERHUB_*` secrets are unset) on `v*` tag pushes, plus manual `workflow_dispatch` with an explicit existing `v*` tag to backfill an image for a past release. Compiles the binaries inside a `rust:1-bookworm` builder container to match the Dockerfile's bookworm-slim runtime glibc (host-built ubuntu binaries do not run there), builds with `--features omnigraph-server/aws`, and moves `latest` only on real tag pushes — never on backfill dispatches. Separate from release.yml so an image-publish failure cannot block the binary release / Homebrew chain.

View file

@ -172,11 +172,12 @@ endpoint and credentials. CI exercises this path against containerized RustFS.
## Container Deployment
Pull the prebuilt public image (published to GHCR for every `v*` release by
Pull the prebuilt public image (published for every `v*` release by
`publish-image.yml`; built with the `aws` feature, linux/amd64):
```bash
docker pull ghcr.io/modernrelay/omnigraph-server:v0.8.1
docker pull modernrelay/omnigraph-server:v0.8.1 # Docker Hub
# or: docker pull ghcr.io/modernrelay/omnigraph-server:v0.8.1
```
Or build it yourself: