Skip to main content
This guide covers deploying Temporal Server using Docker, from local development setups to production-ready containerized deployments.

Quick Start

The fastest way to get Temporal running locally:
brew install temporal
temporal server start-dev
This starts a server with in-memory SQLite storage suitable for development.

Docker Image

Temporal provides official Docker images:
docker pull temporalio/server:latest

Image Details

The server image is based on Alpine Linux and includes:
  • temporal-server binary in /usr/local/bin/
  • Entrypoint script at /etc/temporal/entrypoint.sh
  • Non-root user temporal (UID 1000, GID 1000)

Docker Compose Setup

Development Environment

For local development with all dependencies:
1

Create docker-compose.yml

docker-compose.yml
services:
  mysql:
    image: mysql:8.0.29-oracle
    container_name: temporal-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - ./mysql-init:/docker-entrypoint-initdb.d
    networks:
      - temporal-network
  
  elasticsearch:
    image: elasticsearch:7.10.1
    container_name: temporal-elasticsearch
    ports:
      - "9200:9200"
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms100m -Xmx100m
    networks:
      - temporal-network
  
  temporal:
    image: temporalio/server:latest
    container_name: temporal-server
    depends_on:
      - mysql
    ports:
      - "7233:7233"
      - "7243:7243"
      - "8000:8000"
    environment:
      - DB=mysql8
      - MYSQL_SEEDS=mysql
      - MYSQL_USER=temporal
      - MYSQL_PWD=temporal
      - ENABLE_ES=false
      - DYNAMIC_CONFIG_FILE_PATH=/etc/temporal/config/dynamicconfig/docker.yaml
    volumes:
      - ./config:/etc/temporal/config
    networks:
      - temporal-network
  
  temporal-ui:
    image: temporalio/ui:latest
    container_name: temporal-ui
    depends_on:
      - temporal
    ports:
      - "8233:8080"
    environment:
      - TEMPORAL_ADDRESS=temporal:7233
    networks:
      - temporal-network

networks:
  temporal-network:
    driver: bridge
2

Create MySQL initialization script

mysql-init/init.sql
CREATE USER 'temporal'@'%' IDENTIFIED BY 'temporal';
GRANT ALL PRIVILEGES ON *.* TO 'temporal'@'%';
FLUSH PRIVILEGES;
3

Start services

docker-compose up -d
4

Initialize database schema

# Create databases
docker exec temporal-server temporal-sql-tool \
  --ep mysql --port 3306 \
  --user temporal --password temporal \
  --db temporal --plugin mysql8 create

docker exec temporal-server temporal-sql-tool \
  --ep mysql --port 3306 \
  --user temporal --password temporal \
  --db temporal_visibility --plugin mysql8 create

# Setup schema
docker exec temporal-server temporal-sql-tool \
  --ep mysql --port 3306 \
  --user temporal --password temporal \
  --db temporal --plugin mysql8 \
  setup-schema -v 0.0

docker exec temporal-server temporal-sql-tool \
  --ep mysql --port 3306 \
  --user temporal --password temporal \
  --db temporal --plugin mysql8 \
  update-schema --schema-name mysql/v8/temporal

# Repeat for visibility
docker exec temporal-server temporal-sql-tool \
  --ep mysql --port 3306 \
  --user temporal --password temporal \
  --db temporal_visibility --plugin mysql8 \
  setup-schema -v 0.0

docker exec temporal-server temporal-sql-tool \
  --ep mysql --port 3306 \
  --user temporal --password temporal \
  --db temporal_visibility --plugin mysql8 \
  update-schema --schema-name mysql/v8/visibility

Access the UI

Open http://localhost:8233 to access Temporal Web UI.

Environment Variables

The Docker image supports configuration via environment variables:

Database Configuration

DB=mysql8
MYSQL_SEEDS=mysql-host
MYSQL_USER=temporal
MYSQL_PWD=temporal
DB_PORT=3306
DBNAME=temporal
VISIBILITY_DBNAME=temporal_visibility

# Connection pool
SQL_MAX_CONNS=20
SQL_MAX_IDLE_CONNS=20
SQL_MAX_CONN_TIME=1h

# TLS (optional)
SQL_TLS_ENABLED=false
SQL_CA=/path/to/ca.pem
SQL_CERT=/path/to/cert.pem
SQL_CERT_KEY=/path/to/key.pem

Elasticsearch Configuration

ENABLE_ES=true
ES_VERSION=v7
ES_SCHEME=http
ES_SEEDS=elasticsearch-host
ES_PORT=9200
ES_USER=elastic
ES_PWD=changeme
ES_VIS_INDEX=temporal_visibility_v1_dev

Service Configuration

# History shards
NUM_HISTORY_SHARDS=4

# Service ports
FRONTEND_GRPC_PORT=7233
FRONTEND_HTTP_PORT=7243
FRONTEND_MEMBERSHIP_PORT=6933

HISTORY_GRPC_PORT=7234
HISTORY_MEMBERSHIP_PORT=6934

MATCHING_GRPC_PORT=7235
MATCHING_MEMBERSHIP_PORT=6935

WORKER_GRPC_PORT=7239
WORKER_MEMBERSHIP_PORT=6939

# Bind address
BIND_ON_IP=0.0.0.0
TEMPORAL_BROADCAST_ADDRESS=

Logging and Metrics

# Logging
LOG_LEVEL=info

# Prometheus metrics
PROMETHEUS_ENDPOINT=0.0.0.0:8000
PROMETHEUS_TIMER_TYPE=histogram

# Or StatsD metrics
STATSD_ENDPOINT=statsd-host:8125

# Profiling
PPROF_PORT=7936

TLS Configuration

# Internode TLS
TEMPORAL_TLS_REQUIRE_CLIENT_AUTH=true
TEMPORAL_TLS_SERVER_CERT=/certs/server-cert.pem
TEMPORAL_TLS_SERVER_KEY=/certs/server-key.pem
TEMPORAL_TLS_SERVER_CA_CERT=/certs/ca-cert.pem
TEMPORAL_TLS_INTERNODE_SERVER_NAME=temporal-server

# Frontend TLS
TEMPORAL_TLS_FRONTEND_CERT=/certs/frontend-cert.pem
TEMPORAL_TLS_FRONTEND_KEY=/certs/frontend-key.pem
TEMPORAL_TLS_CLIENT1_CA_CERT=/certs/client-ca-cert.pem
TEMPORAL_TLS_FRONTEND_SERVER_NAME=temporal-frontend

Production Deployment

Multi-Container Setup

For production, run separate containers for each service:
services:
  temporal-frontend:
    image: temporalio/server:latest
    command:
      - /usr/local/bin/temporal-server
      - start
      - --service
      - frontend
    environment:
      - SERVICES=frontend
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '2'
          memory: 4G
    networks:
      - temporal-network
  
  temporal-history:
    image: temporalio/server:latest
    command:
      - /usr/local/bin/temporal-server
      - start
      - --service
      - history
    environment:
      - SERVICES=history
      - NUM_HISTORY_SHARDS=512
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '4'
          memory: 8G
    networks:
      - temporal-network
  
  temporal-matching:
    image: temporalio/server:latest
    command:
      - /usr/local/bin/temporal-server
      - start
      - --service
      - matching
    environment:
      - SERVICES=matching
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '2'
          memory: 4G
    networks:
      - temporal-network
  
  temporal-worker:
    image: temporalio/server:latest
    command:
      - /usr/local/bin/temporal-server
      - start
      - --service
      - worker
    environment:
      - SERVICES=worker
    deploy:
      replicas: 1
      resources:
        limits:
          cpus: '2'
          memory: 4G
    networks:
      - temporal-network

networks:
  temporal-network:
    driver: overlay

Health Checks

Add health checks to your containers:
healthcheck:
  test: ["CMD", "/usr/local/bin/temporal-server", "health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

Volume Mounts

Mount configuration and TLS certificates:
volumes:
  - ./config/production.yaml:/etc/temporal/config/config.yaml:ro
  - ./certs:/etc/temporal/certs:ro
  - ./dynamicconfig:/etc/temporal/config/dynamicconfig:ro

Dockerfile Reference

The official Temporal Server Dockerfile:
ARG ALPINE_TAG=3.23.3
FROM alpine:${ALPINE_TAG}

ARG TARGETARCH

RUN apk add --no-cache \
    ca-certificates \
    tzdata && addgroup -g 1000 temporal && \
    adduser -u 1000 -G temporal -D temporal

COPY --chmod=755 ./build/${TARGETARCH}/temporal-server /usr/local/bin/
COPY --chmod=755 ./scripts/sh/entrypoint.sh /etc/temporal/entrypoint.sh

WORKDIR /etc/temporal
USER temporal

CMD [ "/etc/temporal/entrypoint.sh" ]

Entrypoint Script

#!/bin/sh
set -eu -o pipefail

# Resolve hostname to IP address if not already set
: "${BIND_ON_IP:=$(getent hosts "$(hostname)" | awk '{print $1;}')}" 
export BIND_ON_IP

# Set broadcast address for wildcard binds
if [ "${BIND_ON_IP}" = "0.0.0.0" ] || [ "${BIND_ON_IP}" = "::0" ]; then
    : "${TEMPORAL_BROADCAST_ADDRESS:=$(getent hosts "$(hostname)" | awk '{print $1;}')}" 
    export TEMPORAL_BROADCAST_ADDRESS
fi

exec temporal-server start

Common Issues

Symptoms: Container fails to start with database connection errorsSolutions:
  • Ensure database is running and accessible
  • Verify network connectivity between containers
  • Check database credentials in environment variables
  • Initialize schema using temporal-sql-tool or temporal-cassandra-tool
Symptoms: “Address already in use” errorsSolutions:
  • Check for other services using the same ports
  • Modify port mappings in docker-compose.yml
  • Use environment variables to change default ports
Symptoms: Container OOM killed or high CPU usageSolutions:
  • Increase Docker memory limits
  • Reduce NUM_HISTORY_SHARDS for development
  • Lower database connection pool sizes
  • Scale services horizontally
For Kubernetes deployments, see the official Temporal Helm charts.

Build docs developers (and LLMs) love