# Local Kubernetes Deployment Guide



**Last Updated**: 2026-01-05
**Difficulty**: Intermediate
**Duration**: 30-45 minutes

## Workflow

```mermaid
graph TD
 Start([ Start]) --> EnvPrep[1⃣ Environment Prep<br/>Enable K8s in Docker Desktop]
 EnvPrep --> BuildImg[2⃣ Build Docker Image<br/>docker build -t service:local]
 BuildImg --> LoadImg[3⃣ Load Image to Cluster<br/>kind load docker-image]
 LoadImg --> Secrets[4⃣ Configure Secrets<br/>kubectl create secret]
 Secrets --> Deploy[5⃣ Deploy Service<br/>kubectl apply -f manifests]
 Deploy --> Verify[6⃣ Verify Deployment<br/>kubectl get pods]
 Verify --> Test[7⃣ Test Service<br/>Port Forward & Curl]
 Test --> End([ Complete])

 subgraph Deployment[" Deployment Resources"]
 Deploy --> |Apply| ConfigMap[ConfigMap<br/>Environment Config]
 Deploy --> |Apply| K8sDeployment[Deployment<br/>Pod Template]
 Deploy --> |Apply| Service[Service<br/>LoadBalancer/NodePort]
 end

 %% Start/End nodes - Success Green
 style Start fill:#004d40,stroke:#00bfa5,stroke-width:3px,color:#e0f2f1
 style End fill:#1b5e20,stroke:#66bb6a,stroke-width:3px,color:#e8f5e9

 %% Workflow steps - Dark theme with distinct colors
 style EnvPrep fill:#263238,stroke:#546e7a,stroke-width:2px,color:#eceff1
 style BuildImg fill:#3e2723,stroke:#8d6e63,stroke-width:2px,color:#efebe9
 style LoadImg fill:#1a237e,stroke:#5c6bc0,stroke-width:2px,color:#e8eaf6
 style Secrets fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#ffebee
 style Deploy fill:#004d40,stroke:#26a69a,stroke-width:2px,color:#e0f2f1
 style Verify fill:#1565c0,stroke:#42a5f5,stroke-width:2px,color:#e3f2fd
 style Test fill:#4a148c,stroke:#ab47bc,stroke-width:2px,color:#f3e5f5

 %% Deployment resources subgraph
 style Deployment fill:#1a1a1a,stroke:#424242,stroke-width:2px,color:#e0e0e0
 style ConfigMap fill:#263238,stroke:#78909c,stroke-width:1px,color:#cfd8dc
 style K8sDeployment fill:#263238,stroke:#78909c,stroke-width:1px,color:#cfd8dc
 style Service fill:#263238,stroke:#78909c,stroke-width:1px,color:#cfd8dc
```

## Overview

This guide details how to deploy the IAM Service (or any microservice in the GoodGo ecosystem) to a local Kubernetes cluster using Docker Desktop on macOS.

**Important:** This guide assumes you are using **Docker Desktop** with **Kubernetes enabled**. If you are using Minikube or plain Kind, the steps might differ slightly (especially the image loading part).

## 1. Prerequisites

### Software
- **Docker Desktop 4.0+**: [Download Link](https://www.docker.com/products/docker-desktop/)
 - Kubernetes must be enabled in settings.
- **kubectl CLI**: Command-line tool for interacting with K8s.
 ```bash
 brew install kubectl
 ```
- **kind CLI**: Required to load images into the cluster if using Kind backend explicitely.
 ```bash
 brew install kind
 ```
- **pnpm 8+**: Project package manager.
 ```bash
 npm install -g pnpm
 ```

### Knowledge
- Basic understanding of Kubernetes concepts: **Pod**, **Deployment**, **Service**, **Secret**, **ConfigMap**.
- Familiarity with basic Docker commands (`docker build`, `docker images`).
- Ability to navigate and run commands in the Terminal.

## 2. Environment Preparation

### 2.1 Enable Kubernetes in Docker Desktop

1. Open **Docker Desktop**.
2. Click the **Settings ()** icon.
3. Select the **Kubernetes** tab.
4. Check **Enable Kubernetes**.
5. Select **Show system containers (advanced)** for easier debugging (optional).
6. Click **Apply & Restart**.
7. Wait 2-3 minutes until the Kubernetes icon in the bottom corner turns green.

### 2.2 Verify Kubernetes Connection

Check if `kubectl` is connected to the correct context:

```bash
# Check current context
kubectl config current-context
# Expected Output: docker-desktop

# List all nodes in the cluster
kubectl get nodes
# Expected Output:
# NAME STATUS ROLES AGE VERSION
# docker-desktop Ready control-plane 10m v1.29.1
```

## 3. Build Docker Image

We need to build the service image before deploying. Taking `iam-service` as an example.

```bash
# Navigate to the kubernetes deployment directory
cd deployments/local/kubernetes

# Build the Docker image from the root context
# Note: -f points to service Dockerfile, context is root (../../..)
docker build -t iam-service:local -f ../../../services/iam-service/Dockerfile ../../..

# Verify the image was built successfully
docker images | grep iam-service
# Expected Output:
# iam-service local [IMAGE_ID] [SIZE] [CREATED]
```

## 4. Load Image into Cluster

** IMPORTANT**: Docker Desktop can use different backends. If you are running Kubernetes inside Docker Desktop, sometimes it doesn't immediately see local images if using a `kind` node underneath.

If you are using **Kind** (Kubernetes in Docker) separately or a specific Docker Desktop config, you need to load the image:

```bash
# Load image into kind cluster (if using kind explicitly)
kind load docker-image iam-service:local --name desktop

# Validating image presence (optional, hard with Docker Desktop K8s directly)
```

**Tip:** With default Docker Desktop, building the local image (`docker build ...`) is usually automatically available to Docker Desktop's K8s cluster. This loading step is mainly for those using `kind` CLI to create separate clusters.

## 5. Configure Secrets & ConfigMap

Kubernetes environments need sensitive environment variables (Secrets) and general configuration (ConfigMap).

### 5.1 Create Secrets (Manually)

You can run a script or the following commands to create secrets securely.

```bash
# Create a dedicated namespace for local testing
kubectl create namespace iam-local

# Generate random secrets and store in Kubernetes
kubectl create secret generic iam-service-secrets \
 --from-literal=DATABASE_URL="postgresql://user:password@host.docker.internal:5432/iam_db?schema=public" \
 --from-literal=JWT_SECRET="$(openssl rand -base64 32)" \
 --from-literal=JWT_REFRESH_SECRET="$(openssl rand -base64 32)" \
 --from-literal=ENCRYPTION_KEY="$(openssl rand -base64 32)" \
 -n iam-local

# Verify secrets creation
kubectl get secrets -n iam-local
```

**Note on `host.docker.internal`:** On macOS, for a K8s pod to connect to PostgreSQL running on the host machine (or another container via port mapping), we use `host.docker.internal`.

### 5.2 ConfigMap

The `iam-service-configmap.yaml` file typically contains non-sensitive variables like `NODE_ENV`, `LOG_LEVEL`.

```bash
# Apply ConfigMap
kubectl apply -f iam-service-configmap.yaml -n iam-local
```

## 6. Deploy Service

Now we will deploy the main resources.

```bash
# Apply Deployment manifest
kubectl apply -f iam-service-deployment.yaml -n iam-local

# Apply Service manifest (LoadBalancer/NodePort)
kubectl apply -f iam-service-service.yaml -n iam-local
```

## 7. Verify & Debug

After deployment, ensure the Pod is stable (Running).

### 7.1 Check Pods

```bash
# Get all pods in the namespace
kubectl get pods -n iam-local

# Expected Output:
# NAME READY STATUS RESTARTS AGE
# iam-service-68994fdc79-gh2mj 1/1 Running 0 30s
```

### 7.2 View Detailed Logs

If Status is not `Running` (e.g., `CrashLoopBackOff` or `ImagePullBackOff`), check logs:

```bash
# Stream logs from the pod
kubectl logs -f -n iam-local -l app=iam-service

# Describe pod to see events (pull error, mounts, scheduling)
kubectl describe pod -n iam-local -l app=iam-service
```

### 7.3 Common Errors

1. **ImagePullBackOff**:
 - **Reason**: K8s cannot find `iam-service:local` image.
 - **Fix**: Ensure `imagePullPolicy: IfNotPresent` or `Never` in local deployment yaml. If using Kind, remember to run `kind load`.

2. **CrashLoopBackOff**:
 - **Reason**: Runtime error, usually unable to connect to Database.
 - **Fix**: Check `DATABASE_URL` in Secret. Ensure Postgres is running and accessible from K8s (use `host.docker.internal`).

3. **Pending Service**:
 - **Reason**: `LoadBalancer` type on local sometimes hangs pending IP.
 - **Fix**: Not a problem, we can use `port-forward` or access via `localhost`.

## 8. Test Service Access

To access the service from your local machine, the safest way is `port-forward`.

```bash
# Port forward from local port 5002 to service port 80
kubectl port-forward svc/iam-service 5002:80 -n iam-local

# Terminal will hang and show: Forwarding from 127.0.0.1:5002 -> 8000
```

Open another terminal and test:

```bash
# Test Health Check
curl http://localhost:5002/health/live
# Response: {"status":"ok", ...}

# View Swagger/OpenAPI docs (if enabled)
open http://localhost:5002/api-docs
```

## 9. Cleanup

When done, delete resources to free up capacity.

```bash
# Delete the namespace (removes all resources within)
kubectl delete namespace iam-local
```

## Quick Tips

### Common Issues & Solutions

| Issue | Symptoms | Solution |
|-------|----------|----------|
| **ImagePullBackOff** | Pod stuck in `ImagePullBackOff` | Set `imagePullPolicy: Never` in deployment YAML<br/>Run `kind load docker-image` if using Kind |
| **CrashLoopBackOff** | Pod keeps restarting | Check logs with `kubectl logs`<br/>Verify `DATABASE_URL` in secrets<br/>Ensure DB is accessible via `host.docker.internal` |
| **Pending LoadBalancer** | Service External-IP shows `<pending>` | Normal on local—use `kubectl port-forward` instead |
| **Connection Refused** | Can't connect after port-forward | Check pod is `Running` (not `CrashLoopBackOff`)<br/>Verify correct port mapping |

### Essential Commands Reference

```bash
# Debugging
kubectl get pods -n iam-local -w # Watch pods in real-time
kubectl describe pod <POD_NAME> -n iam-local # Detailed pod info + events
kubectl logs -f <POD_NAME> -n iam-local # Stream logs
kubectl exec -it <POD_NAME> -n iam-local -- /bin/sh # Shell into container

# Image Management
docker build -t service:local -f Dockerfile . # Build image
kind load docker-image service:local --name desktop # Load to Kind cluster
docker images | grep service # List local images

# Quick Deployment
kubectl apply -f deployment.yaml -n iam-local # Deploy single manifest
kubectl apply -f . -n iam-local # Deploy all files in directory
kubectl rollout restart deployment/iam-service -n iam-local # Force pod restart

# Cleanup
kubectl delete pod <POD_NAME> -n iam-local --force # Force delete stuck pod
kubectl delete namespace iam-local --force --grace-period=0 # Force delete namespace
```

### Visual Status Indicators

- **Running** - Service is healthy
- **Pending** - Waiting for resources
- **CrashLoopBackOff** - Service keeps failing (check logs!)
- **ContainerCreating** - Pod starting up
- **Terminating** - Pod shutting down

### Pro Tips

1. ** Fast Rebuild**: For quick iteration, use `kubectl rollout restart deployment/iam-service -n iam-local` after rebuilding image
2. ** Watch Mode**: Use `-w` flag to watch resources update in real-time
3. ** YAML Validation**: Run `kubectl apply --dry-run=client -f file.yaml` to validate before applying
4. ** Label Filtering**: Use `-l app=iam-service` to filter resources by label instead of typing pod names


## References

- [Kubernetes Documentation](https://kubernetes.io/docs/)
- [Docker Desktop for Mac](https://docs.docker.com/desktop/mac/networking/)
- [Prisma Deployment Guide](https://www.prisma.io/docs/guides/deployment/deployment-guides/deploying-to-kubernetes)
