Skip to content
Merged
Show file tree
Hide file tree
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
89 changes: 89 additions & 0 deletions .github/workflows/publish-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Publish images

# Force-(re)build and push all three container images for a chosen ref —
# normally the tip of main. Publishing only; this does NOT deploy (cd.yml owns
# rollout to production).
#
# Why this exists: images are tagged by git tree hash, not commit SHA. On a PR
# whose branch is up to date with main the merge is effectively a fast-forward,
# so the merge commit's tree equals the PR-head tree that ci.yml's
# build-and-push already published, and cd.yml deploys that prebuilt image. But
# when a PR is merged while its branch is behind main (branch protection does
# not require branches to be up to date), GitHub writes a real merge commit
# whose tree differs from the PR-head tree. ci.yml built an image for the
# PR-head tree, so the tip-of-main tree has no matching image. cd.yml's
# build-missing job normally backfills it on the push to main, but this is the
# on-demand escape hatch for when it did not (e.g. its run was superseded by a
# newer push in the cd-deploy concurrency group), or when an image otherwise
# needs to be rebuilt.
#
# The matrix, runner, tag scheme and registry login are kept in sync with
# ci.yml's build-and-push and cd.yml's build-missing.

on:
workflow_dispatch:
inputs:
ref:
description: "Git ref (branch, tag, or SHA) to build from. Defaults to the ref this workflow is dispatched on (tip of main)."
required: false
type: string

permissions:
contents: read

jobs:
publish:
if: github.repository == 'imaustink/glyph'
# Self-hosted, arm64 — matches the production cluster's nodes. Building on
# ubuntu-latest (amd64) produces images that fail with "exec format error"
# when the kubelet tries to run them.
runs-on: arc-runner-set
# One job per image so the three builds run in parallel on separate
# runners. Keep in sync with ci.yml's build-and-push and cd.yml's
# build-missing.
strategy:
fail-fast: false
matrix:
include:
- image: frontend
dockerfile: Dockerfile
context: .
build-args: --build-arg VITE_STORAGE_MODE=api --build-arg VITE_API_URL=
- image: api
dockerfile: api/Dockerfile
context: api/
- image: collab
dockerfile: collab/Dockerfile
context: .
steps:
# An empty `ref` input falls back to the ref this workflow was dispatched
# on (the default branch, i.e. the tip of main, unless another is picked).
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- name: Docker login
run: |
echo "$REGISTRY_PASSWORD" | docker login docker.io \
-u "$REGISTRY_USERNAME" --password-stdin
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}

# Tagged by git tree hash (content) so the tag matches exactly what
# cd.yml resolves and deploys. Unconditional build + push (unlike
# cd.yml's build-missing, which skips when the tag already exists): this
# is a *force* publish, so it also overwrites a bad or stale image under
# the same tag. `--pull --no-cache` guarantees a genuinely fresh build so
# the force path also fixes an image whose contents (not just the pushed
# tag) went bad — a base image that moved under a floating tag, or a
# poisoned local layer cache on a reused arc-runner-set runner — which a
# cache-reusing rebuild would otherwise reproduce byte-for-byte.
- name: Build & push ${{ matrix.image }} (forced)
run: |
ref="docker.io/blackmarket/glyph-${{ matrix.image }}:$(git rev-parse HEAD^{tree})"
echo "Force-publishing $ref"
docker build --pull --no-cache ${{ matrix.build-args }} \
-t "$ref" \
-f ${{ matrix.dockerfile }} ${{ matrix.context }}
docker push "$ref"
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ CI publishes images to public Docker Hub repositories for pull requests from thi

Images are built for **linux/arm64** and tagged with the git tree hash of the commit they were built from (`git rev-parse HEAD^{tree}`); there is no `latest` tag. These are the default repositories in the Helm chart (`helm/glyph/values.yaml`).

Because the tag is the tree hash, merging a PR whose branch is **behind `main`** produces a merge commit whose tree differs from the PR-head tree CI built — so the tip-of-main image can be missing. CD's build-missing job normally backfills it on the push to `main`; the **Publish images** workflow (`.github/workflows/publish-images.yml`, `workflow_dispatch`) is the on-demand escape hatch to force-(re)build and push all three images for a chosen ref (default: tip of `main`) without deploying.

## Desktop app

Glyph also ships as a native desktop app for macOS and Linux (`src-tauri/`) — a thin window that connects to your self-hosted Glyph server, with automatic updates.
Expand Down
Loading