426 lines
16 KiB
Markdown
426 lines
16 KiB
Markdown
---
|
|
id: RAIL-AP-WP-0001
|
|
type: workplan
|
|
title: "Enable Gitea Container Registry for Cluster Image Publishing"
|
|
domain: railiance
|
|
repo: railiance-apps
|
|
status: archived
|
|
owner: railiance
|
|
topic_slug: railiance
|
|
created: "2026-05-15"
|
|
updated: "2026-06-05"
|
|
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: done
|
|
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.
|
|
|
|
**Done (2026-05-19):**
|
|
|
|
- Added `helm/gitea-registry-values.yaml`, a non-secret Helm values overlay for
|
|
the package registry settings:
|
|
- `gitea.config.packages.ENABLED: true`
|
|
- `gitea.config.packages.LIMIT_SIZE_CONTAINER: -1`
|
|
- `gitea.config.repository.DISABLED_REPO_UNITS: ""`
|
|
- `gitea.config.server.ROOT_URL: "https://gitea.coulomb.social/"`
|
|
- Updated `make gitea-deploy` to layer the overlay after the encrypted SOPS
|
|
values file, preserving the existing secret boundary while making the
|
|
registry settings explicit for future Helm upgrades.
|
|
- Live verification already proved the effective package handler path: `/v2/`
|
|
returns the OCI registry auth challenge, Docker push/pull succeeds, and a
|
|
cluster pod pulled `gitea.coulomb.social/coulomb/state-hub:6186a99`.
|
|
- No decrypted Helm values or secret material were 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://<gitea-host>/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 <gitea-host>
|
|
docker tag state-hub:local <gitea-host>/coulomb/state-hub:<tag>
|
|
docker push <gitea-host>/coulomb/state-hub:<tag>
|
|
docker pull <gitea-host>/coulomb/state-hub:<tag>
|
|
```
|
|
|
|
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: done
|
|
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.
|
|
|
|
**Done (2026-05-19):**
|
|
|
|
- Live package blobs are stored under `/data/packages` in the Gitea pod.
|
|
- `/data` is backed by PVC `default/gitea-shared-storage`, 10 GiB,
|
|
`local-path`, `RWO`.
|
|
- `/data/packages` was about 798.5 MiB after the State Hub and
|
|
Vergabe Teilnahme image pushes.
|
|
- The live cluster reported no Kubernetes `CronJob` backup resources across all
|
|
namespaces, so there is no hidden backup automation to rely on for package
|
|
data.
|
|
- Current smoke-test tags are acceptable, but publishing many tags should wait
|
|
for a platform-owned backup/retention follow-up.
|
|
|
|
## 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:<tag>`.
|
|
- 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.
|
|
|
|
### 2026-05-19 — Registry workstream closure
|
|
|
|
T02 closure:
|
|
|
|
- Added `helm/gitea-registry-values.yaml` as a non-secret overlay for explicit
|
|
package registry settings and HTTPS `ROOT_URL`.
|
|
- Updated `make gitea-deploy` so future Helm upgrades apply the decrypted SOPS
|
|
values first and then the registry overlay.
|
|
- `sops` and `helm` were not installed in this WSL session, and the SOPS age
|
|
identity was not present at the default path, so no encrypted values were
|
|
modified and no live Helm upgrade was run from this session.
|
|
- Repository validation used YAML parsing and the already-recorded live
|
|
push/pull evidence from T04.
|
|
|
|
T06 closure:
|
|
|
|
- Confirmed live package storage directory `/data/packages`.
|
|
- Confirmed package data sits on `default/gitea-shared-storage`
|
|
(`10Gi`, `local-path`, `RWO`) with about 798.5 MiB in package blobs.
|
|
- Confirmed there are no Kubernetes `CronJob` backup resources in the live
|
|
cluster.
|
|
- Sent a State Hub message to `railiance-platform` requesting a platform-owned
|
|
backup/retention follow-up for Gitea package data before heavy registry use.
|
|
|
|
## 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
|
|
`<gitea-host>/coulomb/state-hub:<tag>`.
|
|
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.
|