Running Temporal Server on Minikube#

This guide deploys Temporal Server on a local Minikube cluster with PostgreSQL persistence. By the end, you will have the Temporal frontend, Web UI, and CLI all working against a real Kubernetes deployment.

If you need background on what Temporal is, start with Introduction to Temporal.

Prerequisites#

ToolMinimum VersionPurpose
minikube1.32+Local Kubernetes cluster
kubectl1.28+Kubernetes CLI
helm3.14+Package manager for Kubernetes
temporal1.0+Temporal CLI
docker24+Container runtime (minikube driver)

Your machine needs at least 4 CPU cores and 8 GB RAM available to Docker. For minikube driver details, see Minikube Setup and Drivers and Minikube Docker Driver.

Quick Start with Makefile#

The companion repository handles everything:

git clone https://github.com/statherm/temporal-examples.git
cd temporal-examples
make cluster-up temporal-up
make temporal-status

The rest of this article explains what those targets do.

Step-by-Step Setup#

Start Minikube#

minikube start \
  --driver=docker \
  --cpus=4 \
  --memory=8192 \
  --disk-size=20g \
  --kubernetes-version=v1.29.2 \
  --profile=temporal

The --profile=temporal flag creates a named cluster so it does not interfere with other minikube profiles.

Add Helm Repositories#

helm repo add temporal https://temporalio.github.io/helm-charts
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

Install PostgreSQL#

Deploy PostgreSQL first so it is ready when Temporal starts:

kubectl create namespace temporal

helm upgrade --install temporal-postgresql bitnami/postgresql \
  --namespace temporal \
  --set auth.postgresPassword=temporal \
  --set auth.database=temporal \
  --set primary.persistence.size=5Gi \
  --set primary.resources.requests.cpu=250m \
  --set primary.resources.requests.memory=256Mi \
  --wait --timeout 120s

Install Temporal Server#

Create a values file that points Temporal at your PostgreSQL instance and disables bundled databases:

# values-temporal.yaml
server:
  replicaCount: 1
  config:
    persistence:
      default:
        driver: sql
        sql:
          driver: postgres12
          host: temporal-postgresql
          port: 5432
          database: temporal
          user: postgres
          password: temporal
          maxConns: 20
      visibility:
        driver: sql
        sql:
          driver: postgres12
          host: temporal-postgresql
          port: 5432
          database: temporal_visibility
          user: postgres
          password: temporal
          maxConns: 10

cassandra:
  enabled: false
mysql:
  enabled: false
postgresql:
  enabled: false
elasticsearch:
  enabled: false

schema:
  setup:
    enabled: true
  update:
    enabled: true

web:
  replicaCount: 1
  service:
    type: ClusterIP
    port: 8080

We disable Cassandra, MySQL, and the bundled PostgreSQL because we deployed PostgreSQL separately. The schema.setup job creates required tables on first install.

helm upgrade --install temporal temporal/temporal \
  --namespace temporal \
  -f values-temporal.yaml \
  --timeout 600s

Wait for Pods#

kubectl -n temporal rollout status deployment/temporal-frontend --timeout=300s
kubectl -n temporal rollout status deployment/temporal-history --timeout=300s
kubectl -n temporal rollout status deployment/temporal-matching --timeout=300s
kubectl -n temporal rollout status deployment/temporal-worker --timeout=300s

Helm Values Walkthrough#

SettingPurpose
server.replicaCount: 1Single replica per service. See Temporal HA Cluster for production.
persistence.default.sql.driver: postgres12PostgreSQL 12+ wire protocol. Works with PostgreSQL 13-16.
schema.setup.enabled: trueRun schema creation job on install.
elasticsearch.enabled: falseUse SQL-based visibility. Simpler but less powerful for listing workflows.

Accessing Temporal#

Port-forward the Web UI and frontend:

kubectl port-forward -n temporal svc/temporal-web 8080:8080 &
kubectl port-forward -n temporal svc/temporal-frontend 7233:7233 &

Open http://localhost:8080 for the Web UI. Test the CLI:

temporal operator namespace list
temporal operator namespace create --namespace test-ns --retention 72h

Verifying the Installation#

# Check all pods
kubectl get pods -n temporal

# Check cluster health
temporal operator cluster health
# SERVING

# Start a test workflow (no worker needed to verify server accepts it)
temporal workflow start \
  --task-queue test-queue \
  --type HelloWorkflow \
  --workflow-id test-001

temporal workflow describe --workflow-id test-001

The workflow stays “Running” because no worker is registered. See Your First Temporal Workflow in Go to build one.

Teardown#

# Quick (from companion repo)
make cluster-down

# Manual
helm uninstall temporal -n temporal
helm uninstall temporal-postgresql -n temporal
kubectl delete pvc --all -n temporal
kubectl delete namespace temporal
minikube delete --profile=temporal

Troubleshooting#

CrashLoopBackOff on server pods. Usually the schema job did not complete. Check kubectl logs -n temporal job/temporal-schema-setup. If PostgreSQL was not ready, delete the jobs and reinstall:

kubectl delete job -n temporal temporal-schema-setup temporal-schema-update
helm upgrade --install temporal temporal/temporal -n temporal -f values-temporal.yaml

PostgreSQL connection refused. Verify the service name matches your values file: kubectl get svc -n temporal | grep postgresql. The Bitnami chart names the service after the release name directly (e.g., temporal-postgresql, not temporal-postgresql-postgresql).

Pods stuck in Pending. Insufficient resources. Increase minikube allocation:

minikube stop --profile=temporal
minikube start --profile=temporal --cpus=6 --memory=10240

Web UI shows no data. Check frontend logs for gRPC or persistence errors: kubectl logs -n temporal deployment/temporal-frontend --tail=50.

Next Steps#