Files
reactbin/specs/013-k8s-manifests/quickstart.md
agatha bf27c97deb Feat: Add Kubernetes manifests for k3s production deployment
Adds complete k8s/ manifest tree: Namespace, VaultAuth + VaultStaticSecret
CRDs (VSO secret sync from Vault KV v2), API and UI Deployments and Services,
nginx Ingress with cert-manager TLS, MinIO StatefulSet with PVC and init Job,
and Alembic init container on the API Deployment for automatic schema
migrations. Includes .yamllint.yml config and validate-k8s Makefile target.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 21:19:09 +00:00

2.9 KiB

Quickstart: Kubernetes Production Deployment

Before You Apply

  1. Store API secrets in Vault at reactbin/api/config (KV v2):

    DATABASE_URL          = postgresql+asyncpg://reactbin:<pw>@<host>:5432/reactbin
    JWT_SECRET_KEY        = <long-random-string>
    OWNER_USERNAME        = <your-username>
    OWNER_PASSWORD        = <your-password>
    S3_ENDPOINT_URL       = http://minio.reactbin.svc.cluster.local:9000
    S3_BUCKET_NAME        = reactbin
    S3_ACCESS_KEY_ID      = <same as MINIO_ROOT_USER>
    S3_SECRET_ACCESS_KEY  = <same as MINIO_ROOT_PASSWORD>
    API_BASE_URL          = https://<your-domain>
    API_DOCS_ENABLED      = false
    
  2. Store MinIO credentials in Vault at reactbin/minio/credentials (KV v2):

    MINIO_ROOT_USER     = <choose a strong username>
    MINIO_ROOT_PASSWORD = <choose a strong password>
    
  3. Create a Vault Kubernetes auth role bound to the default service account in the reactbin namespace with read access to both paths above.

  4. Confirm DNS resolves to the cluster ingress IP and the letsencrypt-prod ClusterIssuer exists.

Deploy

# Substitute the real image tags
sed -i 's|reactbin-api:latest|reactbin-api:v1.0.0|g' k8s/api/deployment.yaml
sed -i 's|reactbin-ui:latest|reactbin-ui:v1.0.0|g' k8s/ui/deployment.yaml

# Apply everything
kubectl apply -f k8s/

Verify

# Watch pods come up (init container runs first on the API pod)
kubectl get pods -n reactbin -w

# API health
curl -sf https://<your-domain>/api/v1/health && echo "API OK"

# UI reachable
curl -sf -o /dev/null -w "%{http_code}\n" https://<your-domain>/

# Docs correctly gated
curl -o /dev/null -w "%{http_code}\n" https://<your-domain>/docs    # → 404
curl -o /dev/null -w "%{http_code}\n" https://<your-domain>/redoc   # → 404

# Check migration init container ran
kubectl logs -n reactbin -l app=api -c alembic-migrate

Scenario: Migration fails on deploy

# Pod will be stuck in Init state
kubectl get pods -n reactbin
# NAME          READY   STATUS                  RESTARTS
# api-xxx-yyy   0/1     Init:CrashLoopBackOff   2

# See why
kubectl logs -n reactbin <pod-name> -c alembic-migrate

# Fix the issue (e.g. correct DATABASE_URL in Vault, wait for VSO to resync)
# Then delete the pod to force a fresh rollout
kubectl rollout restart deployment/api -n reactbin

Scenario: Update to a new image version

kubectl set image deployment/api api=reactbin-api:v1.1.0 -n reactbin
kubectl set image deployment/ui ui=reactbin-ui:v1.1.0 -n reactbin
# Kubernetes rolls out new pods; init container runs migrations before traffic switches

Scenario: Restore after MinIO pod restart

MinIO uses a PersistentVolumeClaim. Pod restarts do not affect stored data. Verify:

kubectl delete pod -n reactbin minio-0
kubectl get pods -n reactbin -w   # minio-0 restarts, PVC reattaches
# Previously uploaded images should still be accessible via the API