excalidraw: migrate image build to GHA -> private ghcr (ADR-0002)
The image was still built by hand and pushed to DockerHub (v1..v4), predating the all-builds-off-infra doctrine; Viktor chose to move it onto the standard pipeline while shipping the export/rename feature rather than keep the manual flow. Mirrors the k8s-portal pattern: .github/workflows/build-excalidraw.yml (go test + buildx linux/amd64, pushes ghcr latest+sha), excalidraw ns added to the Kyverno ghcr-credentials allowlist (package is PRIVATE), deployment now pins ghcr :latest with pullPolicy Always + pull secret, Keel force/match-tag/5m annotations seed the metadata (live values win via ignore_changes). DockerHub viktorbarzin/excalidraw-library:v4 stays frozen as the rollback image. Docs: ci-cd.md + .claude/CLAUDE.md image lists updated (also backfilled the missing k8s-portal rows in ci-cd.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1cbc1e962b
commit
8fc657f431
5 changed files with 77 additions and 7 deletions
|
|
@ -137,7 +137,7 @@ audiobook-search) now also land on ghcr.
|
||||||
chrome-service-novnc, android-emulator.
|
chrome-service-novnc, android-emulator.
|
||||||
- **PRIVATE ghcr:** f1-stream, job-hunter, instagram-poster, payslip-ingest,
|
- **PRIVATE ghcr:** f1-stream, job-hunter, instagram-poster, payslip-ingest,
|
||||||
wealthfolio-sync, fire-planner, recruiter-responder, tripit, infra-cli,
|
wealthfolio-sync, fire-planner, recruiter-responder, tripit, infra-cli,
|
||||||
infra-ci, k8s-portal. Pulled via the Kyverno-synced `ghcr-credentials` allowlist
|
infra-ci, k8s-portal, excalidraw-library. Pulled via the Kyverno-synced `ghcr-credentials` allowlist
|
||||||
(`stacks/kyverno/modules/kyverno/ghcr-credentials.tf`; NOT cluster-wide; cred
|
(`stacks/kyverno/modules/kyverno/ghcr-credentials.tf`; NOT cluster-wide; cred
|
||||||
= Vault `secret/viktor/ghcr_pull_token`, a dedicated classic PAT scoped to
|
= Vault `secret/viktor/ghcr_pull_token`, a dedicated classic PAT scoped to
|
||||||
`read:packages` (UI-minted 2026-06-15; no longer the admin `github_pat`
|
`read:packages` (UI-minted 2026-06-15; no longer the admin `github_pat`
|
||||||
|
|
@ -153,7 +153,9 @@ github↔forgejo divergence was deliberately NOT reconciled):
|
||||||
`build-cli.yml` → DockerHub `viktorbarzin/infra` (kept) + `ghcr.io/viktorbarzin/infra-cli`;
|
`build-cli.yml` → DockerHub `viktorbarzin/infra` (kept) + `ghcr.io/viktorbarzin/infra-cli`;
|
||||||
`build-infra-ci.yml` → `ghcr.io/viktorbarzin/infra-ci`; `build-k8s-portal.yml` →
|
`build-infra-ci.yml` → `ghcr.io/viktorbarzin/infra-ci`; `build-k8s-portal.yml` →
|
||||||
PRIVATE `ghcr.io/viktorbarzin/k8s-portal` (Keel-deployed; the LAST in-cluster
|
PRIVATE `ghcr.io/viktorbarzin/k8s-portal` (Keel-deployed; the LAST in-cluster
|
||||||
Woodpecker build, migrated 2026-06-13 — completes "no local builds"). **infra-ci**
|
Woodpecker build, migrated 2026-06-13 — completes "no local builds"); `build-excalidraw.yml` →
|
||||||
|
PRIVATE `ghcr.io/viktorbarzin/excalidraw-library` (Keel-deployed; replaced
|
||||||
|
manual DockerHub pushes 2026-07-02 — DockerHub `:v4` frozen as rollback). **infra-ci**
|
||||||
is the image the `.woodpecker/default.yml` apply step + `drift-detection.yml` run
|
is the image the `.woodpecker/default.yml` apply step + `drift-detection.yml` run
|
||||||
in (proven by pipelines 165/166). chatterbox-tts is already built by tripit's GHA → ghcr.
|
in (proven by pipelines 165/166). chatterbox-tts is already built by tripit's GHA → ghcr.
|
||||||
The Woodpecker `build-ci-image.yml` + `build-cli.yml` pipelines were REMOVED;
|
The Woodpecker `build-ci-image.yml` + `build-cli.yml` pipelines were REMOVED;
|
||||||
|
|
|
||||||
42
.github/workflows/build-excalidraw.yml
vendored
Normal file
42
.github/workflows/build-excalidraw.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
name: Build excalidraw-library
|
||||||
|
|
||||||
|
# ADR-0002 / no-local-builds: excalidraw-library (infra-owned Go app behind
|
||||||
|
# draw.viktorbarzin.me) builds off-infra on GHA → private ghcr; Keel polls
|
||||||
|
# ghcr:latest and rolls the deployment. Replaces the manual DockerHub pushes
|
||||||
|
# (viktorbarzin/excalidraw-library:v4 stays frozen as the rollback image).
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- 'stacks/excalidraw/project/**'
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.21'
|
||||||
|
- run: go test ./...
|
||||||
|
working-directory: stacks/excalidraw/project
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: stacks/excalidraw/project
|
||||||
|
platforms: linux/amd64
|
||||||
|
provenance: false
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
ghcr.io/viktorbarzin/excalidraw-library:latest
|
||||||
|
ghcr.io/viktorbarzin/excalidraw-library:${{ github.sha }}
|
||||||
|
|
@ -94,7 +94,7 @@ can't reach Forgejo's public hairpin.
|
||||||
| Visibility | Packages | Pull mechanism |
|
| Visibility | Packages | Pull mechanism |
|
||||||
|------------|----------|----------------|
|
|------------|----------|----------------|
|
||||||
| **Public** | beadboard, nextcloud-todos, claude-agent-service, claude-memory-mcp, kms-website, freedify, tuya_bridge, x402-gateway, chrome-service-novnc, android-emulator | Anonymous |
|
| **Public** | beadboard, nextcloud-todos, claude-agent-service, claude-memory-mcp, kms-website, freedify, tuya_bridge, x402-gateway, chrome-service-novnc, android-emulator | Anonymous |
|
||||||
| **Private** | f1-stream, job-hunter, instagram-poster, payslip-ingest, wealthfolio-sync, fire-planner, recruiter-responder, tripit, infra-cli, infra-ci | `ghcr-credentials` dockerconfigjson |
|
| **Private** | f1-stream, job-hunter, instagram-poster, payslip-ingest, wealthfolio-sync, fire-planner, recruiter-responder, tripit, infra-cli, infra-ci, k8s-portal, excalidraw-library | `ghcr-credentials` dockerconfigjson |
|
||||||
|
|
||||||
Private-image pulls use the `ghcr-credentials` dockerconfigjson, cloned by the
|
Private-image pulls use the `ghcr-credentials` dockerconfigjson, cloned by the
|
||||||
kyverno stack's `sync-ghcr-credentials` ClusterPolicy to an explicit
|
kyverno stack's `sync-ghcr-credentials` ClusterPolicy to an explicit
|
||||||
|
|
@ -188,6 +188,8 @@ reconciled — the workflows were added to the GitHub lineage via PR):
|
||||||
| android-emulator | `build-android-emulator.yml` | public `ghcr.io/viktorbarzin/android-emulator` |
|
| android-emulator | `build-android-emulator.yml` | public `ghcr.io/viktorbarzin/android-emulator` |
|
||||||
| infra CLI | `build-cli.yml` | DockerHub `viktorbarzin/infra` (kept) + `ghcr.io/viktorbarzin/infra-cli` |
|
| infra CLI | `build-cli.yml` | DockerHub `viktorbarzin/infra` (kept) + `ghcr.io/viktorbarzin/infra-cli` |
|
||||||
| infra-ci | `build-infra-ci.yml` | private `ghcr.io/viktorbarzin/infra-ci` |
|
| infra-ci | `build-infra-ci.yml` | private `ghcr.io/viktorbarzin/infra-ci` |
|
||||||
|
| k8s-portal | `build-k8s-portal.yml` | private `ghcr.io/viktorbarzin/k8s-portal` (Keel rolls `:latest` digests) |
|
||||||
|
| excalidraw-library | `build-excalidraw.yml` | private `ghcr.io/viktorbarzin/excalidraw-library` (Keel rolls `:latest` digests; DockerHub `:v4` frozen as rollback) |
|
||||||
|
|
||||||
**`infra-ci`** is the image the `.woodpecker/default.yml` apply step and
|
**`infra-ci`** is the image the `.woodpecker/default.yml` apply step and
|
||||||
`drift-detection.yml` run in (proven by pipelines 165/166). `chatterbox-tts` is
|
`drift-detection.yml` run in (proven by pipelines 165/166). `chatterbox-tts` is
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ resource "kubernetes_namespace" "excalidraw" {
|
||||||
name = "excalidraw"
|
name = "excalidraw"
|
||||||
labels = {
|
labels = {
|
||||||
"istio-injection" : "disabled"
|
"istio-injection" : "disabled"
|
||||||
tier = local.tiers.aux
|
tier = local.tiers.aux
|
||||||
"keel.sh/enrolled" = "true"
|
"keel.sh/enrolled" = "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +45,15 @@ resource "kubernetes_deployment" "excalidraw" {
|
||||||
app = "excalidraw"
|
app = "excalidraw"
|
||||||
tier = local.tiers.aux
|
tier = local.tiers.aux
|
||||||
}
|
}
|
||||||
|
# Keel rolls new ghcr:latest digests (k8s-portal pattern). Values here are
|
||||||
|
# recreate-correct seeds only — the keys are in ignore_changes below, so
|
||||||
|
# the live annotations win on an existing deployment.
|
||||||
|
annotations = {
|
||||||
|
"keel.sh/policy" = "force"
|
||||||
|
"keel.sh/trigger" = "poll"
|
||||||
|
"keel.sh/match-tag" = "true"
|
||||||
|
"keel.sh/pollSchedule" = "@every 5m"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spec {
|
spec {
|
||||||
replicas = 1
|
replicas = 1
|
||||||
|
|
@ -67,9 +76,19 @@ resource "kubernetes_deployment" "excalidraw" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spec {
|
spec {
|
||||||
|
# GHCR pull secret: the ghcr-credentials Secret in this namespace is
|
||||||
|
# cloned in by the kyverno stack's sync-ghcr-credentials ClusterPolicy
|
||||||
|
# (allowlisted private-ghcr namespaces only — ADR-0002). Source of
|
||||||
|
# truth: stacks/kyverno/modules/kyverno/ghcr-credentials.tf.
|
||||||
|
image_pull_secrets {
|
||||||
|
name = "ghcr-credentials"
|
||||||
|
}
|
||||||
container {
|
container {
|
||||||
image = "viktorbarzin/excalidraw-library:v4"
|
# ADR-0002: GHA-built (.github/workflows/build-excalidraw.yml),
|
||||||
image_pull_policy = "IfNotPresent"
|
# PRIVATE ghcr; Keel rolls new :latest digests. DockerHub
|
||||||
|
# viktorbarzin/excalidraw-library:v4 is the frozen rollback image.
|
||||||
|
image = "ghcr.io/viktorbarzin/excalidraw-library:latest"
|
||||||
|
image_pull_policy = "Always"
|
||||||
name = "excalidraw"
|
name = "excalidraw"
|
||||||
port {
|
port {
|
||||||
container_port = 8080
|
container_port = 8080
|
||||||
|
|
@ -107,7 +126,7 @@ resource "kubernetes_deployment" "excalidraw" {
|
||||||
}
|
}
|
||||||
lifecycle {
|
lifecycle {
|
||||||
ignore_changes = [
|
ignore_changes = [
|
||||||
spec[0].template[0].spec[0].dns_config, # KYVERNO_LIFECYCLE_V1
|
spec[0].template[0].spec[0].dns_config, # KYVERNO_LIFECYCLE_V1
|
||||||
spec[0].template[0].spec[0].container[0].image, # KEEL_IGNORE_IMAGE — Keel manages tag updates
|
spec[0].template[0].spec[0].container[0].image, # KEEL_IGNORE_IMAGE — Keel manages tag updates
|
||||||
metadata[0].annotations["keel.sh/policy"],
|
metadata[0].annotations["keel.sh/policy"],
|
||||||
metadata[0].annotations["keel.sh/trigger"],
|
metadata[0].annotations["keel.sh/trigger"],
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,11 @@ locals {
|
||||||
# ghcr.io/passionprojectsanca/book-plotter (built by GHA in Anca's repo,
|
# ghcr.io/passionprojectsanca/book-plotter (built by GHA in Anca's repo,
|
||||||
# under her own org's ghcr). The deployment references the cloned secret.
|
# under her own org's ghcr). The deployment references the cloned secret.
|
||||||
"plotting-book",
|
"plotting-book",
|
||||||
|
# excalidraw: infra-owned image migrated from manual DockerHub pushes to
|
||||||
|
# PRIVATE ghcr.io/viktorbarzin/excalidraw-library (ADR-0002, built by
|
||||||
|
# .github/workflows/build-excalidraw.yml). The deployment references the
|
||||||
|
# cloned secret.
|
||||||
|
"excalidraw",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue