Skip to main content

Helm Deployment Architecture

Overview

The platform uses a single Helm chart (infrastructure/helm/luckyplans/) to deploy all services across two k3d environments.

EnvironmentClusterImage RegistryImage Tag
localk3d on laptopnone (k3d import)latest
prodk3d on VPS / on-premisesghcr.iosha-<commit> (CI) / semver

Services

ServiceTypePortTransport
webNext.js frontend3000HTTP
api-gatewayNestJS GraphQL3001HTTP
prisma-migrateMigration job imageHelm pre-upgrade job
redisMessage broker / cache6379Redis 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

envimage.registryrendered
local""luckyplans/api-gateway:latest
prodghcr.ioghcr.io/takeshi-su57/api-gateway:sha-abc1234

Environment Differences

Keylocalprod
config.nodeEnvdevelopmentproduction
config.corsOriginhttp://localhosthttps://beta.luckyplans.xyz
ingress.hosts.app""beta.luckyplans.xyz
ingress.hosts.api""api.luckyplans.xyz
ingress.hosts.admin""admin.luckyplans.xyz
ingress.tls.enabledfalsetrue
web.buildArgs.graphqlUrl/graphqlhttps://api.luckyplans.xyz/graphql
image.registry""ghcr.io
image tagslatestsha-<commit>
replicas12

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 .