0010: Vibe Kanban POC — Self-Hosted Deployment

Summary

Deploy Vibe Kanban Cloud to k8s-lab as a self-hosted kanban board for AI development workflows. The component lives in the ai-dev repo but deploys to the code-server namespace, following the established OpenClaw cross-namespace pattern.

Problem Statement

The AI development environment needs a kanban board for tracking work. Vibe Kanban is an AI-native project management tool that integrates with coding agents. We want to self-host it on k8s-lab and make it accessible alongside the existing code-server and OpenCode tooling.

Goals

  • Deploy a working Vibe Kanban Cloud instance on k8s-lab
  • Accessible via public ingress at vibe-kanban.lab.ctoaas.co
  • Authenticate users via Google OAuth (ingress gate) + GitHub OAuth (VK identity)
  • Mount code-server-storage PVC read-write for workspace/worktree operations (core functionality — VK creates git worktrees on disk to isolate task work)
  • Follow existing deployment patterns (Kustomize component, ArgoCD sync, ESO secrets)

Non-Goals

  • Relay/tunnel server (not needed — web UI accessed directly)
  • Azurite/blob storage for attachments (can add later)
  • Multi-user team features (single-user POC)

Architecture Findings

Vibe Kanban Cloud Stack

Vibe Kanban Cloud is a 3-service stack:

ServiceImagePortPurpose
remote-serverBuilt from crates/remote/Dockerfile8081Rust binary serving web UI + REST API
remote-dbpostgres:16-alpine5432PostgreSQL with wal_level=logical
electricelectricsql/electric:1.3.33000ElectricSQL real-time sync

Key facts:

  • State is stored in PostgreSQL (not markdown/files)
  • OAuth is mandatory — no single-user or password mode exists
  • No pre-built Docker images — build from source via image-factory (crates/remote/Dockerfile), pinned version
  • The remote-server serves the full web UI (React SPA) + API on a single port (8081)
  • Health check: GET http://127.0.0.1:8081/v1/health
  • Build time: 10-15 minutes (Rust compilation)

Workspace & Filesystem Interaction

VK is not a passive kanban board — it actively interacts with the filesystem:

  1. When you create a workspace, VK creates a git worktree on disk (a separate working directory with its own branch)
  2. Worktrees are stored in .vibe-kanban-workspaces/ (configurable)
  3. Coding agents launched by VK read/write files in these worktrees
  4. VK can create PRs, manage branches, and run git operations

This means the code-server-storage PVC must be mounted read-write with appropriate UID/GID permissions. The VK server also needs git credentials (SSH keys or tokens) for remote git operations.

Startup Ordering

ElectricSQL depends on a database user (electric_sync) that the remote-server creates on first startup via migrations. The required startup order is:

  1. PostgreSQL starts first (readiness: pg_isready)
  2. remote-server starts after Postgres is healthy (runs migrations, creates electric_sync user)
  3. ElectricSQL starts after remote-server is healthy (connects as electric_sync)

Source Code

https://github.com/BloopAI/vibe-kanban

Deployment Pattern

Cross-Namespace Pattern (Following OpenClaw)

The component lives in the ai-dev repo at infrastructure/kustomize/components/vibe-kanban/ but deploys to the code-server namespace. This works because:

  1. The component’s kustomization.yaml sets namespace: code-server
  2. Kustomize stamps this namespace on all resources in the component
  3. The ArgoCD ai-dev AppProject allows namespace: '*'
  4. ArgoCD applies the rendered manifests to whichever namespace Kustomize assigned

Reference implementation: infrastructure/kustomize/components/openclaw/ (same pattern).

Authentication Strategy

Dual-layer authentication — both layers serve different purposes:

Ingress layer (access control): Keep the standard traefik Google OAuth middleware (traefik-google-auth@kubernetescrd). This prevents unauthenticated access from the public internet. The Google OAuth cookie is shared across *.lab.ctoaas.co, so you’ll already be authenticated from other services (code-server, etc.) — no extra login prompt.

Application layer (user identity): VK’s built-in GitHub OAuth provides user identity within the app. The code-server namespace already has the gh-oauth secret via ClusterExternalSecret. The callback URL (/v1/oauth/github/callback) should pass through the forwardAuth middleware since you’d already have a valid Google OAuth session cookie.

Important: The existing GitHub OAuth App must have Vibe Kanban’s callback URL added: https://vibe-kanban.lab.ctoaas.co/v1/oauth/github/callback

Note: If VK also supports Google OAuth natively, we could potentially use the same Google credentials for both layers, reducing to a single sign-in. Worth investigating but not required for POC.

Secrets

SecretSourceNotes
GITHUB_CLIENT_IDExisting gh-oauth secret in code-server nsAlready available via ClusterExternalSecret
GITHUB_CLIENT_SECRETExisting gh-oauth secret in code-server nsAlready available via ClusterExternalSecret
VIBEKANBAN_REMOTE_JWT_SECRETNew — add to central-secret-storeGenerate with openssl rand -base64 48
DB_PASSWORDNew — add to central-secret-storeSecure password for Postgres
ELECTRIC_ROLE_PASSWORDNew — add to central-secret-storeMust match between remote-server and electric

New secrets are pulled via an ExternalSecret resource in the component, referencing the central-secret-store ClusterSecretStore (same pattern as OpenClaw’s externalsecret.yaml).

PVC Usage

PVCCreated ByMount ModePurpose
code-server-storagePre-existing (remote-development)Read-writeSource code access for workspace/worktree creation, git operations
vibe-kanban-dbThis componentRead-writePostgres data (/var/lib/postgresql/data), 5Gi
vibe-kanban-electricThis componentRead-writeElectricSQL persistent state (/app/persistent), 1Gi

Security context: The code-server-storage PVC is owned by UID 1000 (coder). The VK remote-server must run as runAsUser: 1000, fsGroup: 1000 to match, same as other pods that mount this PVC (code-server, codev, static-files).

Node scheduling: code-server-storage is ReadWriteOnce and already shared by multiple pods on the same node. VK pods must also land on that node — may need a nodeSelector or affinity rule if scheduling becomes an issue.

Git Credentials

VK needs git credentials for worktree operations and PR creation. The code-server namespace has:

  • ssh-key secret — SSH private key for git operations
  • gh-oauth secret — GitHub token for API operations

Mount the SSH key into the VK container (same pattern as code-server) to enable remote git operations.

Docker Image

Build via image-factory rather than a one-off manual build:

  • Enrol in image-factory-state/images.yaml
  • Source: BloopAI/vibe-kanban (upstream directly — no fork needed for POC)
  • Dockerfile: crates/remote/Dockerfile
  • This gives automated rebuilds, base image tracking, and version pinning
  • Image published to ghcr.io/craigedmunds/vibe-kanban-remote:<pinned-version>

Resource Estimates

ServiceCPU RequestMemory RequestCPU LimitMemory Limit
remote-server100m256Mi500m1Gi
remote-db100m256Mi500m1Gi
electric50m128Mi250m512Mi
Total250m640Mi1250m2.5Gi

Open Questions

  1. GitHub OAuth App callback: Need to verify the existing GitHub OAuth App supports multiple callback URLs, and add https://vibe-kanban.lab.ctoaas.co/v1/oauth/github/callback.

  2. Google OAuth at both layers: If VK supports Google OAuth natively, could use the same Google OAuth app for both traefik and VK — single sign-in experience. Worth checking VK’s OAuth config options.

  3. VK_ALLOWED_ORIGINS: Must be set to https://vibe-kanban.lab.ctoaas.co on the remote-server, or API requests will get 403 behind the ingress.

  4. SERVER_DATABASE_URL interpolation: The URL includes $(DB_PASSWORD) relying on Kubernetes env var substitution. Since DB_PASSWORD is defined before SERVER_DATABASE_URL in the container spec, this should work — but worth watching during first deployment.

  5. Image-factory source: Using BloopAI/vibe-kanban directly. Fork only if we need local patches later.

  6. Workspace directory location: VK defaults to .vibe-kanban-workspaces/ — need to verify this is configurable and decide where on the PVC to place it.