8.7 KiB
Local Kubernetes Deployment Guide
Last Updated: 2026-01-04
Difficulty: Intermediate
Duration: 30-45 minutes
Overview
This guide describes how to deploy the IAM Service to a local Kubernetes cluster using Docker Desktop on macOS.
Prerequisites
Software
- Docker Desktop 4.0+ with Kubernetes enabled
- kubectl CLI
- kind CLI (for loading images into cluster)
- pnpm 8+
Knowledge
- Basic understanding of Kubernetes (Pods, Deployments, Services)
- Familiarity with Docker and containerization
- Command line proficiency
Step 1: Environment Preparation
1.1 Enable Kubernetes in Docker Desktop
- Open Docker Desktop
- Go to Settings → Kubernetes
- Check Enable Kubernetes
- Select cluster provisioning:
- kind (recommended for development)
- or kubeadm
- Click Apply & Restart
- Wait 2-3 minutes for Kubernetes to start
1.2 Verify Kubernetes
# Check kubectl context
kubectl config current-context
# Output: docker-desktop
# Check nodes
kubectl get nodes
# Output: docker-desktop Ready control-plane ...
# Check kind cluster name (if using kind)
kind get clusters
# Output: desktop
1.3 Install kind CLI
# Install kind
brew install kind
# Verify installation
kind version
Step 2: Build Docker Image
# Navigate to deployments directory
cd deployments/local/kubernetes
# Build image (script auto-builds)
docker build -t iam-service:local -f ../../../services/iam-service/Dockerfile ../../..
# Verify image
docker images | grep iam-service
# Output: iam-service:local ... 1.67GB
Step 3: Load Image into Kubernetes Cluster
⚠️ Important: Docker Desktop Kubernetes with kind backend does not automatically share images with Docker Desktop. You must load the image into the cluster:
# Load image into kind cluster
kind load docker-image iam-service:local --name desktop
# Wait for completion (may take 1-2 minutes)
Note: If using kubeadm instead of kind, you can skip this step as kubeadm shares images better.
Step 4: Configure Secrets
4.1 Prepare Environment Variables
Create .env.k8s file with the following variables:
# Database
DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
# JWT Secrets (MUST change in production!)
JWT_SECRET=your-strong-jwt-secret-min-32-chars
JWT_REFRESH_SECRET=your-strong-refresh-secret-min-32-chars
JWT_ID_SECRET=your-strong-id-secret-min-32-chars
# Encryption (optional)
ENCRYPTION_KEY=your-32-char-encryption-key-here
4.2 Create Kubernetes Secrets
The deploy.sh script will automatically create secrets, or you can create them manually:
# Create namespace
kubectl create namespace iam-local
# Create secrets
kubectl create secret generic iam-service-secrets \
--from-literal=DATABASE_URL="postgresql://..." \
--from-literal=JWT_SECRET="$(openssl rand -base64 32)" \
--from-literal=JWT_REFRESH_SECRET="$(openssl rand -base64 32)" \
--from-literal=JWT_ID_SECRET="$(openssl rand -base64 32)" \
--from-literal=ENCRYPTION_KEY="$(openssl rand -base64 32)" \
-n iam-local
# Verify secrets
kubectl get secrets -n iam-local
Step 5: Deploy Service
5.1 Using Automated Script
cd deployments/local/kubernetes
# Run deployment script
./deploy.sh
# Script will:
# 1. Create namespace
# 2. Create secrets
# 3. Apply ConfigMap
# 4. Build Docker image
# 5. Deploy service
5.2 Manual Deployment
# Apply ConfigMap
kubectl apply -f iam-service-configmap.yaml
# Apply Deployment
kubectl apply -f iam-service-deployment.yaml
# Apply Service
kubectl apply -f iam-service-service.yaml
Step 6: Verify Deployment
6.1 Check Pod Status
# View pods
kubectl get pods -n iam-local
# Expected output:
# NAME READY STATUS RESTARTS AGE
# iam-service-68994fdc79-gh2mj 1/1 Running 0 2m
6.2 Check Logs
# View logs
kubectl logs -f -n iam-local -l app=iam-service
# Successful logs:
# [iam-service] Database connected successfully
# [iam-service] Service started on port 5001
6.3 Check Service
# View services
kubectl get svc -n iam-local
# Output:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# iam-service LoadBalancer 10.96.187.160 <pending> 80:30577/TCP
Note: EXTERNAL-IP will be <pending> because local cluster doesn't have a cloud load balancer.
Step 7: Test Service
7.1 Port Forward
# Forward port to access service
kubectl port-forward svc/iam-service 5002:80 -n iam-local
# Service will be available at http://localhost:5002
7.2 Test Health Endpoints
# Test liveness probe
curl http://localhost:5002/health/live
# Response:
# {
# "success": true,
# "data": {"status": "live"},
# "timestamp": "2026-01-04T06:04:03.425Z"
# }
# Test readiness probe
curl http://localhost:5002/health/ready
# Test API docs
open http://localhost:5002/api-docs
Troubleshooting
Issue 1: ImagePullBackOff
Symptoms:
kubectl get pods -n iam-local
# NAME READY STATUS RESTARTS AGE
# iam-service-6b8fbb574d-hn27z 0/1 ImagePullBackOff 0 2m
Cause: kind cluster cannot find image iam-service:local
Solution:
# Load image into kind cluster
kind load docker-image iam-service:local --name desktop
# Restart deployment
kubectl rollout restart deployment/iam-service -n iam-local
Issue 2: CrashLoopBackOff
Symptoms:
kubectl get pods -n iam-local
# NAME READY STATUS RESTARTS AGE
# iam-service-xxx 0/1 CrashLoopBackOff 5 5m
Cause: Service crashes on startup
Debug:
# View logs
kubectl logs -n iam-local -l app=iam-service --tail=50
# Describe pod
kubectl describe pod -n iam-local -l app=iam-service
Common solutions:
- Check DATABASE_URL in secrets
- Verify Prisma migrations have run
- Check environment variables in ConfigMap
Issue 3: Database Connection Failed
Symptoms: Logs show Database connection error
Solution:
# Verify DATABASE_URL
kubectl get secret iam-service-secrets -n iam-local -o jsonpath='{.data.DATABASE_URL}' | base64 -d
# Test connection from pod
kubectl exec -it -n iam-local deployment/iam-service -- sh
# Inside pod:
# npx prisma db push
Issue 4: Redis Connection Errors
Symptoms: Logs show Redis connection error
Cause: Redis not deployed in cluster
Solution (optional - only if Redis needed):
# Deploy Redis
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: iam-local
spec:
serviceName: redis
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: iam-local
spec:
selector:
app: redis
ports:
- port: 6379
EOF
# Update ConfigMap with REDIS_HOST=redis
kubectl edit configmap iam-service-config -n iam-local
# Restart deployment
kubectl rollout restart deployment/iam-service -n iam-local
Debugging Commands
# View all resources
kubectl get all -n iam-local
# Describe deployment
kubectl describe deployment iam-service -n iam-local
# Describe pod
kubectl describe pod -n iam-local -l app=iam-service
# View events
kubectl get events -n iam-local --sort-by='.lastTimestamp'
# Exec into pod
kubectl exec -it -n iam-local deployment/iam-service -- sh
# View logs realtime
kubectl logs -f -n iam-local -l app=iam-service
# View logs from previous container (if pod restarted)
kubectl logs -n iam-local -l app=iam-service --previous
Cleanup
# Delete deployment
kubectl delete -f iam-service-deployment.yaml
kubectl delete -f iam-service-service.yaml
kubectl delete -f iam-service-configmap.yaml
# Delete secrets
kubectl delete secret iam-service-secrets -n iam-local
# Delete namespace (deletes everything)
kubectl delete namespace iam-local
Next Steps
- Deploy Redis (optional): To enable caching and rate limiting
- Setup Ingress: To expose service externally
- Configure HPA: Horizontal Pod Autoscaler for auto-scaling
- Add Monitoring: Prometheus + Grafana
- Setup CI/CD: Automated deployment on code changes