---
title: "Deploy Temporal Production"
description: "Self-hosted Temporal cluster with PostgreSQL and a protected Web UI."
category: "Queues"
url: https://railway.com/deploy/temporal-production
---

# Deploy Temporal Production

Self-hosted Temporal cluster with PostgreSQL and a protected Web UI.

**[Deploy Temporal Production on Railway](https://railway.com/template/temporal-production)**

- **Creator:** Omry atia's Projects
- **Category:** Queues
- **Total deploys:** 2

## Template content

### temporal-ui https://avatars.githubusercontent.com/u/56493103?s=200&v=4

- **Image:** temporalio/ui:2.53.3

### temporal-schema https://avatars.githubusercontent.com/u/56493103?s=200&v=4

- **Image:** temporalio/admin-tools:1.31.2
- **Start command:** `/bin/sh -c 'set -eu; until nc -z "$POSTGRES_SEEDS" "$DB_PORT"; do echo "Waiting for PostgreSQL..."; sleep 2; done; temporal-sql-tool --plugin postgres12 --ep "$POSTGRES_SEEDS" -u "$POSTGRES_USER" -p "$DB_PORT" --db "$DBNAME" create || true; temporal-sql-tool --plugin postgres12 --ep "$POSTGRES_SEEDS" -u "$POSTGRES_USER" -p "$DB_PORT" --db "$DBNAME" setup-schema -v 0.0 || true; temporal-sql-tool --plugin postgres12 --ep "$POSTGRES_SEEDS" -u "$POSTGRES_USER" -p "$DB_PORT" --db "$DBNAME" update-schema -d /etc/temporal/schema/postgresql/v12/temporal/versioned; temporal-sql-tool --plugin postgres12 --ep "$POSTGRES_SEEDS" -u "$POSTGRES_USER" -p "$DB_PORT" --db "$VISIBILITY_DBNAME" create || true; temporal-sql-tool --plugin postgres12 --ep "$POSTGRES_SEEDS" -u "$POSTGRES_USER" -p "$DB_PORT" --db "$VISIBILITY_DBNAME" setup-schema -v 0.0 || true; temporal-sql-tool --plugin postgres12 --ep "$POSTGRES_SEEDS" -u "$POSTGRES_USER" -p "$DB_PORT" --db "$VISIBILITY_DBNAME" update-schema -d /etc/temporal/schema/postgresql/v12/visibility/versioned; printf "#!/bin/sh\nprintf '\''HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\nConnection: close\\r\\n\\r\\nOK'\''\n" >/tmp/health-handler.sh; chmod +x /tmp/health-handler.sh; echo "Schema ready; serving healthcheck on port $PORT"; exec nc -lk -s :: -p "$PORT" -e /tmp/health-handler.sh'`
- **Health check:** /healthz

### temporal-ui-auth https://avatars.githubusercontent.com/u/12955528?s=200&v=4

- **Image:** caddy:2.11.4-alpine
- **Start command:** `/bin/sh -c 'set -eu; HASH=$(caddy hash-password --plaintext "$UI_PASSWORD"); printf ":%s {\n handle /healthz {\n  respond \"OK\" 200\n }\n handle {\n  basic_auth {\n   %s %s\n  }\n  reverse_proxy %s\n  header {\n   X-Content-Type-Options nosniff\n   X-Frame-Options DENY\n   Referrer-Policy no-referrer\n  }\n }\n}\n" "$PORT" "$UI_USERNAME" "$HASH" "$UI_UPSTREAM" >/tmp/Caddyfile; exec caddy run --config /tmp/Caddyfile --adapter caddyfile'`
- **Health check:** /healthz
- **Public domain:** Yes

### temporal-bootstrap https://avatars.githubusercontent.com/u/56493103?s=200&v=4

- **Image:** temporalio/admin-tools:1.31.2
- **Start command:** `/bin/sh -c 'set -eu; until temporal operator cluster health --address "$TEMPORAL_ADDRESS" >/dev/null 2>&1; do echo "Waiting for Temporal cluster..."; sleep 3; done; if ! temporal operator namespace describe --address "$TEMPORAL_ADDRESS" --namespace "$DEFAULT_NAMESPACE" >/dev/null 2>&1; then temporal operator namespace create --address "$TEMPORAL_ADDRESS" --namespace "$DEFAULT_NAMESPACE" --retention "$DEFAULT_NAMESPACE_RETENTION"; fi; printf "#!/bin/sh\nprintf '\''HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\nConnection: close\\r\\n\\r\\nOK'\''\n" >/tmp/health-handler.sh; chmod +x /tmp/health-handler.sh; echo "Bootstrap ready; serving healthcheck on port $PORT"; exec nc -lk -s :: -p "$PORT" -e /tmp/health-handler.sh'`
- **Health check:** /healthz

### temporal-system-worker https://avatars.githubusercontent.com/u/56493103?s=200&v=4

- **Image:** temporalio/server:1.31.2
- **Start command:** `/bin/sh -c "set -eu; printf '{}\n' > \"\$DYNAMIC_CONFIG_FILE_PATH\"; BIND_ON_IP=\$(getent hosts \$(hostname) | awk 'NR==1 {print \$1}'); export BIND_ON_IP; export TEMPORAL_BROADCAST_ADDRESS=\$BIND_ON_IP; exec temporal-server start --service worker"`

### temporal-db https://avatars.githubusercontent.com/u/177543?s=200&v=4

- **Image:** ghcr.io/railwayapp-templates/postgres-ssl:18

### temporal-matching https://avatars.githubusercontent.com/u/56493103?s=200&v=4

- **Image:** temporalio/server:1.31.2
- **Start command:** `/bin/sh -c "set -eu; printf '{}\n' > \"\$DYNAMIC_CONFIG_FILE_PATH\"; BIND_ON_IP=\$(getent hosts \$(hostname) | awk 'NR==1 {print \$1}'); export BIND_ON_IP; export TEMPORAL_BROADCAST_ADDRESS=\$BIND_ON_IP; exec temporal-server start --service matching"`

### temporal-history https://avatars.githubusercontent.com/u/56493103?s=200&v=4

- **Image:** temporalio/server:1.31.2
- **Start command:** `/bin/sh -c "set -eu; printf '{}\n' > \"\$DYNAMIC_CONFIG_FILE_PATH\"; BIND_ON_IP=\$(getent hosts \$(hostname) | awk 'NR==1 {print \$1}'); export BIND_ON_IP; export TEMPORAL_BROADCAST_ADDRESS=\$BIND_ON_IP; exec temporal-server start --service history"`

### temporal-frontend https://avatars.githubusercontent.com/u/56493103?s=200&v=4

- **Image:** temporalio/server:1.31.2
- **Start command:** `/bin/sh -c "set -eu; printf '{}\n' > \"\$DYNAMIC_CONFIG_FILE_PATH\"; BIND_ON_IP=\$(getent hosts \$(hostname) | awk 'NR==1 {print \$1}'); export BIND_ON_IP; export TEMPORAL_BROADCAST_ADDRESS=\$BIND_ON_IP; exec temporal-server start --service frontend"`

## Documentation

# Deploy and Host Temporal Production on Railway

Temporal Production is a self-hosted Temporal cluster for running durable, fault-tolerant applications on Railway. It preserves Workflow state, timers, retries, and event histories through failures and restarts. The template includes independently scalable Temporal services, PostgreSQL persistence, automatic schema initialization, Namespace creation, health checks, and an authenticated Web UI.

## About Hosting Temporal Production

Hosting Temporal Production requires multiple coordinated services rather than a single development server. This template deploys the Temporal Frontend, History, Matching, and System Worker roles as independent Railway services connected through private networking. PostgreSQL provides durable persistence for Workflow state, event history, Namespace metadata, and Visibility records. Dedicated setup services initialize and upgrade the required database schemas and create the default Namespace. A private Temporal Web UI is exposed through a Basic Auth-protected gateway. Application-specific Workers are deployed separately and connect to the private Frontend endpoint. Operators remain responsible for backups, monitoring, resource sizing, retention policies, and controlled upgrades.

## Common Use Cases

* Orchestrate durable AI, UGC, image, audio, and video-generation pipelines that may run for minutes or hours.
* Coordinate long-running business processes such as approvals, payments, onboarding, fulfillment, and data synchronization.
* Run reliable background jobs, schedules, retries, timeout handling, and multi-service Workflows without losing progress after failures.

## Dependencies for Temporal Production Hosting

* PostgreSQL with persistent storage for Workflow state, event history, Namespace metadata, and Visibility records. PostgreSQL is included and configured by this template.
* Application Workers created with a supported Temporal SDK. Workers contain your application-specific Workflows and Activities and must be deployed separately.
* Railway private networking for communication between Temporal services, PostgreSQL, the Web UI, and application Workers.

### Implementation Details

The template deploys the following services:

* `temporal-db`: PostgreSQL persistence and advanced Visibility storage.
* `temporal-schema`: Creates and upgrades the Temporal database schemas.
* `temporal-frontend`: Private gRPC endpoint for SDK clients, Workers, CLI tools, and the Web UI.
* `temporal-history`: Maintains Workflow state, event histories, timers, and state transitions.
* `temporal-matching`: Routes Workflow, Activity, and Nexus tasks to application Workers.
* `temporal-system-worker`: Runs Temporal internal system Workflows and maintenance tasks.
* `temporal-bootstrap`: Waits for cluster readiness and creates the default Namespace.
* `temporal-ui`: Provides the Temporal Web UI for inspecting Workflows, schedules, failures, and event histories.
* `temporal-ui-auth`: Protects the Web UI with HTTP Basic Authentication.

Application Workers deployed in the same Railway project should use the following configuration:

`TEMPORAL_ADDRESS=${{temporal-frontend.RAILWAY_PRIVATE_DOMAIN}}:7233`

`TEMPORAL_NAMESPACE=default`

The Temporal Frontend remains private by default. Only the authenticated Web UI gateway requires a public Railway domain.

The template automatically generates database and Web UI passwords, initializes the persistence and Visibility schemas, creates the configured default Namespace, and configures deployment health checks.

Before running critical production workloads, configure PostgreSQL backups, monitoring and alerts, suitable service resource limits, Namespace retention, and a tested sequential upgrade procedure.

## Why Deploy Temporal Production on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Temporal Production on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.


## Similar templates

- [smoothmq](https://railway.com/deploy/AJv-64) — A drop-in replacement for AWS SQS
- [Kafka UI](https://railway.com/deploy/kafka-ui) — Kafbat UI — Open-source web UI to monitor and manage Apache Kafka clusters
- [Hatchet Lite](https://railway.com/deploy/hatchet-lite) — Hatchet Lite with postgres

Open this page in a browser: https://railway.com/deploy/temporal-production
