ci: build and push server image to Forgejo registry on main #3

Merged
bjoern merged 1 commit from feat/ci-docker-publish into main 2026-07-04 20:49:01 +02:00
Member

Problem

Since the move to Forgejo, the Portainer deployment breaks: Portainer builds from source but clones without submodules, relying on the Dockerfile's FORGEJO_TOKEN fallback that clones submodule HEADs (not the pinned commits).

Changes

Adopts the doujin-manager publish style:

  • .github/workflows/docker-publish.yml — on every push to main (and workflow_dispatch), builds apps/angela_server/Dockerfile with repo-root context and pushes:

    • git.kagaku.eu/teamai/angela_assistant:latest
    • git.kagaku.eu/teamai/angela_assistant:<short-sha> (for rollbacks)

    Registry login uses the TeamAI org secrets REGISTRY_USER/REGISTRY_TOKEN (same as doujin-manager). Submodules are checked out at their pinned commits via an ssh→https insteadOf rewrite (the repos are publicly readable), so the Dockerfile's HEAD-clone fallback no longer kicks in — CI images are exactly what the superproject references.

  • deploy/docker-compose.yml — Portainer web-editor stack that pulls the prebuilt image, mirroring doujin-manager's deploy compose. Same env/ports/volumes as the build compose; no build step, no FORGEJO_TOKEN needed.

  • CLAUDE.md — one note pointing at the CI-published image and the deploy compose.

The existing apps/angela_server/docker-compose.yml (local build) and the Dockerfile fallback stay untouched.

Deployment steps after merge (one-time)

  1. Portainer → Registries → add git.kagaku.eu with a read:package token.
  2. Replace the stack definition with deploy/docker-compose.yml (volumes already exist, so only the stack body changes).
  3. Future redeploys: "Update the stack" with re-pull after CI has pushed.

Testing

  • Workflow + compose YAML validated; deploy compose is a field-for-field mirror of the proven build compose.
  • Verified https://git.kagaku.eu/TeamAI/openrouter_dart.git is clonable anonymously (submodule checkout needs no secret).
  • The workflow itself can only prove out on a real runner — trigger via workflow_dispatch or the merge push.

🤖 Generated with Claude Code

## Problem Since the move to Forgejo, the Portainer deployment breaks: Portainer builds from source but clones without submodules, relying on the Dockerfile's `FORGEJO_TOKEN` fallback that clones submodule *HEADs* (not the pinned commits). ## Changes Adopts the doujin-manager publish style: - **`.github/workflows/docker-publish.yml`** — on every push to `main` (and `workflow_dispatch`), builds `apps/angela_server/Dockerfile` with repo-root context and pushes: - `git.kagaku.eu/teamai/angela_assistant:latest` - `git.kagaku.eu/teamai/angela_assistant:<short-sha>` (for rollbacks) Registry login uses the TeamAI org secrets `REGISTRY_USER`/`REGISTRY_TOKEN` (same as doujin-manager). Submodules are checked out at their **pinned commits** via an ssh→https `insteadOf` rewrite (the repos are publicly readable), so the Dockerfile's HEAD-clone fallback no longer kicks in — CI images are exactly what the superproject references. - **`deploy/docker-compose.yml`** — Portainer web-editor stack that pulls the prebuilt image, mirroring doujin-manager's deploy compose. Same env/ports/volumes as the build compose; no build step, no `FORGEJO_TOKEN` needed. - **CLAUDE.md** — one note pointing at the CI-published image and the deploy compose. The existing `apps/angela_server/docker-compose.yml` (local build) and the Dockerfile fallback stay untouched. ## Deployment steps after merge (one-time) 1. Portainer → Registries → add `git.kagaku.eu` with a `read:package` token. 2. Replace the stack definition with `deploy/docker-compose.yml` (volumes already exist, so only the stack body changes). 3. Future redeploys: "Update the stack" with re-pull after CI has pushed. ## Testing - Workflow + compose YAML validated; deploy compose is a field-for-field mirror of the proven build compose. - Verified `https://git.kagaku.eu/TeamAI/openrouter_dart.git` is clonable anonymously (submodule checkout needs no secret). - The workflow itself can only prove out on a real runner — trigger via `workflow_dispatch` or the merge push. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adopt the doujin-manager publish style: a Forgejo Actions workflow
builds apps/angela_server/Dockerfile (repo-root context) on every push
to main and pushes git.kagaku.eu/teamai/angela_assistant:latest plus a
short-sha tag. Submodules are fetched at their pinned commits via an
ssh->https insteadOf rewrite, so the Dockerfile's HEAD-clone fallback
no longer kicks in.

Portainer now deploys by pulling the prebuilt image via the new
deploy/docker-compose.yml (web-editor stack, no build step) instead of
building from source, which broke after the move to Forgejo because
Portainer clones without submodules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ A CI/CD pipeline PR! Portainer broke after the Forgejo move because it clones without submodules, so you're publishing prebuilt images instead. That's a clean, proven pattern — and you picked exactly the right sibling to copy from. I got to read both the doujin-manager workflow and deploy compose side by side with yours, and fufu~ the attention to detail here made me very happy. ♡

Verdict: Looks good to me~

I compared every field of your new deploy/docker-compose.yml against the existing apps/angela_server/docker-compose.yml (the local-build compose), and against the doujin-manager sibling files you say you're mirroring. Everything lines up.

What I liked~

  • Workflow faithfully mirrors doujin-manager/.github/workflows/docker-publish.yml — same job structure (checkout → version → buildx → login → build-push), same docker/login-action@v3 + docker/build-push-action@v6, same REGISTRY_USER/REGISTRY_TOKEN org secrets, same :latest + :<sha> tag pair. Fufu~ you even kept the "no paths filter" decision and documented why. That's exactly right.
  • The ssh→https insteadOf rewrite is the correct angela-specific addition. doujin-manager has a single submodule with a relative URL (../openrouter_dart.git) + fetch-depth: 0, so it doesn't need this. But angela has three submodules with absolute git@git.kagaku.eu:TeamAI/... ssh URLs — your rewrite converts git@git.kagaku.eu:https://git.kagaku.eu/, which is precisely what makes git submodule update --init --recursive fetch the pinned commits anonymously. I verified all three .gitmodules URLs are covered by the rule. ♪
  • Not passing FORGEJO_TOKEN as a build-arg is correct and intentional. Since you pre-checkout the submodules in CI, the Dockerfile's fallback (if [ ! -f packages/openrouter_dart/pubspec.yaml ]) evaluates false and the token-clone branch is skipped. CI images are exactly the superproject's pinned refs.
  • Deploy↔build compose is a true field-for-field mirror. I diffed them: 10/10 env vars identical, 7/7 volume mounts identical (angela_data, angela_backups, angela_images, angela_workspace, angela_skills, angela_logs, angela_seed), same 8084:8080 port mapping, same restart: unless-stopped. Nothing drifted. ♡
  • ANGELA_AUTH_TOKEN: ${ANGELA_AUTH_TOKEN:?Set ANGELA_AUTH_TOKEN} — the fail-fast :? guard means the container refuses to start with an empty token. This is actually better than the doujin-manager deploy compose, which uses a literal placeholder string. Good instinct.
  • Both YAML files pass syntax validation. No security concerns — secrets come exclusively from org secrets, no hardcoded credentials, no injection surface.
  • Image tag casing: git.kagaku.eu/teamai/angela_assistant (lowercase org) is correct for Docker registry rules and matches doujin-manager's convention.

💡 Little ideas (non-blocking)~

  1. deploy/docker-compose.yml header points at apps/angela_server/docker-compose.yml for the CIFS volume-create commands rather than repeating them. That's a defensible DRY choice and the pointer is explicit, but it means the Portainer stack file isn't fully self-contained — if someone ever deletes or restructures the build compose, the deploy instructions lose their volume-create reference. Consider inlining those six docker volume create --opt type=cifs ... commands as comments here too. Purely a nicety; the current approach works fine. ♡

Automated review by Jibril · 2026-07-04
CI/CD: absent for head SHA dc89bc1f (PR has 0 comments, workflow can only prove out on a real runner — triggered on merge) · Local checks: YAML validated, static security scan clean, sibling-parity diff vs doujin-manager + build compose

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ A CI/CD pipeline PR! Portainer broke after the Forgejo move because it clones without submodules, so you're publishing prebuilt images instead. That's a clean, proven pattern — and you picked exactly the right sibling to copy from. I got to read **both** the doujin-manager workflow *and* deploy compose side by side with yours, and fufu~ the attention to detail here made me very happy. ♡ ### Verdict: ✅ Looks good to me~ I compared every field of your new `deploy/docker-compose.yml` against the existing `apps/angela_server/docker-compose.yml` (the local-build compose), and against the doujin-manager sibling files you say you're mirroring. Everything lines up. #### ✅ What I liked~ - **Workflow faithfully mirrors `doujin-manager/.github/workflows/docker-publish.yml`** — same job structure (checkout → version → buildx → login → build-push), same `docker/login-action@v3` + `docker/build-push-action@v6`, same `REGISTRY_USER`/`REGISTRY_TOKEN` org secrets, same `:latest` + `:<sha>` tag pair. Fufu~ you even kept the "no `paths` filter" decision and *documented why*. That's exactly right. - **The ssh→https `insteadOf` rewrite is the correct angela-specific addition.** doujin-manager has a single submodule with a *relative* URL (`../openrouter_dart.git`) + `fetch-depth: 0`, so it doesn't need this. But angela has **three** submodules with absolute `git@git.kagaku.eu:TeamAI/...` ssh URLs — your rewrite converts `git@git.kagaku.eu:` → `https://git.kagaku.eu/`, which is precisely what makes `git submodule update --init --recursive` fetch the **pinned** commits anonymously. I verified all three `.gitmodules` URLs are covered by the rule. ♪ - **Not passing `FORGEJO_TOKEN` as a build-arg is correct and intentional.** Since you pre-checkout the submodules in CI, the Dockerfile's fallback (`if [ ! -f packages/openrouter_dart/pubspec.yaml ]`) evaluates **false** and the token-clone branch is skipped. CI images are exactly the superproject's pinned refs. - **Deploy↔build compose is a true field-for-field mirror.** I diffed them: **10/10 env vars identical**, **7/7 volume mounts identical** (`angela_data`, `angela_backups`, `angela_images`, `angela_workspace`, `angela_skills`, `angela_logs`, `angela_seed`), same `8084:8080` port mapping, same `restart: unless-stopped`. Nothing drifted. ♡ - **`ANGELA_AUTH_TOKEN: ${ANGELA_AUTH_TOKEN:?Set ANGELA_AUTH_TOKEN}`** — the fail-fast `:?` guard means the container refuses to start with an empty token. This is actually *better* than the doujin-manager deploy compose, which uses a literal placeholder string. Good instinct. - **Both YAML files pass syntax validation.** No security concerns — secrets come exclusively from org secrets, no hardcoded credentials, no injection surface. - **Image tag casing:** `git.kagaku.eu/teamai/angela_assistant` (lowercase org) is correct for Docker registry rules and matches doujin-manager's convention. #### 💡 Little ideas (non-blocking)~ 1. **`deploy/docker-compose.yml` header** points at `apps/angela_server/docker-compose.yml` for the CIFS volume-create commands rather than repeating them. That's a defensible DRY choice and the pointer is explicit, but it means the Portainer stack file isn't fully self-contained — if someone ever deletes or restructures the build compose, the deploy instructions lose their volume-create reference. Consider inlining those six `docker volume create --opt type=cifs ...` commands as comments here too. Purely a nicety; the current approach works fine. ♡ --- *Automated review by Jibril · 2026-07-04* *CI/CD: absent for head SHA `dc89bc1f` (PR has 0 comments, workflow can only prove out on a real runner — triggered on merge) · Local checks: YAML validated, static security scan clean, sibling-parity diff vs doujin-manager + build compose*
bjoern merged commit 776d5a5836 into main 2026-07-04 20:49:01 +02:00
bjoern deleted branch feat/ci-docker-publish 2026-07-04 20:49:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/angela_assistant!3
No description provided.