26 KiB
CD Pipeline Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build a GitOps CD pipeline that automatically builds a container image on main push and deploys it to k3s on koala via Flux.
Architecture: BuildKit runs as a systemd service on koala (same host as the Gitea runner); CD pushes images to the Gitea registry and commits image tag updates to the infra repo; Flux reconciles within 60s. App secrets (including ANTHROPIC_API_KEY) are SOPS-encrypted in the infra repo and decrypted by Flux at apply time.
Tech Stack: Go 1.26, Node.js 22 (for claude CLI), BuildKit (buildctl), Gitea Actions, Flux (kustomize-controller), SOPS + age, k3s/containerd
Environment context
This plan spans three environments. Each task header notes which environment it runs in:
- [this-repo] —
/Users/mathias/Documents/local-dev/AI/supervisoron flamingo - [koala-ssh] —
ssh koala(run commands viassh koala "...") - [infra-repo] —
gitea.d-ma.be/mathias/infra(clone to a temp dir, work there, push) - [gitea-ui] — Gitea web UI at
https://gitea.d-ma.be - [kubectl] — kubectl from flamingo (home LAN)
File map
This repo (supervisor):
- Create:
Dockerfile - Create:
.gitea/workflows/cd.yml
koala host:
- Create:
/etc/systemd/system/buildkitd.service(or user-level equivalent) - Create:
/root/.config/buildkit/buildkitd.toml(registry auth config)
Infra repo (gitea.d-ma.be/mathias/infra):
- Create:
apps/supervisor/namespace.yaml - Create:
apps/supervisor/deployment.yaml - Create:
apps/supervisor/service.yaml - Create:
apps/supervisor/secrets.enc.yaml(SOPS-encrypted) - Create:
apps/supervisor/kustomization.yaml - Create:
apps/imagepullsecret/secret.enc.yaml(SOPS-encrypted) - Create:
apps/imagepullsecret/kustomization.yaml - Modify:
clusters/koala/kustomization.yaml(add supervisor + imagepullsecret) - Modify:
flux-system/kustomization.yamlor relevant Flux Kustomization CRD (add SOPS decryption)
Task 1: Dockerfile [this-repo]
The supervisor binary depends on the claude CLI as a subprocess. The image uses a multi-stage build: Go builder stage compiles the binary; the runtime stage is Node.js (for npm install -g @anthropic-ai/claude-code). Config files are baked in. The brain/ directory is a volume mount.
Files:
-
Create:
Dockerfile -
Step 1: Verify no Dockerfile exists
ls Dockerfile 2>/dev/null || echo "confirmed: no Dockerfile"
Expected: confirmed: no Dockerfile
- Step 2: Create the Dockerfile
# syntax=docker/dockerfile:1
# ── Build stage ───────────────────────────────────────────────────────────────
FROM golang:1.26-bookworm AS builder
ARG VERSION=dev
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" \
-o /out/supervisor ./cmd/supervisor
# ── Runtime stage ─────────────────────────────────────────────────────────────
# Node.js 22 slim — needed for claude CLI subprocess
FROM node:22-slim
# Install claude CLI (provides the `claude` binary the supervisor shells out to)
RUN npm install -g @anthropic-ai/claude-code \
&& claude --version \
&& echo "claude CLI installed"
# Copy supervisor binary
COPY --from=builder /out/supervisor /usr/local/bin/supervisor
# Bake in config (models.yaml + skill discipline files)
COPY config/ /app/config/
WORKDIR /app
# brain/ is writable state — mount a PersistentVolume here
VOLUME /app/brain
ENV SUPERVISOR_CONFIG_DIR=/app/config/supervisor
ENV SUPERVISOR_MODELS_FILE=/app/config/models.yaml
ENV SUPERVISOR_BRAIN_DIR=/app/brain
ENV SUPERVISOR_SESSIONS_DIR=/app/brain/sessions
ENV SUPERVISOR_PORT=3200
EXPOSE 3200
ENTRYPOINT ["/usr/local/bin/supervisor"]
- Step 3: Build locally to verify it compiles (no push)
# buildctl must be available locally, OR use docker if available on flamingo
docker build --target builder -t supervisor-build-test . && echo "build stage OK"
# If no docker on flamingo, skip this step and verify at Task 3 on koala instead
- Step 4: Commit
git add Dockerfile
git commit -m "feat: add multi-stage Dockerfile with claude CLI runtime"
Task 2: BuildKit systemd service on koala [koala-ssh]
Install buildkitd as a root-level systemd service on koala. The Gitea runner process runs as root (confirmed by PID/cgroup), so the root socket at /run/buildkit/buildkitd.sock is accessible to it.
Files:
-
Create:
/etc/systemd/system/buildkitd.serviceon koala -
Create:
/etc/buildkit/buildkitd.tomlon koala (registry auth) -
Step 1: Check if buildkitd is already installed
ssh koala "buildkitd --version 2>/dev/null || echo 'not installed'"
- Step 2: Install buildkitd on koala
Download the latest buildkit release binary (arm64 or amd64 — koala has x86_64):
ssh koala "
BUILDKIT_VERSION=v0.21.0
curl -sSL https://github.com/moby/buildkit/releases/download/\${BUILDKIT_VERSION}/buildkit-\${BUILDKIT_VERSION}.linux-amd64.tar.gz \
| tar -xz -C /usr/local/
buildkitd --version
"
Expected output includes: buildkitd github.com/moby/buildkit v0.21.0
- Step 3: Create buildkitd.toml with Gitea registry auth
The [registry] block configures auth for pushing to gitea.d-ma.be. The actual credentials come from ~/.docker/config.json (which buildkitd reads automatically) — this toml just enables the registry:
ssh koala "
mkdir -p /etc/buildkit
cat > /etc/buildkit/buildkitd.toml << 'EOF'
[worker.containerd]
enabled = false
[worker.oci]
enabled = true
[registry.\"gitea.d-ma.be\"]
http = false
insecure = false
EOF
"
- Step 4: Create systemd unit
ssh koala "
cat > /etc/systemd/system/buildkitd.service << 'EOF'
[Unit]
Description=BuildKit daemon
After=network.target
[Service]
Type=notify
ExecStart=/usr/local/bin/buildkitd --config /etc/buildkit/buildkitd.toml
Restart=on-failure
LimitNOFILE=1048576
LimitNPROC=1048576
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable buildkitd
systemctl start buildkitd
"
- Step 5: Verify the socket exists and is responsive
ssh koala "
systemctl status buildkitd --no-pager
buildctl --addr unix:///run/buildkit/buildkitd.sock debug info
"
Expected: service active (running), buildctl shows BuildKit version info.
- Step 6: Smoke-test build with trivial Dockerfile
ssh koala "
echo 'FROM alpine:3.21
RUN echo hello' | buildctl --addr unix:///run/buildkit/buildkitd.sock build \
--frontend dockerfile.v0 \
--local context=/ \
--opt filename=Dockerfile \
--output type=image,name=localhost/smoke-test:latest
echo 'smoke test OK'
"
Expected: smoke test OK
Task 3: Gitea registry push auth for buildctl [koala-ssh]
buildctl reads Docker-style credentials from /root/.docker/config.json. Create the credentials file so the runner can push to gitea.d-ma.be.
Prerequisites: A Gitea user token or password with write:packages scope for the mathias org. Create one in Gitea → User Settings → Applications → Generate Token (scopes: write:packages).
- Step 1: Create Gitea access token
In Gitea UI (https://gitea.d-ma.be) → top-right avatar → Settings → Applications → Generate New Token.
-
Token name:
buildkit-push -
Scopes:
write:packages(container registry write) -
Copy the token — it won't be shown again.
-
Step 2: Write docker config.json on koala
Replace <TOKEN> with the token from Step 1:
ssh koala "
mkdir -p /root/.docker
TOKEN=<TOKEN>
AUTH=\$(echo -n 'mathias:'\${TOKEN} | base64)
cat > /root/.docker/config.json << EOF
{
\"auths\": {
\"gitea.d-ma.be\": {
\"auth\": \"\${AUTH}\"
}
}
}
EOF
chmod 600 /root/.docker/config.json
echo 'credentials written'
"
- Step 3: Verify push works
ssh koala "
echo 'FROM alpine:3.21' | buildctl --addr unix:///run/buildkit/buildkitd.sock build \
--frontend dockerfile.v0 \
--local context=/ \
--opt filename=Dockerfile \
--output type=image,name=gitea.d-ma.be/mathias/supervisor:push-test,push=true
echo 'push OK'
"
Expected: push OK. Verify in Gitea UI: https://gitea.d-ma.be/mathias/supervisor/packages should show a push-test tag.
- Step 4: Delete the test image tag
In Gitea UI → supervisor repo → Packages tab → delete the push-test tag.
Task 4: age keypair + Flux SOPS decryption [kubectl + flamingo]
Flux decrypts SOPS-encrypted secrets at apply time. It needs the age private key stored as a k8s Secret in the flux-system namespace.
- Step 1: Verify age is installed
age --version || brew install age
- Step 2: Generate age keypair
age-keygen -o /tmp/supervisor-age.key
cat /tmp/supervisor-age.key
Output includes two lines:
# public key: age1xxxxxx...
AGE-SECRET-KEY-1xxxxxxx...
Copy the public key (the age1... value) — you'll need it in Task 7 for encrypting secrets.
Store the private key file securely — back it up outside the cluster (e.g., 1Password or encrypted note).
- Step 3: Create the SOPS age secret in flux-system
kubectl create secret generic sops-age \
--from-file=age.agekey=/tmp/supervisor-age.key \
-n flux-system
kubectl get secret sops-age -n flux-system
Expected: secret exists with age.agekey key.
- Step 4: Shred the temp key file
shred -u /tmp/supervisor-age.key
- Step 5: Check what Flux Kustomization CRDs exist in the infra repo
git clone git@gitea.d-ma.be:mathias/infra.git /tmp/infra-sops-setup
ls /tmp/infra-sops-setup/flux-system/
Look for a kustomization.yaml or gotk-sync.yaml that defines the main Flux Kustomization resource pointing at the clusters/koala/ path.
- Step 6: Patch the Flux Kustomization to enable SOPS decryption
Find the Kustomization resource that syncs clusters/koala/. It will look like:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: flux-system
namespace: flux-system
spec:
path: ./clusters/koala
...
Add the decryption block:
decryption:
provider: sops
secretRef:
name: sops-age
Edit the file in /tmp/infra-sops-setup/flux-system/ and commit:
cd /tmp/infra-sops-setup
# Edit the relevant Kustomization yaml to add decryption block (shown above)
git add flux-system/
git commit -m "feat: enable SOPS decryption via age key in flux-system"
git push
- Step 7: Verify Flux picks up the change
flux reconcile source git flux-system
flux get kustomizations
Expected: flux-system Kustomization shows Ready True with no errors.
- Step 8: Clean up temp clone
rm -rf /tmp/infra-sops-setup
Task 5: Infra repo — supervisor app manifests [infra-repo]
Create the full k8s manifest set for the supervisor service in the infra repo. The deployment uses an IMAGE_TAG placeholder; the CD job patches this with the actual git sha before pushing.
Prerequisites: age public key from Task 4 Step 2.
- Step 1: Clone the infra repo
git clone git@gitea.d-ma.be:mathias/infra.git /tmp/infra-supervisor
cd /tmp/infra-supervisor
- Step 2: Create namespace
mkdir -p apps/supervisor
cat > apps/supervisor/namespace.yaml << 'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: supervisor
EOF
- Step 3: Create deployment
The brain volume is a hostPath on koala (simplest for a single-node service; add a PVC later if needed). The image uses imagePullSecrets to pull from the Gitea registry.
cat > apps/supervisor/deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: supervisor
namespace: supervisor
spec:
replicas: 1
selector:
matchLabels:
app: supervisor
template:
metadata:
labels:
app: supervisor
spec:
nodeSelector:
kubernetes.io/hostname: koala
imagePullSecrets:
- name: gitea-registry
containers:
- name: supervisor
image: gitea.d-ma.be/mathias/supervisor:IMAGE_TAG
ports:
- containerPort: 3200
envFrom:
- secretRef:
name: supervisor-secrets
env:
- name: SUPERVISOR_PORT
value: "3200"
- name: LITELLM_BASE_URL
value: "http://iguana:4000"
- name: LLAMA_SWAP_URL
value: "http://koala:8080"
- name: INGEST_BASE_URL
value: "http://localhost:3300"
volumeMounts:
- name: brain
mountPath: /app/brain
volumes:
- name: brain
hostPath:
path: /var/lib/supervisor/brain
type: DirectoryOrCreate
EOF
- Step 4: Create service
cat > apps/supervisor/service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: supervisor
namespace: supervisor
spec:
selector:
app: supervisor
ports:
- port: 3200
targetPort: 3200
type: ClusterIP
EOF
- Step 5: Create kustomization.yaml for supervisor
cat > apps/supervisor/kustomization.yaml << 'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- deployment.yaml
- service.yaml
- secrets.enc.yaml
EOF
- Step 6: Ensure clusters/koala/kustomization.yaml exists and includes supervisor
Check if the file exists:
cat clusters/koala/kustomization.yaml 2>/dev/null || echo "need to create"
If it exists, add supervisor and imagepullsecret resources. If it does not exist, create it:
cat > clusters/koala/kustomization.yaml << 'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../apps/imagepullsecret
- ../../apps/supervisor
EOF
If it already exists, add the two resource lines (preserving existing entries).
- Step 7: Commit (without secrets — those come in Task 6)
cd /tmp/infra-supervisor
git add apps/supervisor/ clusters/koala/
git commit -m "feat(supervisor): add k8s manifests for supervisor service"
git push
Task 6: SOPS-encrypted secrets in infra repo [infra-repo + flamingo]
Two encrypted secret files: the imagePullSecret for the Gitea container registry, and the supervisor app secrets (ANTHROPIC_API_KEY, LITELLM_API_KEY).
Prerequisites:
-
age public key from Task 4 Step 2 (format:
age1xxxxx...) -
sopsinstalled (brew install sopsif missing) -
Gitea registry token (same one used in Task 3, or create a read-only one for pulling)
-
Step 1: Verify sops is installed
sops --version || brew install sops
- Step 2: Create .sops.yaml in infra repo root
This tells sops which key to use for all files in the repo:
cd /tmp/infra-supervisor
cat > .sops.yaml << 'EOF'
creation_rules:
- age: age1REPLACE_WITH_YOUR_PUBLIC_KEY
EOF
git add .sops.yaml
git commit -m "chore: add sops config (age key)"
git push
Replace age1REPLACE_WITH_YOUR_PUBLIC_KEY with the actual age public key from Task 4.
- Step 3: Create and encrypt the imagePullSecret
The imagePullSecret is a namespace-less Secret (it will be targeted per namespace via Kustomize). Create it in the imagepullsecret app:
mkdir -p apps/imagepullsecret
# Create a registry pull token in Gitea: Settings → Applications → Generate Token
# Scopes: read:packages
# Use that token here (or reuse the buildkit-push token — read access is enough for pulling)
PULL_TOKEN=<gitea-read-packages-token>
PULL_AUTH=$(echo -n "mathias:${PULL_TOKEN}" | base64)
cat > /tmp/gitea-pull-secret.yaml << EOF
apiVersion: v1
kind: Secret
metadata:
name: gitea-registry
namespace: supervisor
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"gitea.d-ma.be": {
"auth": "${PULL_AUTH}"
}
}
}
EOF
sops --encrypt /tmp/gitea-pull-secret.yaml > apps/imagepullsecret/secret.enc.yaml
rm /tmp/gitea-pull-secret.yaml
cat > apps/imagepullsecret/kustomization.yaml << 'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secret.enc.yaml
EOF
Verify the encrypted file looks correct (should show sops: metadata at the bottom):
tail -20 apps/imagepullsecret/secret.enc.yaml
- Step 4: Create and encrypt supervisor app secrets
# ANTHROPIC_API_KEY: your Anthropic API key
# LITELLM_API_KEY: the key your LiteLLM instance expects (can be any string if it's local)
cat > /tmp/supervisor-secrets.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: supervisor-secrets
namespace: supervisor
type: Opaque
stringData:
ANTHROPIC_API_KEY: "REPLACE_WITH_REAL_KEY"
LITELLM_API_KEY: "REPLACE_WITH_REAL_KEY"
EOF
# Edit /tmp/supervisor-secrets.yaml to insert real values, then:
sops --encrypt /tmp/supervisor-secrets.yaml > apps/supervisor/secrets.enc.yaml
rm /tmp/supervisor-secrets.yaml
Verify:
tail -20 apps/supervisor/secrets.enc.yaml
# Should show encrypted values and sops metadata
- Step 5: Commit encrypted secrets
cd /tmp/infra-supervisor
git add apps/imagepullsecret/ apps/supervisor/secrets.enc.yaml .sops.yaml
git commit -m "feat: add SOPS-encrypted imagePullSecret and supervisor app secrets"
git push
- Step 6: Verify Flux reconciles and creates the secrets
Wait ~60s then:
flux reconcile kustomization flux-system --with-source
kubectl get secrets -n supervisor
Expected: gitea-registry and supervisor-secrets appear in the supervisor namespace.
- Step 7: Clean up temp clone
rm -rf /tmp/infra-supervisor
Task 7: Gitea org-level secrets [gitea-ui + koala-ssh]
Set the three secrets that all repos in the mathias org will inherit. These go in the Gitea org (not individual repos).
Files: No files — Gitea UI configuration.
- Step 1: Generate SSH deploy key for infra repo
On flamingo:
ssh-keygen -t ed25519 -C "cd-bot infra deploy key" -f /tmp/infra-deploy-key -N ""
cat /tmp/infra-deploy-key # private key → INFRA_DEPLOY_KEY secret
cat /tmp/infra-deploy-key.pub # public key → add to Gitea infra repo as deploy key
- Step 2: Add public key to infra repo as a deploy key (write access)
In Gitea UI: https://gitea.d-ma.be/mathias/infra → Settings → Deploy Keys → Add Deploy Key.
-
Title:
cd-bot -
Key: paste content of
/tmp/infra-deploy-key.pub -
Enable write access: ✓
-
Step 3: Set org-level secrets in Gitea
In Gitea UI: https://gitea.d-ma.be/org/mathias/settings/secrets → Add Secret.
Set these three secrets:
| Secret name | Value |
|---|---|
INFRA_DEPLOY_KEY |
content of /tmp/infra-deploy-key (private key, including -----BEGIN... lines) |
BUILDKIT_REGISTRY_AUTH |
same base64 auth string as used in Task 3 Step 2 (format: mathias:<token> base64-encoded) |
Note: BUILDKIT_REGISTRY_AUTH is redundant if /root/.docker/config.json is already on the runner host from Task 3 — but setting it as a secret allows the cd.yml to explicitly pass it to buildctl for clarity and rotation.
- Step 4: Clean up temp key files
shred -u /tmp/infra-deploy-key /tmp/infra-deploy-key.pub
- Step 5: Verify secrets appear in Gitea
In Gitea UI: https://gitea.d-ma.be/org/mathias/settings/secrets — confirm both secrets are listed (values are hidden, only names shown).
Task 8: cd.yml workflow [this-repo]
Create the CD workflow that triggers after CI passes, builds the image with buildctl, and commits the updated tag to the infra repo.
Files:
-
Create:
.gitea/workflows/cd.yml -
Step 1: Create cd.yml
cat > .gitea/workflows/cd.yml << 'EOF'
name: cd
on:
push:
branches: [main]
jobs:
deploy:
name: Build and deploy
needs: [check] # 'check' is the job name in ci.yml
runs-on: self-hosted
env:
SERVICE: supervisor
REGISTRY: gitea.d-ma.be
IMAGE: gitea.d-ma.be/mathias/supervisor
INFRA_REPO: git@gitea.d-ma.be:mathias/infra.git
BUILDKIT_HOST: unix:///run/buildkit/buildkitd.sock
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build and push image
run: |
IMAGE_TAG="${{ github.sha }}"
echo "Building ${IMAGE}:${IMAGE_TAG}"
buildctl --addr "${BUILDKIT_HOST}" build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--opt build-arg:VERSION="${IMAGE_TAG}" \
--output "type=image,name=${IMAGE}:${IMAGE_TAG},push=true"
echo "IMAGE_TAG=${IMAGE_TAG}" >> $GITHUB_OUTPUT
id: build
- name: Update infra repo
run: |
IMAGE_TAG="${{ github.sha }}"
# Write SSH key for infra repo
mkdir -p ~/.ssh
echo "${{ secrets.INFRA_DEPLOY_KEY }}" > ~/.ssh/infra_deploy_key
chmod 600 ~/.ssh/infra_deploy_key
ssh-keyscan gitea.d-ma.be >> ~/.ssh/known_hosts 2>/dev/null
# Clone infra repo
GIT_SSH_COMMAND="ssh -i ~/.ssh/infra_deploy_key -o IdentitiesOnly=yes" \
git clone "${INFRA_REPO}" /tmp/infra-update
# Patch the image tag
cd /tmp/infra-update
sed -i "s|gitea.d-ma.be/mathias/supervisor:.*|gitea.d-ma.be/mathias/supervisor:${IMAGE_TAG}|" \
"apps/${SERVICE}/deployment.yaml"
# Commit and push
git config user.email "cd-bot@d-ma.be"
git config user.name "CD Bot"
git add "apps/${SERVICE}/deployment.yaml"
git commit -m "chore(deploy): ${SERVICE} → ${IMAGE_TAG}"
GIT_SSH_COMMAND="ssh -i ~/.ssh/infra_deploy_key -o IdentitiesOnly=yes" \
git push
# Clean up
rm -rf /tmp/infra-update
rm ~/.ssh/infra_deploy_key
echo "Infra repo updated: ${SERVICE} → ${IMAGE_TAG}"
EOF
- Step 2: Verify the
needsjob name matches ci.yml
grep "^ [a-z].*:$" .gitea/workflows/ci.yml
The output should show check: as the quality-gate job name. The cd.yml uses needs: [check] — confirm this matches.
- Step 3: Commit
git add .gitea/workflows/cd.yml
git commit -m "feat: add CD workflow (buildctl → Gitea registry → infra repo update)"
Task 9: End-to-end smoke test
Trigger the full pipeline and verify each stage.
- Step 1: Push to main to trigger CI + CD
git push origin main
- Step 2: Monitor CI job in Gitea
Open https://gitea.d-ma.be/mathias/supervisor/actions — wait for the ci workflow check job to pass.
- Step 3: Monitor CD job
In the same actions view, the cd workflow should start after ci passes. Check the Build and push image step output for:
Building gitea.d-ma.be/mathias/supervisor:<sha>
And the Update infra repo step for:
Infra repo updated: supervisor → <sha>
- Step 4: Verify image in Gitea registry
https://gitea.d-ma.be/mathias/supervisor/packages
Should show a new tag matching the commit sha.
- Step 5: Verify infra repo commit
git clone git@gitea.d-ma.be:mathias/infra.git /tmp/infra-verify
cd /tmp/infra-verify
git log --oneline -3
Expected: most recent commit message is chore(deploy): supervisor → <sha>.
grep "image:" apps/supervisor/deployment.yaml
Expected: image: gitea.d-ma.be/mathias/supervisor:<sha>
- Step 6: Verify Flux reconciles
flux get kustomizations
Expected: flux-system shows Ready True and Applied revision: main/<infra-sha>.
kubectl get pods -n supervisor
Expected: supervisor pod is Running with the new image sha.
- Step 7: Verify pod started correctly
kubectl logs -n supervisor deployment/supervisor --tail=20
Expected: supervisor startup logs (MCP server listening on port 3200, no errors).
- Step 8: Clean up verify clone
rm -rf /tmp/infra-verify
Task 10: Post-deploy — registry retention policy [gitea-ui]
Prevent the Gitea container registry from filling up by setting a tag retention policy.
- Step 1: Set tag retention in Gitea
In Gitea UI: https://gitea.d-ma.be/mathias/supervisor → Settings → Packages → Container Registry.
Set: Keep last 20 tags per image name.
If Gitea does not expose a UI retention policy, note this for manual cleanup and open a task to automate it (e.g., a weekly Actions job that calls docker image prune via the Gitea API).
- Step 2: Verify existing test tags are cleaned up
Manually delete any test tags pushed during Task 3 if not already done.
Self-review checklist (for plan author — not a task)
- Spec coverage: BuildKit systemd ✓, cd.yml ✓, Flux SOPS ✓, infra repo structure ✓, imagePullSecret ✓, app secrets ✓, Gitea org secrets ✓, error handling (implicit in workflow failures) ✓, registry retention ✓, smoke test ✓
- Placeholders:
REPLACE_WITH_YOUR_PUBLIC_KEYandREPLACE_WITH_REAL_KEYare intentional — real values come from user's secrets; marked clearly - Type consistency: No shared types across tasks (infra-only plan)
- Known gaps:
needs: [check]assumes ci.yml job name ischeck— verified in Task 8 Step 2. Thesedimage tag patch assumes no other image line in deployment.yaml — the deployment template only has oneimage:line.