ci: build and push Docker image to Forgejo registry on master #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "ci/docker-publish"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Adds a Forgejo Actions workflow that builds the Docker image and pushes it to the Forgejo container registry on every push to
master(+workflow_dispatch):git.kagaku.eu/teamai/categoraiserver:latest— tracks master (push events only)git.kagaku.eu/teamai/categoraiserver:<short-sha>— every build, for rollback / pre-merge testingA manually dispatched branch build publishes only its sha tag —
:latestis never unreviewed code (same guard as Kagura's workflow).Same pattern as angela_assistant / Kagura's
docker-publish.yml:runs-on: docker,docker/build-push-action@v6+ buildxREGISTRY_USER/REGISTRY_TOKEN(Forgejo's automaticGITHUB_TOKENcannot write packages)pathsfilter — single-project repo, every push is build-relevantdeploy/docker-compose.yml (new)
Registry-pull variant for the Portainer web editor —
image:instead ofbuild:. The rootdocker-compose.ymlstays for local development builds. Volumes becomeexternal: true; the header comment documents the one-timedocker volume createcommands (copied from the root compose's CIFS definition).Merge checklist
REGISTRY_USER/REGISTRY_TOKENmust be available to this repo — org-level actions secrets cover this automatically; if they were only set per-repo on Kagura/angela, add them once under Settings → Actions → Secrets.git.kagaku.eu/teamai/categoraiserver:latestshould appear in the registry.deploy/docker-compose.yml, create the external volumes first.No code changes — build config only.
🔮 fufu~ Jibril reviewed your code!
Ooh, a brand-new repo's very first PR — build config only, no code to sink my teeth into~ So instead I fetched your two named siblings (
Kaguraanddoujin-managerdocker-publish.yml) and diffed your workflow against them line by line. That's where the fun was hiding. ♪Verdict: ⛔ I can't let this pass~ ♡
The bones are lovely — action versions, login, buildx, the deploy compose, all faithful. But your
:latesttag has two unguarded doors, and:latestis your production channel (your own deploy compose says so: "Update the stack with re-pull pulls :latest"). A tag that production pulls on every redeploy deserves the same armor its siblings wear~⛔ These need fixing before I'm satisfied~
[.github/workflows/docker-publish.yml:14 + :46] —
workflow_dispatchunconditionally publishes:latest. The PR body says "same pattern as Kagura's docker-publish.yml", but Kagura guards exactly this — I quote its comment: "A manually dispatched branch build publishes its version tag alone, so Portainer's:latestis never quietly replaced by unmerged work." Your copy dispatches on any branch (Forgejo runs the branch's own workflow file) and pushes that branch's image as:latest. Fufu~ you wouldn't test this very workflow by dispatching it onci/docker-publishpre-merge… and silently flash unmerged code into the production tag, would you? ♡ That's precisely the footgun Kagura's conditional exists for.Fix: mirror Kagura — compute the tag list in a step and append
:latestonly whengithub.event_name == 'push':then
tags: ${{ steps.tags.outputs.list }}.[.github/workflows/docker-publish.yml:10 (missing)] — no
concurrencygroup.doujin-manager's workflow queues per-ref with a comment describing this exact failure: "Queue runs per ref instead of racing pushes of:latest. No cancel-in-progress: a half-cancelled push must never leave a stale:latestbehind." Two quick pushes to master → two parallel build-and-push jobs → last writer on:latestis nondeterministic, so Portainer's next re-pull can serve the older commit. Nondeterministic production state from a race is a bug, not a style preference~Fix:
(deliberately without
cancel-in-progress, per the sibling's reasoning).💡 Little ideas (non-blocking)~
/home/appuser/local(the local SQLite db fromappsettings.Container.json) isn't volume-mounted; a container recreate wipes it. Inherited faithfully from the root compose, andDatabaseSyncServicesyncs to the CIFS remote, so likely rebuildable — but if the local db ever holds anything precious, give it its own external volume.start_period: 40s(+3×30s retries) is the only grace before the healthcheck kills the container, andDatabaseSyncServiceblocks startup on the initial sync. Same value as the root compose, so presumably fine in practice — just bump it if first-boot sync ever gets slow.✅ What I liked~
docker volume createcommands with the full CIFS options copied verbatim from the root compose'sdriver_opts(I diffed them: username/uid=1000/iocharset/vers=3.0/file_mode/dir_mode — exact match). Future-you will thank present-you~fetch-depth: 0(no MinVer here — your Dockerfile passes noAPP_VERSION), nosubmodules: true(no.gitmodules), nopathsfilter (single-project repo, and doujin-manager documented why paths filters bite anyway)./scalaris really mapped (app.MapScalarApiReference(), Program.cs:99) andcurlreally ships in the runtime image (Dockerfile apt-get line). And the PR body's merge checklist is honest about the secrets prerequisite — Forgejo's automaticGITHUB_TOKENindeed can't write packages.(No test-coverage ask this time — the repo has no test projects and this PR adds zero runtime code; the workflow's first live run on master IS the test. fufu~ I checked anyway~)
Automated review by Jibril · 2026-09-14
CI/CD: absent for head SHA
d13db31(no workflow has run) · Local checks: YAML validated (both files), healthcheck endpoint + curl cross-verified vs Program.cs/Dockerfile, sibling workflows (Kagura, doujin-manager docker-publish.yml) fetched and diffed🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A build-config PR~ My favorite kind — pure plumbing, no runtime code to misbehave... but that just means every line has to be exactly right, because nobody tests their pipelines until 2 AM when the registry rejects a push~ ♡ So I tested it for you. Fufu~
Verdict: ✅ Looks good to me~
✅ What I liked~
:latestdispatch guard is genuinely correct — and I verified it, not just read it. I extracted the tag script and ran both arms against the real head SHA:push→git.kagaku.eu/teamai/categoraiserver:001631b+:latest;workflow_dispatch→ sha tag only,:latestuntouched. Theenv:indirection forgithub.event_name(instead of interpolating${{ }}directly inside the script) is the injection-safe form — cleaner than Kagura's sibling version, and it fixes a real gap in angela's workflow, which hasworkflow_dispatchwithout the guard and would happily overwrite:latestwith an unmerged branch. You took the best sibling and improved it~ ♪.gitmodulesin this repo, unlike Kagura/angela), no MinVer versioning (verified: noDirectory.Build.props— short-sha tags like angela are the right scheme here), nopathsfilter with the justification written in a comment. Each omission is deliberate and documented. That's the good kind of consistency — pattern-faithful, not cargo-culted.REGISTRY_USER/REGISTRY_TOKENorg secrets, same comment explaining why the automaticGITHUB_TOKENcan't write packages. Matches Kagura and angela exactly.deploy/docker-compose.ymlis a faithful sibling of angela's Portainer stack file — header with one-time prerequisites,image:instead ofbuild:, external volumes with the exactdocker volume createCIFS command copied from the root compose, and the service definition (ports8001:5001, env, volume mounts,/scalarhealthcheck timings) matches the root compose property-for-property. The secrets in the comments are<SMB_USER>/<SMB_PASS>placeholders only — nothing real leaked~💡 Little ideas (non-blocking)~
concurrency:group, so two rapid master pushes race each other into the registry (last-write-wins on:latest, wasted build minutes). Kagura and angela don't have one either, so this is an org-wide someday-idea, not a repo-specific gap — consistency wins for now.concurrency: { group: docker-publish-${{ github.ref }}, cancel-in-progress: true }would be the shape if you ever want it.No code paths, no test debt, no coverage questions — for a config-only PR, the "test" is the workflow surviving contact with the runner, and everything checkable locally checks out. Merge it and let the first master push be the proof~ fufu~ ♡
Automated review by Jibril · 2026-09-14
CI/CD: absent for head SHA
001631b(no coverage bot, 0 prior comments) · Local checks: YAML validated (workflow + both composes), tag-script both arms simulated against real head SHA, sibling-diffed vs Kagura + angeladocker-publish.yml+ angeladeploy/docker-compose.yml, submodule/MinVer absence verifiedBoth blockers from the ⛔ review addressed — head is now
4d0410b:1.
:latestdispatch guard (already in001631b, verified by your re-run) — dispatch publishes sha-only;:latestonly on push events.2. Concurrency group (
4d0410b) — added:Deliberately without
cancel-in-progress, per your first review's reasoning (and doujin-manager's actual comment, which I fetched to check — your second review'scancel-in-progress: trueaside would cancel an in-flight push mid-upload; the earliest-first reading wins): a half-cancelled push must never leave a stale:latestbehind. Queuing serializes rapid master pushes, killing the last-writer-wins race.Verified on the remote branch via fresh clone + raw-byte check: concurrency group present, no
cancel-in-progresskey, dispatch guard from001631bintact, secret refs un-mangled, YAML parses.Non-blocking ideas (local db volume,
start_period) left for a follow-up when the container's real-world first-boot behavior is known — noted, not forgotten.🔮 fufu~ Jibril reviewed your code!
Round three~ The
:latestarmor is complete now, and I checked every rivet myself — not from your reply, from the tree. ♪Verdict: ✅ Looks good to me~
4d0410bis exactly what I asked for, and nothing it shouldn't be: a surgical +5/-0 in.github/workflows/docker-publish.ymlonly (verified:git diff 001631b..4d0410btouches one file, five added lines, zero deletions — the dispatch guard from001631bbyte-intact, secrets untouched).✅ What I liked~
concurrency: group: docker-publish-${{ github.ref }}with nocancel-in-progresskey — I parsed the YAML and grepped for the key form specifically, because a stray occurrence in the comment must never be mistaken for the option. The comment itself is doujin-manager's reasoning verbatim: a half-cancelled push must never leave a stale:latestbehind. Rapid master pushes now queue instead of racing — the last-writer-wins nondeterminism is dead~push→git.kagaku.eu/teamai/categoraiserver:4d0410b+:latest;workflow_dispatch→ sha tag only. Theenv:indirection forevent_nameremains the injection-safe form. A guard that survives a later commit is a guard that was written properly~ ♡8001:5001, environment (map-form vs list-form — semantically identical, Compose accepts both), healthcheck timings includingstart_period: 40s, and both volume mounts. Image-only (nobuild:), volumesexternal: true, placeholders-only secrets in the header comments.cancel-in-progressasides and correctly sided with the earliest-first reasoning. Reading the siblings yourself instead of trusting the reviewer's paraphrase — fufu~ that's exactly how this is supposed to work~ ♪💡 Little ideas (non-blocking)~
start_periodheadroom for first-boot sync) remain correctly parked with a stated trigger — revisit when the container's real-world first-boot behavior is known. No action needed now.Both blockers from the ⛔ review are closed, verified against the tree at
4d0410b, with zero scope creep between them. The:latestchannel now has both pieces of armor its siblings wear. Merge it and let the first master push light the pipeline up~ fufu~ ♡Automated review by Jibril · 2026-09-14
CI/CD: absent for head SHA
4d0410b(workflow itself lands in this PR — its first master run is the test) · Local checks: incremental diff verified surgical (1 file, +5/-0), tag-script both arms re-simulated at real head SHA, concurrency key parsed + key-form grep (comment-only hit excluded), YAML validated (workflow + both composes), deploy↔root compose property parity re-verified