Skip to main content

How to Deploy

All environments use the Helm chart at infrastructure/helm/luckyplans/ with per-environment values files. Continuous delivery is handled by ArgoCD in production.

Architecture Overview

Traefik ingress (single public IP, host-based routing)
luckyplans.xyz -> landing:8080
docs.luckyplans.xyz -> docs:8080
beta.luckyplans.xyz -> web:3000
api.luckyplans.xyz -> api-gateway:3001
admin.luckyplans.xyz -> keycloak/argocd

api-gateway -> Redis
api-gateway -> Keycloak
api-gateway -> PostgreSQL
api-gateway -> MinIO

App services run in the luckyplans namespace.

Prerequisites

ToolVersionInstall
DockerLatestdocker.com
k3dLatestk3d.io
kubectlLatestkubernetes.io
Helm>= 3.0helm.sh
cert-managerv1.17.1Required for prod TLS
kubesealLatestRequired for prod sealed secrets

Environments

localprod
Clusterk3d on laptopk3d on VPS / on-premises
CD methodDirect HelmArgoCD auto-sync
Values filevalues.yamlvalues.yaml + values.prod.yaml
Image registrynone (k3d import)ghcr.io
Image tagslatestsha-<commit> or semver
Replicas12
TLSoffon

Production domains

  • luckyplans.xyz -> landing SPA
  • docs.luckyplans.xyz -> docs SPA
  • beta.luckyplans.xyz -> web frontend
  • api.luckyplans.xyz -> api-gateway
  • admin.luckyplans.xyz -> Keycloak and ArgoCD
  • v0.api.luckyplans.xyz -> legacy API proxy

Local Deployment

Full deploy

pnpm deploy:local

This creates the cluster, builds images, imports them into k3d, and upgrades the Helm release.

After completion:

Targeted deploy

./infrastructure/scripts/deploy-local.sh landing
./infrastructure/scripts/deploy-local.sh web
./infrastructure/scripts/deploy-local.sh api-gateway web
./infrastructure/scripts/deploy-local.sh prisma-migrate
./infrastructure/scripts/deploy-local.sh --helm-only

Teardown

pnpm deploy:teardown

Status

pnpm deploy:status

Manual step-by-step

k3d cluster create luckyplans-local \
--port "80:80@loadbalancer" \
--port "443:443@loadbalancer" \
--agents 1

kubectl config use-context k3d-luckyplans-local

docker build -t luckyplans/landing:latest -f apps/landing/Dockerfile .
docker build --build-arg DOCS_APP_URL="/login" -t luckyplans/docs:latest -f apps/docs/Dockerfile .
docker build --build-arg NEXT_PUBLIC_GRAPHQL_URL="/graphql" --build-arg NEXT_PUBLIC_DOCS_URL="/docs" -t luckyplans/web:latest -f apps/web/Dockerfile .
docker build -t luckyplans/api-gateway:latest -f apps/api-gateway/Dockerfile .
docker build -t luckyplans/prisma-migrate:latest -f packages/prisma/Dockerfile .

docker pull redis:7-alpine
docker pull postgres:17-alpine
k3d image import redis:7-alpine -c luckyplans-local
k3d image import postgres:17-alpine -c luckyplans-local
k3d image import luckyplans/landing:latest -c luckyplans-local
k3d image import luckyplans/docs:latest -c luckyplans-local
k3d image import luckyplans/web:latest -c luckyplans-local
k3d image import luckyplans/api-gateway:latest -c luckyplans-local
k3d image import luckyplans/prisma-migrate:latest -c luckyplans-local

helm upgrade --install luckyplans infrastructure/helm/luckyplans \
--namespace luckyplans \
--create-namespace \
--rollback-on-failure --timeout 3m

CI/CD with ArgoCD

Production deployments follow:

Push to main -> CI -> Docker Build & Push -> Update Image Tags -> ArgoCD auto-sync -> smoke tests

Production First-Time Setup

Prerequisites

  • DNS A records for luckyplans.xyz, docs.luckyplans.xyz, beta.luckyplans.xyz, api.luckyplans.xyz, admin.luckyplans.xyz, and v0.api.luckyplans.xyz
  • Firewall access for ports 22, 80, and 443
  • GitHub token with package read access
  • CD_PUSH_TOKEN for updating tags
  • kubeseal installed locally

Setup steps

k3d cluster create luckyplans-prod \
--port "80:80@loadbalancer" \
--port "443:443@loadbalancer" \
--agents 1

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.1/cert-manager.yaml
kubectl -n cert-manager rollout status deploy/cert-manager

./infrastructure/scripts/install-sealed-secrets.sh

git clone https://github.com/takeshi-su57/luckyplans.git
cd luckyplans
./infrastructure/scripts/install-argocd.sh --github-token <your-github-pat>

./infrastructure/scripts/seal-secrets.sh

Paste the generated encrypted values into infrastructure/helm/luckyplans/values.prod.yaml, commit, and let ArgoCD sync.

Secrets Management

Production secrets are managed with Bitnami Sealed Secrets.

Required secrets:

  • JWT_SECRET
  • SESSION_SECRET
  • WORKER_CREDENTIAL_PEPPER
  • KEYCLOAK_CLIENT_SECRET
  • POSTGRES_PASSWORD
  • KEYCLOAK_ADMIN_PASSWORD
  • MINIO_ACCESS_KEY
  • MINIO_SECRET_KEY

Rotate secrets by regenerating and resealing them:

./infrastructure/scripts/seal-secrets.sh

Kubernetes Security Baseline

  1. Keep sealedSecrets.enabled: true in production.
  2. Rotate session, worker, JWT, Keycloak, and MinIO secrets on a fixed schedule and after incidents.
  3. Restrict access to values.prod.yaml, Sealed Secrets backups, and CI secrets.
  4. Enforce HTTPS-only ingress and keep certManager.enabled: true.
  5. Keep image tags immutable.
  6. Use non-root containers and do not relax capability drops.
  7. Keep ArgoCD auto-sync as the source of truth.

Missing WORKER_CREDENTIAL_PEPPER

If the gateway fails with Missing required environment variable: WORKER_CREDENTIAL_PEPPER:

./infrastructure/scripts/seal-secrets.sh --seal-only WORKER_CREDENTIAL_PEPPER

Add the output under sealedSecrets.encryptedData in values.prod.yaml and let ArgoCD sync.

Scaling

apiGateway:
replicas: 3

landing:
replicas: 2

docs:
replicas: 2

web:
replicas: 2

Commit and push the values change. Do not use kubectl scale on ArgoCD-managed workloads.

Edge Agent Release and Rollout

CI needs these signing secrets:

  • EDGE_RELEASE_SIGNING_PRIVATE_KEY
  • EDGE_RELEASE_SIGNING_KEY_ID

The gateway verifies releases with EDGE_RELEASE_SIGNING_PUBLIC_KEY.

Artifact publishing flow

The release workflow produces:

  • luckyplans-edge-agent-v<version>-linux-x64-service.tar.gz
  • luckyplans-edge-agent-v<version>-win32-x64-service.zip
  • detached .sig files
  • SHA256SUMS
  • manifest.json

Release registration flow

  1. Build and publish edge artifacts.
  2. Open manifest.json.
  3. Register the release using createEdgeRelease.
  4. Confirm edgeReleases returns platform-specific artifacts.
  5. Set targetVersion for selected workers or start a campaign.

Registration token requirement

Edge registration requires an active enrollment token created in the Edges UI.

Recommended flow:

  1. Open /edges.
  2. Create an enrollment token.
  3. Copy it immediately.
  4. Use it during edge onboarding.
  5. Revoke it after onboarding completes.

Viewing Logs

kubectl -n luckyplans logs -f deployment/api-gateway
kubectl -n luckyplans logs -f deployment/web
kubectl -n luckyplans logs <pod-name> --previous

Rollback

ArgoCD-managed

git log --oneline -5
git revert <commit-sha>
git push origin main

Local

helm -n luckyplans rollback luckyplans