--- id: RAIL-AP-WP-0001 type: workplan title: "Enable Gitea Container Registry for Cluster Image Publishing" domain: railiance repo: railiance-apps status: active owner: railiance topic_slug: railiance created: "2026-05-15" updated: "2026-05-15" planning_priority: high planning_order: 1 state_hub_workstream_id: "abd268e6-5af9-45ec-93e0-5ffca0211dd0" --- # Enable Gitea Container Registry for Cluster Image Publishing ## Goal Enable the existing Railiance-managed Gitea deployment to serve as an OCI container registry so cluster workloads can publish and pull images through the same source-forge boundary already used for Git hosting. The immediate forcing function is `CUST-WP-0011`: the State Hub image `state-hub:local` builds and smoke-tests locally, but cannot be published until Docker can authenticate against the Gitea registry `/v2/` endpoint. ## Placement in the Railiance Tooling Set This workplan lives in `railiance-apps` because Gitea is an S5 application workload. The active deployment surface is: - `helm/gitea-values.sops.yaml` — SOPS-encrypted Gitea Helm values. - `Makefile` target `gitea-deploy` — applies the Gitea Helm release. - `Makefile` target `gitea-status` — checks Gitea pod and database health. Lower-layer ownership remains unchanged: | Concern | Owner repo | Notes | |---------|------------|-------| | Gitea application config and Helm release | `railiance-apps` | This workplan | | Gitea database and CNPG health | `railiance-platform` | Existing `gitea-db` | | Ingress controller / cluster routing primitives | `railiance-cluster` | Only if `/v2/` requires lower-layer route changes | | Forgejo replacement roadmap | `railiance-infra` | `RAIL-HO-WP-0005` remains the umbrella plan | This is not a replacement for the Forgejo production migration. It is a pragmatic enablement step for current Gitea so images needed before Forgejo cutover have a governed registry target. ## Current Evidence - `railiance-apps/SCOPE.md` states that Gitea Helm values are owned here and that Gitea is the active git hosting platform for Railiance and Custodian repos. - `railiance-apps/Makefile` deploys Gitea via `helm/gitea-values.sops.yaml` into the `gitea` namespace. - `helm/gitea-values.sops.yaml` currently contains Gitea app config sections such as `server`, `database`, `cache`, `session`, and `queue`, but no visible unencrypted package-registry section. - `CUST-WP-0011` recorded Docker login/push receiving HTTP 404 from `/v2/`. Runtime inspection found no `[packages]` section in the live Gitea `app.ini`. ## Safety Contract - Do not commit decrypted Helm values or secrets. - Take a Gitea backup or verify the most recent backup before changing the live Helm release. - Preserve Git hosting behavior: clone, push, login, and repository browsing must remain healthy after any registry change. - Do not broaden public exposure beyond the current Gitea exposure model. - If package storage/backups require platform changes, pause and create or link a `railiance-platform` task before changing S3 resources. - If `/v2/` routing requires ingress-controller or NodePort changes, pause and create or link a `railiance-cluster` task before changing S2 resources. ## Target State - Gitea package registry is globally enabled. - Gitea container package uploads are allowed with an explicit size policy. - `/v2/` reaches Gitea and returns an OCI registry authentication challenge rather than a generic 404. - Docker can log in with a package-capable personal access token. - The State Hub image can be tagged, pushed, pulled, and referenced by a future cluster deployment. - The chosen registry URL and image naming convention are documented for downstream workplans. ## Tasks ### T01 — Inventory current registry and routing state ```task id: RAIL-AP-WP-0001-T01 status: done priority: high state_hub_task_id: "30075930-6585-465d-9b8f-1c5f2304632d" ``` Confirm the live Gitea registry state before changing Helm values. Checks: - Confirm the active Helm release, chart version, namespace, and values source. - Inspect live Gitea `app.ini` for `[packages]`, `[repository]`, `ROOT_URL`, and any package or repository-unit settings. - Check `GET /v2/` through each expected access path: service, NodePort or tunnel, and any ingress hostname. - Record the current response codes and headers for `/v2/`. - Identify the registry hostname that Docker and Kubernetes should use. **Done when:** the workplan records whether the blocker is app config, route config, TLS/trust, authentication, or a combination. --- ### T02 — Enable Gitea package and container registry config ```task id: RAIL-AP-WP-0001-T02 status: blocked priority: high state_hub_task_id: "e4136a4a-7730-47fe-bf64-315a513a3d8b" ``` Update `helm/gitea-values.sops.yaml` through `sops` so the generated Gitea `app.ini` enables packages and permits container uploads. Expected app configuration: ```ini [packages] ENABLED = true LIMIT_SIZE_CONTAINER = -1 ``` Also verify repository package units are not globally disabled: ```ini [repository] DISABLED_REPO_UNITS = ``` If the chart values require YAML nesting, express those settings under the existing `gitea.config` tree without exposing decrypted secrets in Git. **Done when:** a dry-rendered or live-inspected `app.ini` includes the package registry settings and no decrypted secret material was committed. --- ### T03 — Ensure `/v2/` reaches the Gitea registry handler ```task id: RAIL-AP-WP-0001-T03 status: done priority: high state_hub_task_id: "21c503be-12c7-411c-a82c-f738536cc114" ``` Make the OCI registry endpoint reachable at the root `/v2/` path for the chosen registry host. Validation: - `curl -i https:///v2/` should return the expected registry auth challenge or an auth-related response, not a generic 404. - Docker must not be pointed at a sub-path registry URL; the registry name is the host, and `/v2/` is fixed by the OCI distribution API. - If Gitea is served through a sub-path, route root-level `/v2/` to the Gitea service as required by Docker-compatible registries. Boundary note: Gitea Helm ingress/service settings belong here. Ingress controller or cluster network changes belong in `railiance-cluster`. **Done when:** `/v2/` is routed correctly through the intended operator and cluster access paths. --- ### T04 — Prove Docker login, push, and pull ```task id: RAIL-AP-WP-0001-T04 status: done priority: high state_hub_task_id: "5ffd7515-384b-4a11-9b5e-141197d1b985" ``` Use a Gitea user or bot personal access token with package read/write permissions to prove the registry workflow. Smoke sequence: ```bash docker login docker tag state-hub:local /coulomb/state-hub: docker push /coulomb/state-hub: docker pull /coulomb/state-hub: ``` Then verify pull behavior from the cluster node runtime or a disposable Kubernetes pod, including TLS trust and private-registry credentials if the package is private. **Done when:** the State Hub image can be pushed and pulled by both the operator workstation and the Railiance cluster runtime. **Done (2026-05-19):** - Authenticated with `docker login gitea.coulomb.social -u tegwick` using the `GITEA_API_TOKEN` env (token owned by user `tegwick`, Bernd Worsch). - Pushed `gitea.coulomb.social/coulomb/state-hub:6186a99` and `:latest` from the locally built `state-hub:local` image. - Image digest: `sha256:039d29654ccb3754c6ecdbe497c6364bbd8452edcdcb7fa937dd9debf5b734ff`. - Cluster-side pull verified via `kubectl run sh-pull-test --image=gitea.coulomb.social/coulomb/state-hub:6186a99`: pod reached `Running` in ~5s, image size 106 MB, no `imagePullSecret` required (`coulomb` org packages are public by default). - The same token + workflow was independently exercised pushing `vergabe-teilnahme:483a4df` under `RAILIANCE-WP-0002-T03` — confirming the registry is fully usable for any S5 workload. --- ### T05 — Document registry handoff for State Hub deployment ```task id: RAIL-AP-WP-0001-T05 status: done priority: medium state_hub_task_id: "55c2fd0c-ee6b-4524-8022-f21d6e9e046f" ``` Record the approved registry target and downstream handoff details. Expected output: - Registry host and owner/image naming convention. - State Hub image tag used for the successful smoke test. - Whether images are public or private. - Required Kubernetes `imagePullSecret` name or creation command if private. - Link back to `CUST-WP-0011` and its container image provenance. **Done when:** State Hub cluster deployment work can consume the image without rediscovering registry naming, auth, or TLS requirements. --- ### T06 — Capture backup and retention implications ```task id: RAIL-AP-WP-0001-T06 status: blocked priority: medium state_hub_task_id: "d5734ef1-d710-458c-b569-034f03a50bd8" ``` Confirm how Gitea package data is stored and backed up once container images are published. Checks: - Identify whether package blobs live on the existing Gitea persistent volume or another configured storage backend. - Confirm the current backup process includes package data. - Decide whether image retention or cleanup policy is needed before publishing many tags. - If storage or backups need S3/platform changes, create a follow-up `railiance-platform` workplan or task. **Done when:** package data durability is understood and no hidden storage gap is introduced by enabling the registry. ## Implementation Log ### 2026-05-15 — Inventory and S5 routing update T01 findings: - Active Kubernetes context: `default`. - Live Helm release metadata is stored in namespace `default` as revisions `sh.helm.release.v1.gitea.v1` through `v6`. - Live deployment labels report chart `gitea-12.5.0`, app version `1.25.4`, image `docker.gitea.com/gitea:1.25.4-rootless`. - Live Gitea service is `default/gitea`, type `NodePort`, port `3000:32166/TCP`. - `default/gitea` pod app.ini has server `ROOT_URL = http://gitea.coulomb.social`, `SSH_DOMAIN = gitea.coulomb.social`, and `DOMAIN = gitea.coulomb.social`. - No `[packages]` section was found in the inspected live app.ini output, but the application handler is active: pod-local `/v2/` and `http://92.205.130.254:32166/v2/` both returned OCI registry `401 Unauthorized` with `Docker-Distribution-Api-Version: registry/2.0`. - `http://gitea.coulomb.social/v2/` and `https://gitea.coulomb.social/v2/` returned generic `404`, so the immediate blocker is public hostname routing. A secondary cleanup is updating `ROOT_URL` to `https://gitea.coulomb.social/` so future auth challenges use the TLS endpoint. T02 status: - SOPS editing was attempted with `sops 3.9.0`, matching the file metadata. - The local age identity required by `helm/gitea-values.sops.yaml` was not available at the default path, so encrypted Helm values were not changed. - Once the age identity is available, apply these non-secret app config values: ```bash sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["packages"]["ENABLED"]' 'true' sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["packages"]["LIMIT_SIZE_CONTAINER"]' '-1' sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["repository"]["DISABLED_REPO_UNITS"]' '""' sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["server"]["ROOT_URL"]' '"https://gitea.coulomb.social/"' ``` T03 implementation: - Added `manifests/gitea-ingress.yaml`, a Traefik/cert-manager ingress for only `gitea.coulomb.social/v2*` to the existing `default/gitea` service. - Added `make gitea-ingress-deploy`. - Updated Makefile variables so the default Gitea namespace matches the live release namespace `default`; this avoids accidentally deploying a parallel `gitea` namespace release while the live release remains in `default`. - Applied the ingress to the live cluster. - Cert-manager issued `default/gitea-tls`. - `http://gitea.coulomb.social/v2/` now returns `401 Unauthorized` with `Docker-Distribution-Api-Version: registry/2.0`. - `https://gitea.coulomb.social/v2/` now returns `401 Unauthorized` with `Docker-Distribution-Api-Version: registry/2.0` and a TLS token realm. T04 blocker: - Docker is available locally, but no Gitea personal access token was present in this session. Login, push, pull, and cluster runtime pull remain blocked on a package-capable token and the T02/T03 deployment. T05 handoff: - Registry host: `gitea.coulomb.social`. - Image naming convention: `gitea.coulomb.social/coulomb/state-hub:`. - Handoff notes and `imagePullSecret` command are documented in `docs/gitea-container-registry.md`. - This links back to `CUST-WP-0011`, whose local image provenance is `state-hub:local`. T06 findings: - Live Gitea package data is expected to use the existing `/data` mount backed by PVC `default/gitea-shared-storage` (`10Gi`, `local-path`) unless package storage is separately configured later. - No Kubernetes `CronJob` backup resources were present in the live cluster inventory. Backup coverage for `gitea-shared-storage` needs operator confirmation or a `railiance-platform` follow-up before publishing many tags. ## Completion Criteria This workplan is complete when: 1. Gitea's container registry is enabled through governed Helm values. 2. `/v2/` is reachable at the chosen registry host. 3. `state-hub:local` has been pushed as `/coulomb/state-hub:`. 4. The pushed image can be pulled from the Railiance cluster runtime. 5. Registry auth, TLS, naming, and backup/retention notes are documented. ## Notes The future Forgejo migration should inherit the lessons from this workplan: registry package scope, `/v2/` routing, package data backups, and cluster image pull credentials. If Forgejo lands before this plan starts, close this workplan as superseded and move the tasks under `RAIL-HO-WP-0005` / the appropriate S5 Forgejo workplan.