genesis-cicd
Adds one .github/workflows/ci.yml to a genesis monorepo. Railway and Overwatcher templates live in reference.md; Kubernetes uses kubernetes.md with the external lwlee2608/release-manifest actions.
- Kubernetes: after
changes -> resolve, independentserver -> build-server-imageandweb -> build-web-imagepaths join atpublish -> deploy. - Overwatcher: after
changes, independentserver -> build-server-imageandweb -> build-web-imagepaths; external deployment requires both builds to succeed. - Railway:
changes -> server/web.
Question flow
- Read the arguments first.
railway/kubernetes/overwatcheris the CD target;main-prod/release-prodis the branch model;sticky-diskopts into Blacksmith's paid Docker cache. Ask only what is missing. - Ask the rest in ONE AskUserQuestion call:
- CD target:
railway (Recommended)|kubernetes|overwatcher(VM running Docker Compose, deployed by the Overwatcher GitHub App) - Branch model:
main = production (Recommended)|main = staging, release = production - Docker layer cache, only for
kubernetes/overwatcher:GitHub Actions cache (Recommended)— free |Blacksmith sticky disk— $0.50/GB/month, also persistsRUN --mount=type=cache
- CD target:
- Resolve target prerequisites before writing. For Kubernetes, inspect the chart and collect missing GCP project, cluster locations, namespaces, release names, and environment values paths. Require chart support for complete digest references, not tag-only image construction. The canonical scaffold has no Helm chart: if one is missing, stop and ask whether to create it as a separate prerequisite or choose another target. Verify a published commit of
lwlee2608/release-manifestand pin both actions to it; until available, report the dependency blocker rather than invent a version. Do not leave deployment placeholders in a generated workflow. - Confirm in one sentence, then write. Example: "kubernetes, main→staging / release→prod, GCR, GHA cache — proceed?"
Rules
Read the target project first. Confirm service dir names,
make testin the server Makefile,packageManager: pnpm@…pluspnpm-lock.yamlin the web. Setpnpm/action-setup'spackage_json_fileto the service-local file;defaults.run.working-directorydoes not affect actions. For Kubernetes, validateChart.yaml, environment values files, and both image-reference keys against the images being published. Never leaveproject-00.One workflow file, jobs joined by
needs:. A second workflow chained viaworkflow_runbreaks the Actions graph, checks out the default-branch tip unless you passhead_sha, and fires for both the push and PR run of the same commit.Cancel superseded PR runs only. Give push runs distinct concurrency groups so an irrelevant push cannot cancel or displace a pending relevant build. Serialize Kubernetes publication per release track and Helm jobs per deployment target without cancelling active jobs. One publishing workflow owns each track because
github.run_numberis workflow-scoped. Publication and deployment are separate operations; the action does not guarantee deployment order.One test job per service. For Railway/Overwatcher and Kubernetes PRs, filter suites through
changes(dorny/paths-filter). Grant itcontents: readandpull-requests: read; usebase: ${{ github.ref }}. Service changes run that suite; changes toci.ymlrun both. Exclude documentation in these job filters, not the workflow trigger: bothpaths:andpaths-ignore:can leave required PR checks pending.changesalso reports deployment relevance; include actual chart/values paths and shared inputs. Kubernetes pushes instead test every image requested byresolve, including changes not yet published after a failed build. Explicit job status gates must allow PR tests whenresolveis intentionally skipped.Gate downstream work on relevance and successful prerequisites, not skipped tests alone. Overwatcher builds both images when relevant, or on every release push, in independent
build-server-imageandbuild-web-imagejobs. Each needs onlychangesand its own suite (plus optional store for server); retain the base relevance and release-test skip policy. A failed suite or image build does not block the other image path, but external deployment requires both image jobs to succeed. Kubernetes runsresolveon every eligible push, compares caller-computed fingerprints with that track's published baseline, and builds only requested images. Each image job requires successful resolution and its own suite, independently of the other suite.publishrequires successful change detection/resolution, relevance (requested builds, deployment changes, or a release push), success for each requested image job, and skipped status for each unrequested image job. Both image jobs skip with no requested builds; Helm changes and release pushes still publish retained references. A failed suite can leave the other image already pushed, but publication is blocked. Optional store tests gate onlybuild-server-image; publish enforces them through that build's success.deployrequires publication success. Never let PRs publish or deploy.Branch model:
main-prod:push: branches: [main]+pull_request.release-prod:push: branches: [main, release]+pull_request. Kubernetes usesstagingon main andproductionon release. Merging main into release automatically tests/builds changed images from the release commit and deploys production; unchanged production digests are retained. No manual dispatch,source-release, or staging-image promotion is added. For Railway/Overwatcher, retain the base release-test policy: only skip after confirming a protected, successfully tested main tree; otherwise run all suites on release pushes. Do not path-filter release pushes.
CD target:
railway: no image-build jobs, no deploy job. Railway builds from the Dockerfile on push; underrelease-prodpoint its production environment atreleaseand staging atmain. Configure Wait for CI and service watch paths separately; skipped GitHub jobs alone do not suppress Railway deployments. See the reference checklist for promotion exceptions.kubernetes: use the complete Kubernetes reference, not the base GHCR workflow plus a Helm tail. GCR stores images; GitHub Release assets in the application repository store manifests. A track-awaredeployjob runs Helm with both complete digest references from the successful publish action'smanifestoutput. Undermain-prod, main targets production; underrelease-prod, main targets staging and release targets production. Preserve the production environment gate.overwatcher: images to GHCR, no deploy job. Set the service's Workflow field toci.yml. Verify the integration accepts only successful push runs on the service's branch whosebuild-server-imageandbuild-web-imagejobs both succeeded; workflow success alone also includes no-op and PR runs. If the integration cannot check both build jobs, report that prerequisite rather than claim no-op deployment suppression works.
Never deploy shared
latest. Overwatcher retains SHA/branch tags and must select both images using the successful run'shead_sha; fixed-tag-only integrations need that prerequisite resolved. Kubernetes uses unique run/attempt build tags only to push, then records/deploysrepository@sha256:.... Fingerprints cover the full service context, workflow, and all build inputs/arguments. Use a stable build-input commit for serverCOMMIT_SHAand include it in the fingerprint, not the current unrelated workflow SHA. Whole-context docs can trigger rebuilds; root docs outside build inputs do not. Do not copy the abandoned Python release manager into generated projects.Blacksmith runners:
blacksmith-4vcpu-ubuntu-2404forserver,build-server-image, andbuild-web-imageon both image-building targets;blacksmith-2vcpu-ubuntu-2404elsewhere. Keep upstreamactions/setup-go/actions/setup-nodewithcacheon; Blacksmith accelerates the GitHub cache API transparently and itsuseblacksmith/setup-*wrappers are deprecated.Docker layer cache:
type=gha, one scope per image, unless the user chosesticky-disk.useblacksmith/setup-docker-builder+useblacksmith/build-push-actionkeep the builder on a sticky disk billed per GB; never add them for "speed" without the user opting in, and never mix the two approaches in one job. For Kubernetes and Overwatcher, replace the builder in each image job and use separate sticky cache keys<app>-serverand<app>-web; job gates make per-step requested-image conditions unnecessary.Never commit secrets. GHCR uses
GITHUB_TOKENwithpackages: writeonly onbuild-server-imageandbuild-web-image; test jobs remain read-only. Kubernetes grantscontents: writeonly topublish; resolution needscontents: read. GCR/GKE need a service-account JSON the user adds in repo settings. Kubernetes registry-auth jobs export scalar digests, not JSON: multiline secret masking can suppress JSON job outputs. Assemble newly built refs in publish'simagesstep; the publish action supplies retained refs.
Verification procedure
- Run
actionlint .github/workflows/ci.yml, configuring its allowed self-hosted runner labels for Blacksmith. A YAML parser is only a syntax fallback, not validation of Actions expressions or dependencies; report when actionlint was unavailable. grep -n project-00 .github/workflows/ci.ymlreturns nothing.- Run the same steps locally:
go vet ./... && make test;pnpm install --frozen-lockfile && pnpm lint && pnpm build. - Verify the selected target's single job graph. Kubernetes PR tests must work with skipped resolve; all write/deploy jobs must skip. Do not claim live verification without an observed Actions run.
- Kubernetes: verify bootstrap builds both and each image starts after its own suite without waiting for the other. A server-only change retains web; Helm-only changes skip both image jobs but publish/deploy retained digests. An unrelated push retries unpublished changes after a failed build. Healthy root-docs-only main pushes skip both image jobs and do not publish/deploy. Failed/cancelled requested suites or builds block publication even if the other image was pushed. Check scalar digest outputs and publish-side JSON assembly, both cache variants with separate per-image caches, and optional store gates that block only the server build and publication, not web.
- Kubernetes
release-prod: merging main into release automatically uses the production baseline, tests/builds requested release-branch images, and deploys both published digests. Verify the no-new-images release path and publication/Helm failure recovery inkubernetes.md. - Railway/Overwatcher: retain the reference's behavior checks, including both-image SHA builds for Overwatcher and external no-op/deployment configuration. Verify each image starts after its own suite without waiting for the other; a failed suite/build or optional store failure does not block the other image path, but prevents deployment. Check PR/no-op skips, release builds with intentionally skipped checks, and separate per-image caches. Do not infer external CD behavior from GitHub job skips.
Common mistakes to watch for
- Using only the previous-commit diff for Kubernetes builds - changes from a failed build would disappear on the next unrelated push; compare against the published manifest instead.
- Accepting skipped requested tests - skipped must mean an intentionally retained image, not a failed dependency.
- Deploying both images with the current SHA in Kubernetes - unchanged images retain older digests, not the current commit's tag.
- Using workflow-level
paths:orpaths-ignore:on PRs - required PR checks never report and block merging. - Treating workflow success as proof images were published - no-op and PR runs can succeed with image jobs skipped; Kubernetes Helm-only and no-new-images release pushes can publish retained refs with both image jobs skipped.
- Writing a deploy job for Railway or Overwatcher — both deploy on their own; a helm/ssh job would fight them.
npm cifor the web — ignorespnpm-lock.yaml.- Sharing one
type=ghascope between server and web — each build evicts the other's layers.