feat(workplan): KEY-WP-0002 — build & publish KeyCape image to Gitea OCI registry
Some checks failed
CI / Build and Test (push) Has been cancelled

Adds workplan for containerising KeyCape and publishing to the self-hosted
Gitea registry on CoulombCore (92.205.130.254:32166) instead of GHCR. Covers
Makefile targets, Gitea Actions workflow, k3s insecure registry config, machine
account/token management, and a smoke test round-trip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 00:18:12 +01:00
parent 303663e48b
commit 393ef3ca76

View File

@@ -0,0 +1,245 @@
---
id: KEY-WP-0002
type: workplan
title: "KeyCape Container Image — Build & Publish to Gitea OCI Registry"
domain: netkingdom
repo: key-cape
status: active
owner: netkingdom
topic_slug: netkingdom
created: "2026-03-22"
updated: "2026-03-22"
capability_request_id: ""
state_hub_workstream_id: "c8843c7a-460a-47a2-b45a-b8d3940f9aa2"
---
# KEY-WP-0002 — KeyCape Container Image — Build & Publish to Gitea OCI Registry
## Problem
KeyCape has a `Dockerfile` but no automated build pipeline and no published
image. Other services (k3s deployments, local dev) that need to run KeyCape
must build locally from source. There is no versioned artefact to reference
in Helm charts or manifests.
The capability request for this work was originally misrouted to railiance.
It belongs here: KeyCape owns its own image.
## Goal
Produce a versioned OCI image for KeyCape, published to the Gitea container
registry on CoulombCore (`92.205.130.254:32166`), triggered automatically on
every merge to `main` and on semver tags (`v*`).
**Gitea OCI registry endpoint:** `92.205.130.254:32166`
**Image name:** `92.205.130.254:32166/netkingdom/key-cape`
> **Why Gitea, not GHCR?**
> The net-kingdom cluster is self-hosted. Keeping images in Gitea (also
> self-hosted on CoulombCore) avoids any external registry dependency and
> keeps image pulls within the cluster network. GHCR is a future option
> once public distribution is needed.
## Design
### Image naming & tagging
| Trigger | Tags applied |
|---------|-------------|
| push to `main` | `latest`, `main-<short-sha>` |
| tag `v1.2.3` | `1.2.3`, `1.2`, `1`, `latest` |
### Build
Multi-stage Dockerfile already present — no changes needed to the build
itself. The image builds to a distroless static binary (~10 MB).
### Registry auth
Gitea issues a personal access token (or machine account token) with
`write:packages` scope. Stored as Gitea Actions secret `REGISTRY_TOKEN`;
username stored as `REGISTRY_USER`.
For local `make push`, credentials are passed via `docker login` before
the push target runs.
### Makefile targets
```makefile
IMAGE_REGISTRY ?= 92.205.130.254:32166
IMAGE_REPO ?= netkingdom/key-cape
IMAGE_TAG ?= latest
IMAGE := $(IMAGE_REGISTRY)/$(IMAGE_REPO):$(IMAGE_TAG)
image:
docker build -t $(IMAGE) .
push: image
docker push $(IMAGE)
image-tag:
docker tag $(IMAGE) $(IMAGE_REGISTRY)/$(IMAGE_REPO):$(IMAGE_TAG)
```
### Gitea Actions workflow
`.gitea/workflows/image.yaml` — triggers on push to `main` and on `v*` tags:
- Checkout
- Set up Docker Buildx
- Login to `92.205.130.254:32166` using secrets
- Build and push with metadata-action tags
- (Optional) sign with cosign if available
### k3s insecure registry
Gitea runs over plain HTTP on port 32166 (NodePort). k3s must be configured
to treat this endpoint as an insecure registry so image pulls work from
within the cluster:
```yaml
# /etc/rancher/k3s/registries.yaml (on CoulombCore)
mirrors:
"92.205.130.254:32166":
endpoint:
- "http://92.205.130.254:32166"
```
k3s picks this up on restart (or SIGHUP). Worker nodes (if any) need the
same file.
---
## Tasks
### T01 — Makefile: image, push, image-tag targets
```task
id: KEY-WP-0002-T01
status: todo
priority: high
state_hub_task_id: "749472fc-edb9-4948-9ebc-58d5f38327ee"
```
Add `image`, `push`, and `image-tag` targets to `Makefile` with
`IMAGE_REGISTRY`, `IMAGE_REPO`, `IMAGE_TAG` variables defaulting to the
Gitea endpoint and `netkingdom/key-cape:latest`.
Gate: `make image` builds successfully locally; `IMAGE_TAG=dev make image`
produces a differently-tagged image.
---
### T02 — Gitea Actions workflow
```task
id: KEY-WP-0002-T02
status: todo
priority: high
state_hub_task_id: "8ecf18cc-a3bb-4ede-a09c-fcd0d26d7f9d"
```
Create `.gitea/workflows/image.yaml`:
- Trigger: `push` to `main`, `push` tags matching `v*`
- Runner: `act_runner` label (or `ubuntu-latest` if configured)
- Steps: checkout → docker buildx → login → build+push
- Tags via `docker/metadata-action`: `latest` on main, semver on tags
Secrets required (document in README.md under "CI"):
- `REGISTRY_USER` — Gitea username or machine account
- `REGISTRY_TOKEN` — Gitea personal access token with `write:packages`
Gate: workflow file is syntactically valid; documented in README.
---
### T03 — k3s insecure registry config on CoulombCore
```task
id: KEY-WP-0002-T03
status: todo
priority: high
state_hub_task_id: "2dde67f9-944f-418d-a2e9-7367bc556425"
```
On CoulombCore, create/update `/etc/rancher/k3s/registries.yaml` to add
the Gitea NodePort as an HTTP mirror. Restart k3s (or send SIGHUP) and
verify `crictl pull 92.205.130.254:32166/netkingdom/key-cape:latest` works.
Gate: image pull from within the cluster succeeds without TLS errors.
---
### T04 — Create Gitea machine account & token
```task
id: KEY-WP-0002-T04
status: todo
priority: medium
state_hub_task_id: "25775e10-3164-4adb-9c41-835c86fde5f8"
```
In Gitea (http://92.205.130.254:32166), create a machine account
`ci-netkingdom` (or reuse an existing service account) with access to
the `netkingdom` organisation. Generate a token with `write:packages`
scope and store it in:
- Gitea Actions secrets on the `key-cape` repo: `REGISTRY_USER`, `REGISTRY_TOKEN`
- The net-kingdom credential store (SOPS-encrypted) under
`credentials/gitea-ci-token.enc.yaml`
Gate: `docker login 92.205.130.254:32166` succeeds with the token;
secret is in the credential store.
---
### T05 — Smoke test: push and pull a dev image
```task
id: KEY-WP-0002-T05
status: todo
priority: medium
state_hub_task_id: "0f6ab38f-6d34-41af-9180-f19c687947b5"
```
Manually trigger a build-and-push:
```bash
docker login 92.205.130.254:32166
IMAGE_TAG=dev make push
```
Then verify the image is pullable from CoulombCore:
```bash
# on CoulombCore
crictl pull 92.205.130.254:32166/netkingdom/key-cape:dev
```
Gate: pull succeeds; image is listed in Gitea → Packages → netkingdom/key-cape.
---
### T06 — Update README with registry & CI docs
```task
id: KEY-WP-0002-T06
status: todo
priority: low
state_hub_task_id: "946cd34d-94da-4fa9-a781-ed36f6c827a3"
```
Add a "Container Image" section to `README.md` documenting:
- Registry URL and image name
- How to pull (`docker pull 92.205.130.254:32166/netkingdom/key-cape:latest`)
- How to build and push locally (Makefile targets)
- CI secrets required for the Actions workflow
Gate: README section present and accurate.
---
## Done Criteria
- [ ] `make image` and `make push` work locally
- [ ] `.gitea/workflows/image.yaml` present and documented
- [ ] k3s can pull the image from Gitea without TLS errors
- [ ] Machine account token stored in credential store
- [ ] Smoke test: `dev` image pushed and pulled successfully
- [ ] README updated