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
service-coreNestJS microserviceRedis RPC
redisMessage broker / cache6379Redis protocol

service-core communicates exclusively via Redis message patterns and has no Kubernetes Service resource.

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/
    service-core/
    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://luckyplans.xyz
ingress.host"" (any)luckyplans.xyz
ingress.tls.enabledfalsetrue
image.registry""ghcr.io
image tagslatestsha-<commit>
replicas12

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 .