Kubernetes Deployment
Deploy HelloJohn on Kubernetes using Helm or raw manifests, with PostgreSQL, Redis, and ingress configuration.
Kubernetes Deployment
HelloJohn runs as a stateless container and is well-suited for Kubernetes. This guide covers deploying with Helm (recommended) or raw manifests.
Prerequisites
Before deploying:
- Kubernetes 1.25+
kubectlconfigured for your cluster- Helm 3 (if using Helm)
- PostgreSQL accessible from the cluster
- TLS certificate or cert-manager installed
- An ingress controller (nginx, Traefik, etc.)
See Prerequisites for full infrastructure requirements.
Helm Deployment (Recommended)
1. Add the HelloJohn Helm repository
helm repo add hellojohn https://charts.hellojohn.dev
helm repo update2. Create a values file
# values.yaml
replicaCount: 2
image:
repository: ghcr.io/hellojohn/hellojohn
tag: "latest"
pullPolicy: IfNotPresent
env:
HELLOJOHN_ENV: production
HELLOJOHN_PORT: "8080"
envFromSecret: hellojohn-secrets # Kubernetes secret with sensitive values
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: auth.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: hellojohn-tls
hosts:
- auth.example.com
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 13. Create the secrets
Store sensitive configuration in a Kubernetes Secret:
kubectl create secret generic hellojohn-secrets \
--from-literal=HELLOJOHN_DATABASE_URL="postgresql://user:pass@host:5432/hellojohn?sslmode=require" \
--from-literal=HELLOJOHN_JWT_SIGNING_KEY="$(cat signing_key.pem)" \
--from-literal=HELLOJOHN_MASTER_KEY="mk_live_your_master_key" \
--from-literal=HELLOJOHN_SMTP_PASSWORD="your_smtp_password" \
--namespace=hellojohnFor production, use External Secrets Operator or Sealed Secrets to sync secrets from AWS Secrets Manager, Vault, or GCP Secret Manager.
4. Run database migrations
Before the first deploy, run migrations as a Kubernetes Job:
helm install hellojohn hellojohn/hellojohn \
--namespace hellojohn \
--create-namespace \
--values values.yaml \
--set migrate.enabled=true \
--waitOr run migrations separately:
kubectl run hellojohn-migrate \
--image=ghcr.io/hellojohn/hellojohn:latest \
--restart=Never \
--env-from=secret/hellojohn-secrets \
--command -- /app/hellojohn migrate5. Install the chart
helm install hellojohn hellojohn/hellojohn \
--namespace hellojohn \
--create-namespace \
--values values.yaml6. Upgrade
helm upgrade hellojohn hellojohn/hellojohn \
--namespace hellojohn \
--values values.yamlRaw Manifests
If you prefer not to use Helm, here are the core manifests.
Namespace
apiVersion: v1
kind: Namespace
metadata:
name: hellojohnDeployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: hellojohn
namespace: hellojohn
spec:
replicas: 2
selector:
matchLabels:
app: hellojohn
template:
metadata:
labels:
app: hellojohn
spec:
containers:
- name: hellojohn
image: ghcr.io/hellojohn/hellojohn:latest
ports:
- containerPort: 8080
envFrom:
- secretRef:
name: hellojohn-secrets
env:
- name: HELLOJOHN_ENV
value: production
- name: HELLOJOHN_PORT
value: "8080"
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: hellojohnService
apiVersion: v1
kind: Service
metadata:
name: hellojohn
namespace: hellojohn
spec:
selector:
app: hellojohn
ports:
- protocol: TCP
port: 80
targetPort: 8080Ingress (nginx)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hellojohn
namespace: hellojohn
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "1m"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- auth.example.com
secretName: hellojohn-tls
rules:
- host: auth.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hellojohn
port:
number: 80HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hellojohn
namespace: hellojohn
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hellojohn
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: hellojohn
namespace: hellojohn
spec:
minAvailable: 1
selector:
matchLabels:
app: hellojohnHealth Check Endpoint
HelloJohn exposes a health check at GET /health:
{
"status": "ok",
"database": "ok",
"redis": "ok",
"version": "1.4.2"
}Use this for readiness and liveness probes.
Multi-Region Deployment
For multi-region deployments:
- Deploy HelloJohn in each region
- Use a globally distributed PostgreSQL (e.g., CockroachDB, PlanetScale, Neon Branching, or AWS Aurora Global)
- Use Redis per-region for rate limiting (each region has its own Redis)
- Use a global load balancer (Cloudflare Load Balancing, AWS Global Accelerator) to route to the nearest region
JWTs are stateless — any region can verify a token issued by another region, since all regions share the same signing key.
Upgrading
# Pull latest chart
helm repo update
# Upgrade (migrations run automatically if migrate.enabled=true)
helm upgrade hellojohn hellojohn/hellojohn \
--namespace hellojohn \
--values values.yamlHelloJohn uses rolling deployments by default. Old pods are replaced one at a time to maintain zero downtime. Database migrations are backward-compatible with the previous version to support this.