Deploy opencode server to K3s via MetalLB and GitOps workflow #53

Open
opened 2026-08-22 20:52:16 +00:00 by lab · 0 comments
Owner

Deploy the remote OpenCode server to K3s as raw manifests plus the standard
Forgejo GitOps workflow. Requires the image from the container-image issue.

Design summary

iPhone (Tailscale on)
   |  tailnet
   v
Router gl-be6500 (subnet router, advertises 192.168.0.0/24 - already live)
   |
   v
MetalLB 192.168.0.225 :4096  -- basic auth (opencode / password)
   |
Service -> Pod `opencode` (namespace: opencode, Longhorn-capable node)
   |-- PVC `opencode-data` (Longhorn, ~10Gi)
   |     |-- subPath workspace -> /workspace          (cloned repos)
   |     `-- subPath state    -> ~/.local/share/opencode (sessions, LLM auth)
   `-- Image git.yukselcloud.com/lab/opencode:v1 (Forgejo registry)

Decisions already made:

  • Access is VPN-only over the existing router Tailscale subnet route. No
    Caddy route, no Authelia, no WAN redirect.
  • Raw manifests (caddy-style), not Helm — no upstream chart worth trusting
    for an agentic tool holding credentials.
  • Default ServiceAccount, no RBAC — the pod cannot touch the cluster API.
  • Secret values follow the existing repo convention (committed secret.yaml,
    private repo).

Tasks

  • k3s-ha-cluster/opencode/namespace.yaml — namespace opencode

  • k3s-ha-cluster/opencode/secret.yaml — stringData:
    OPENCODE_SERVER_PASSWORD, OPENCODE_SERVER_USERNAME=opencode,
    GIT_TOKEN (Forgejo credential for lab with repo write on
    lab/homelab)

  • k3s-ha-cluster/opencode/pvc.yaml — Longhorn PVC opencode-data,
    10Gi, ReadWriteOnce

  • k3s-ha-cluster/opencode/service.yaml — LoadBalancer,
    loadBalancerIP: 192.168.0.225, port 4096

  • k3s-ha-cluster/opencode/deployment.yaml:

    - 1 replica, image `git.yukselcloud.com/lab/opencode:v1`
    - envFrom secretRef (web password, username, GIT_TOKEN)
    - fsGroup 1000; two mounts of the one PVC via subPaths (`workspace`
      at `/workspace`, `state` at `/home/opencode/.local/share/opencode`)
    - startup command, all idempotent, then exec:
    
      ```sh
      git config --global credential.helper store
      printf 'https://lab:%s@git.yukselcloud.com\n' "$GIT_TOKEN" \
        > "$HOME/.git-credentials"
      [ -d /workspace/homelab/.git ] || \
        git clone https://git.yukselcloud.com/lab/homelab.git /workspace/homelab
      exec opencode web --hostname 0.0.0.0 --port 4096
      ```
    
    - liveness/readiness probes on `GET /global/health`, port 4096
    - modest resources (e.g. requests 100m/256Mi, limits 1/1Gi)
    
  • Verify node image-pull path for the private registry: confirm
    /etc/rancher/k3s/registries.yaml on workers already authenticates to
    git.yukselcloud.com (the forgejo-runner image comes from there), else
    add imagePullSecrets.

  • .forgejo/workflows/deploy-opencode.yaml — standard template:
    paths: k3s-ha-cluster/opencode/**, step
    kubectl -n opencode apply -f k3s-ha-cluster/opencode/. Keep
    docker/ free of YAML so a plain dir apply stays safe.

Acceptance criteria

  • Pod Running; service gets EXTERNAL-IP 192.168.0.225
  • From the laptop:
    curl -u opencode:<pw> http://192.168.0.225:4096/global/health returns
    healthy: true
  • kubectl -n opencode rollout restart deploy/opencode keeps prior sessions
    and cloned repos (PVC persistence works)
  • Pushing to the repo triggers the workflow and re-applies cleanly

Depends on #52.

Deploy the remote OpenCode server to K3s as raw manifests plus the standard Forgejo GitOps workflow. Requires the image from the container-image issue. ## Design summary ```text iPhone (Tailscale on) | tailnet v Router gl-be6500 (subnet router, advertises 192.168.0.0/24 - already live) | v MetalLB 192.168.0.225 :4096 -- basic auth (opencode / password) | Service -> Pod `opencode` (namespace: opencode, Longhorn-capable node) |-- PVC `opencode-data` (Longhorn, ~10Gi) | |-- subPath workspace -> /workspace (cloned repos) | `-- subPath state -> ~/.local/share/opencode (sessions, LLM auth) `-- Image git.yukselcloud.com/lab/opencode:v1 (Forgejo registry) ``` Decisions already made: - Access is VPN-only over the existing router Tailscale subnet route. No Caddy route, no Authelia, no WAN redirect. - Raw manifests (caddy-style), not Helm — no upstream chart worth trusting for an agentic tool holding credentials. - Default ServiceAccount, no RBAC — the pod cannot touch the cluster API. - Secret values follow the existing repo convention (committed secret.yaml, private repo). ## Tasks - [ ] `k3s-ha-cluster/opencode/namespace.yaml` — namespace `opencode` - [ ] `k3s-ha-cluster/opencode/secret.yaml` — stringData: `OPENCODE_SERVER_PASSWORD`, `OPENCODE_SERVER_USERNAME=opencode`, `GIT_TOKEN` (Forgejo credential for `lab` with repo write on `lab/homelab`) - [ ] `k3s-ha-cluster/opencode/pvc.yaml` — Longhorn PVC `opencode-data`, 10Gi, ReadWriteOnce - [ ] `k3s-ha-cluster/opencode/service.yaml` — LoadBalancer, `loadBalancerIP: 192.168.0.225`, port 4096 - [ ] `k3s-ha-cluster/opencode/deployment.yaml`: - 1 replica, image `git.yukselcloud.com/lab/opencode:v1` - envFrom secretRef (web password, username, GIT_TOKEN) - fsGroup 1000; two mounts of the one PVC via subPaths (`workspace` at `/workspace`, `state` at `/home/opencode/.local/share/opencode`) - startup command, all idempotent, then exec: ```sh git config --global credential.helper store printf 'https://lab:%s@git.yukselcloud.com\n' "$GIT_TOKEN" \ > "$HOME/.git-credentials" [ -d /workspace/homelab/.git ] || \ git clone https://git.yukselcloud.com/lab/homelab.git /workspace/homelab exec opencode web --hostname 0.0.0.0 --port 4096 ``` - liveness/readiness probes on `GET /global/health`, port 4096 - modest resources (e.g. requests 100m/256Mi, limits 1/1Gi) - [ ] Verify node image-pull path for the private registry: confirm `/etc/rancher/k3s/registries.yaml` on workers already authenticates to `git.yukselcloud.com` (the forgejo-runner image comes from there), else add `imagePullSecrets`. - [ ] `.forgejo/workflows/deploy-opencode.yaml` — standard template: `paths: k3s-ha-cluster/opencode/**`, step `kubectl -n opencode apply -f k3s-ha-cluster/opencode/`. Keep `docker/` free of YAML so a plain dir apply stays safe. ## Acceptance criteria - Pod Running; service gets EXTERNAL-IP `192.168.0.225` - From the laptop: `curl -u opencode:<pw> http://192.168.0.225:4096/global/health` returns `healthy: true` - `kubectl -n opencode rollout restart deploy/opencode` keeps prior sessions and cloned repos (PVC persistence works) - Pushing to the repo triggers the workflow and re-applies cleanly Depends on #52.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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
lab/homelab#53
No description provided.