Temporal
Durable workflow execution platform for building reliable distributed applications:
- Durable Execution: Workflows survive process and infrastructure failures
- Language Support: SDKs for Go, Java, Python, TypeScript, .NET, PHP
- Visibility: Query and observe workflow state via Web UI and APIs
- Scalability: Horizontally scalable architecture
- Multi-tenancy: Namespace-based isolation for workflows
Prerequisites
- Kubernetes cluster (k3s)
- PostgreSQL cluster (CloudNativePG)
- Keycloak installed and configured
- Vault for secrets management
- External Secrets Operator (optional, for Vault integration)
Installation
just temporal::install
You will be prompted for:
- Temporal host (FQDN): e.g.,
temporal.example.com - Keycloak host (FQDN): e.g.,
auth.example.com - Enable Prometheus monitoring: If kube-prometheus-stack is installed
What Gets Installed
- Temporal Server (frontend, history, matching, worker services)
- Temporal Web UI with Keycloak OIDC authentication
- Temporal Admin Tools for cluster management
- PostgreSQL databases (
temporal,temporal_visibility) - Keycloak OAuth client (confidential client)
- Vault secrets (if External Secrets Operator is available)
Configuration
Environment variables (set in .env.local or override):
| Variable | Default | Description |
|---|---|---|
TEMPORAL_NAMESPACE |
temporal |
Kubernetes namespace |
TEMPORAL_CHART_VERSION |
0.52.0 |
Helm chart version |
TEMPORAL_HOST |
(prompt) | External hostname (FQDN) |
TEMPORAL_OIDC_CLIENT_ID |
temporal |
Keycloak client ID |
KEYCLOAK_HOST |
(prompt) | Keycloak hostname (FQDN) |
KEYCLOAK_REALM |
buunstack |
Keycloak realm |
MONITORING_ENABLED |
(prompt) | Enable Prometheus ServiceMonitor |
Architecture
External Users
|
Cloudflare Tunnel (HTTPS)
|
Traefik Ingress (HTTPS)
|
Temporal Web UI (HTTP inside cluster)
|-- OAuth --> Keycloak (authentication)
|
Temporal Server
|-- Frontend Service (gRPC :7233)
| |-- Client connections
| |-- Workflow/Activity APIs
|
|-- History Service
| |-- Workflow state management
| |-- Event sourcing
|
|-- Matching Service
| |-- Task queue management
| |-- Worker polling
|
|-- Worker Service
| |-- System workflows
| |-- Archival
|
PostgreSQL (temporal, temporal_visibility)
Key Components:
- Frontend: Entry point for all client requests (gRPC API)
- History: Maintains workflow execution history and state
- Matching: Routes tasks to appropriate workers
- Worker: Executes internal system workflows
- Web UI: Browser-based workflow monitoring and management
- Admin Tools: CLI tools for cluster administration
Usage
Access Web UI
- Navigate to
https://your-temporal-host/ - Authenticate via Keycloak SSO
- Select a namespace to view workflows
Temporal CLI Setup (Local Development)
The Temporal gRPC endpoint is only accessible within the cluster network. Use Telepresence to connect from your local machine.
Step 1: Connect to the Cluster
telepresence connect
Step 2: Configure Temporal CLI
Set environment variables (add to .bashrc, .zshrc, or use direnv):
export TEMPORAL_ADDRESS="temporal-frontend.temporal:7233"
export TEMPORAL_NAMESPACE="default"
Or create a named environment for multiple clusters:
# Configure named environment
temporal env set --env buun -k address -v temporal-frontend.temporal:7233
temporal env set --env buun -k namespace -v default
# Use with commands
temporal workflow list --env buun
Step 3: Verify Connection
# Check telepresence status
telepresence status
# Test Temporal connection
temporal operator namespace list
CLI Examples
# List workflows
temporal workflow list
# Describe a workflow
temporal workflow describe --workflow-id my-workflow-id
# Query workflow state
temporal workflow query --workflow-id my-workflow-id --type my-query
# Signal a workflow
temporal workflow signal --workflow-id my-workflow-id --name my-signal
# Terminate a workflow
temporal workflow terminate --workflow-id my-workflow-id --reason "manual termination"
Create a Temporal Namespace
Before running workflows, create a namespace:
just temporal::create-temporal-namespace default
With custom retention period:
just temporal::create-temporal-namespace myapp 7d
List Temporal Namespaces
just temporal::list-temporal-namespaces
Cluster Health Check
just temporal::cluster-info
Connect Workers
Workers connect to the Temporal Frontend service. From within the cluster:
temporal-frontend.temporal:7233
Example Python worker:
from temporalio.client import Client
from temporalio.worker import Worker
async def main():
client = await Client.connect("temporal-frontend.temporal:7233")
worker = Worker(
client,
task_queue="my-task-queue",
workflows=[MyWorkflow],
activities=[my_activity],
)
await worker.run()
Example Go worker:
import (
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
)
func main() {
c, _ := client.Dial(client.Options{
HostPort: "temporal-frontend.temporal:7233",
})
defer c.Close()
w := worker.New(c, "my-task-queue", worker.Options{})
w.RegisterWorkflow(MyWorkflow)
w.RegisterActivity(MyActivity)
w.Run(worker.InterruptCh())
}
Authentication
Web UI (OIDC)
- Users authenticate via Keycloak
- Standard OIDC flow with Authorization Code grant
- Configured via environment variables in the Web UI deployment
gRPC API
- By default, no authentication is required for gRPC connections within the cluster
- For production, configure mTLS or JWT-based authorization
Management
Upgrade Temporal
just temporal::upgrade
Uninstall
just temporal::uninstall
This removes:
- Helm release and all Kubernetes resources
- Namespace
- Keycloak client
Note: The following resources are NOT deleted:
- PostgreSQL databases (
temporal,temporal_visibility) - Vault secrets
Full Cleanup
To remove everything including databases and Vault secrets:
just temporal::uninstall true
Or manually:
just temporal::delete-postgres-user-and-db
Troubleshooting
Check Pod Status
kubectl get pods -n temporal
Expected pods:
temporal-frontend-*- Frontend servicetemporal-history-*- History servicetemporal-matching-*- Matching servicetemporal-worker-*- Worker servicetemporal-web-*- Web UItemporal-admintools-*- Admin tools
View Logs
# Frontend logs
kubectl logs -n temporal deployment/temporal-frontend --tail=100
# History logs
kubectl logs -n temporal deployment/temporal-history --tail=100
# Web UI logs
kubectl logs -n temporal deployment/temporal-web --tail=100
Database Connection Issues
Check PostgreSQL connectivity:
kubectl exec -n temporal deployment/temporal-admintools -- \
psql -h postgres-cluster-rw.postgres -U temporal -d temporal -c "SELECT 1"
Schema Issues
If schema initialization fails, check the schema job:
kubectl logs -n temporal -l app.kubernetes.io/component=schema --all-containers
Service Discovery Issues
Verify services are running:
kubectl get svc -n temporal
Test frontend connectivity from admin tools:
kubectl exec -n temporal deployment/temporal-admintools -- \
tctl cluster health
Web UI Login Issues
Verify Keycloak client configuration:
just keycloak::get-client buunstack temporal
Check Web UI environment variables:
kubectl get deployment temporal-web -n temporal -o jsonpath='{.spec.template.spec.containers[0].env}' | jq
Configuration Files
| File | Description |
|---|---|
temporal-values.gomplate.yaml |
Helm values template |
postgres-external-secret.gomplate.yaml |
PostgreSQL credentials ExternalSecret |
keycloak-auth-external-secret.gomplate.yaml |
Keycloak OIDC credentials ExternalSecret |
Security Considerations
- Pod Security Standards: Namespace configured with baseline enforcement
- Server Security: Temporal server components run with restricted-compliant security contexts
Why Not Restricted?
The namespace cannot use restricted Pod Security Standards due to the Temporal Web UI image (temporalio/ui):
- The image writes configuration files to
./config/docker.yamlat startup - The container's filesystem is owned by root (UID 0)
- When running as non-root user (UID 1000), the container cannot write to these paths
- Error:
unable to create open ./config/docker.yaml: permission denied
The Temporal server components (frontend, history, matching, worker) do meet restricted requirements and run with full security hardening. Only the Web UI component requires baseline.
Server Security Context
Temporal server components (frontend, history, matching, worker) run with:
runAsNonRoot: truerunAsUser: 1000allowPrivilegeEscalation: falseseccompProfile.type: RuntimeDefaultcapabilities.drop: [ALL]