PVC name is now immutable — hardcoded in all 4 deployment templates instead of being a configurable Helm value. Removed pvcName from values.yaml and --set pvcName from deploy.sh. This ensures the PVC can never be renamed or deleted by Helm operations, only manually via kubectl.
6.5 KiB
Deploy Helm Chart
Monolithic Helm chart for all backend services.
Services
| Service | Port(s) | Replicas | HPA | Purpose |
|---|---|---|---|---|
app |
3000 (HTTP) | 2 | 2–10 | Main API server |
gitserver |
8021 (HTTP), 2222 (SSH) | 1 | 1–5 | Git HTTP + SSH server |
email_worker |
8084 (HTTP) | 1 | disabled | Email queue consumer (single instance only) |
git_hook |
8083 (HTTP) | 1 | 1–5 | Git hook worker pool |
metrics_aggregator |
9090 (HTTP) | 1 | 1–5 | Prometheus scrape + Loki push |
static_server |
8081 (HTTP) | 1 | 1–5 | Static file server (avatars, blobs, media) |
Prerequisites
The following resources must exist in the cluster before installing the Helm chart. They are not managed by Helm — install, upgrade, and uninstall of the chart will not touch them.
1. Namespace
kubectl create namespace app
2. PVC (aliyun-nfs-app, 200Ti, ReadWriteMany)
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-data
namespace: app
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 200Ti
storageClassName: aliyun-nfs-app
EOF
The chart references this PVC by hardcoded name
shared-data. This name is immutable — it cannot be changed via Helm values.
3. ConfigMap
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: app-env
namespace: app
data:
APP_REPOS_ROOT: "/data/repos"
APP_AVATAR_PATH: "/data/avatars"
STORAGE_PATH: "/data/files"
STATIC_ROOT: "/data"
APP_LOG_LEVEL: "info"
APP_COOKIE_SECURE: "false"
APP_DOMAIN_URL: "https://your-domain.com"
APP_DATABASE_URL: "postgres://user:pass@postgres:5432/app"
APP_REDIS_URL: "redis://redis:6379"
APP_AI_BASIC_URL: "https://api.openai.com/v1"
APP_AI_API_KEY: "sk-..."
APP_SMTP_PASSWORD: "..."
APP_SESSION_SECRET: "min-32-byte-random-string..."
APP_SSH_SERVER_PRIVATE_KEY: "<hex-encoded-private-key>"
EOF
| Variable | Default / Example | Required |
|---|---|---|
APP_REPOS_ROOT |
/data/repos |
Yes |
APP_AVATAR_PATH |
/data/avatars |
Yes |
STORAGE_PATH |
/data/files |
Yes |
STATIC_ROOT |
/data |
Yes |
APP_LOG_LEVEL |
info |
No |
APP_COOKIE_SECURE |
false |
No |
APP_DOMAIN_URL |
https://your-domain.com |
Yes |
APP_DATABASE_URL |
postgres://... |
Yes |
APP_REDIS_URL |
redis://... |
Yes |
APP_AI_BASIC_URL |
https://api.openai.com/v1 |
Yes |
APP_AI_API_KEY |
sk-... |
Yes |
APP_SMTP_PASSWORD |
... |
Yes |
APP_SESSION_SECRET |
min 32 bytes | Yes |
APP_SSH_SERVER_PRIVATE_KEY |
hex-encoded PEM | Yes |
APP_SSH_PORT |
2222 |
Yes (k8s) |
SSH host key:
APP_SSH_SERVER_PRIVATE_KEYmust be the hex-encoded Ed25519 private key PEM bytes.ssh-keygen -t ed25519 -f /tmp/ssh_host_key -N "" hexdump -v -e '/1 "%02x"' < /tmp/ssh_host_keySession secret: generate 48 random bytes:
openssl rand -base64 48Override the ConfigMap name with
--set configMapName=your-cm-name.
4. Verify prerequisites
kubectl get namespace app
kubectl get pvc -n app shared-data
kubectl get configmap -n app app-env
Quick Start
helm template deploy ./deploy --namespace app --set imageRegistry=ghcr.io/your-org
helm lint ./deploy
# Install
helm upgrade --install deploy ./deploy \
--namespace app \
--set imageRegistry=ghcr.io/your-org \
--set imageTag=v0.2.9
Storage
All services share a single PVC (shared-data) via subPath mounts:
| SubPath | Mount | Used By |
|---|---|---|
repos |
/data/repos |
app, gitserver, git-hook |
avatars |
/data/avatars |
app |
files |
/data/files |
app |
static |
/data |
static-server |
Autoscaling
All services except email_worker have HPA enabled by default. The email worker is fixed at 1 replica and must not be
scaled.
To adjust HPA bounds per service:
--set services.app.autoscaling.maxReplicas=20
--set services.app.autoscaling.targetCPUUtilization=70
To disable HPA for a service:
--set services.git_hook.autoscaling.enabled=false
Ingress
helm upgrade --install deploy ./deploy \
--namespace app \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set ingress.hosts[0].host=your-domain.com
Dependencies
All services require these to be reachable from the cluster:
- PostgreSQL (via
APP_DATABASE_URL) - Redis (via
APP_REDIS_URL) - Git binary (included in all Docker images)
- OpenAI-compatible API (via
APP_AI_BASIC_URL+APP_AI_API_KEY) - Qdrant vector DB (via
APP_QDRANT_URL) - SMTP server (via
APP_SMTP_*) - Embedding model (via
APP_EMBED_MODEL_*)
Optional dependencies with graceful degradation:
| Dependency | Variable | Fallback |
|---|---|---|
| NATS JetStream | NATS_URL + NATS_TOKEN |
Redis queue |
| Loki | LOKI_URL |
Logs discarded |
| OTEL Collector | OTEL_EXPORTER_OTLP_ENDPOINT |
Tracing disabled |
Production Example
helm upgrade --install deploy ./deploy \
--namespace app \
--set imageRegistry=ghcr.io/your-org \
--set imageTag=v0.2.9 \
--set services.app.replicas=3 \
--set services.app.autoscaling.maxReplicas=20 \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set ingress.hosts[0].host=your-domain.com \
--set configMapName=app-env