Helm Deployment Architecture
Overview
The platform uses a single Helm chart (infrastructure/helm/luckyplans/) to deploy all services across two k3d environments.
| Environment | Cluster | Image Registry | Image Tag |
|---|---|---|---|
| local | k3d on laptop | none (k3d import) | latest |
| prod | k3d on VPS / on-premises | ghcr.io | sha-<commit> (CI) / semver |
Services
| Service | Type | Port | Transport |
|---|---|---|---|
web | Next.js frontend | 3000 | HTTP |
api-gateway | NestJS GraphQL | 3001 | HTTP |
prisma-migrate | Migration job image | — | Helm pre-upgrade job |
redis | Message broker / cache | 6379 | Redis protocol |
Edge runtime (apps/edge-agent) is not deployed by this Helm chart. It runs on external Windows/Linux hosts and connects to gateway internal edge endpoints.
Chart Structure
infrastructure/helm/luckyplans/
Chart.yaml
values.yaml # defaults + local k3d config
values.prod.yaml # prod overrides (merged on top of values.yaml)
templates/
_helpers.tpl
namespace.yaml
configmap.yaml # luckyplans-config
secret.yaml # luckyplans-secrets (auto-generates JWT_SECRET)
ingress.yaml # Traefik ingress
cluster-issuer.yaml # cert-manager ClusterIssuer (prod only)
middleware-redirect.yaml # Traefik HTTPS redirect + HSTS (prod only)
pdb.yaml # PodDisruptionBudgets
smoke-test-job.yaml # ArgoCD post-sync smoke tests (prod only)
redis/
api-gateway/
web/
Key Design Decisions
1. Redis: plain template, not Bitnami subchart
Redis runs as a simple redis:7-alpine Deployment + ClusterIP Service. If you migrate to managed Redis later, remove the redis/ templates and point config.redisHost at the managed host.
2. NEXT_PUBLIC_GRAPHQL_URL is a build-time constraint
Next.js bakes NEXT_PUBLIC_* variables into the JavaScript bundle at docker build time — Helm cannot inject this at helm upgrade time.
Changing web.buildArgs.graphqlUrl in a values file only takes effect if you rebuild and redeploy the web image.
3. Namespace is chart-managed with keep annotation
The chart creates the luckyplans namespace annotated with helm.sh/resource-policy: keep, preventing helm uninstall from deleting the namespace and any PVCs or secrets inside it.
4. Image registry prefix is conditional
| env | image.registry | rendered |
|---|---|---|
| local | "" | luckyplans/api-gateway:latest |
| prod | ghcr.io | ghcr.io/takeshi-su57/api-gateway:sha-abc1234 |
Environment Differences
| Key | local | prod |
|---|---|---|
config.nodeEnv | development | production |
config.corsOrigin | http://localhost | https://beta.luckyplans.xyz |
ingress.hosts.app | "" | beta.luckyplans.xyz |
ingress.hosts.api | "" | api.luckyplans.xyz |
ingress.hosts.admin | "" | admin.luckyplans.xyz |
ingress.tls.enabled | false | true |
web.buildArgs.graphqlUrl | /graphql | https://api.luckyplans.xyz/graphql |
image.registry | "" | ghcr.io |
| image tags | latest | sha-<commit> |
| replicas | 1 | 2 |
Legacy service note: v0.api.luckyplans.xyz is routed by this Helm chart to an in-cluster legacy-v0-api proxy service. The proxy pod forwards traffic to the host-level legacy API at host.k3d.internal:9000; production values add a pod hostAliases entry so that name resolves to the Docker host gateway from inside Kubernetes. This avoids Traefik's default ExternalName service restrictions while keeping the legacy process outside Kubernetes.
Deployment Commands
Local (laptop)
pnpm deploy:local
# Builds images, imports into k3d, helm upgrade --install with values.yaml
Prod (ArgoCD GitOps)
Prod is managed by ArgoCD — do not run helm upgrade directly.
See ArgoCD and Deployment Guide.
Teardown (local only)
pnpm deploy:teardown
Useful Commands
# Lint the chart
helm lint infrastructure/helm/luckyplans/
# Render templates locally (dry run)
helm template luckyplans infrastructure/helm/luckyplans/
# Check release status
pnpm deploy:status
# View what Helm has deployed
helm -n luckyplans get values luckyplans
helm -n luckyplans history luckyplans
# Verify health
curl -X POST http://localhost/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ health }"}' | jq .