Deploy Temporal | Durable Workflows, No Elasticsearch
Durable workflows on Postgres alone - 650 MiB less than the ES version
temporal-auth
Just deployed
postgres
Just deployed
/var/lib/postgresql/data
temporal-ui
Just deployed
temporal
Just deployed
Deploy and Host Temporal on Railway
Temporal is a durable execution platform: you write ordinary code, and Temporal makes it survive crashes, restarts and deploys. Workflows keep their state, retries are automatic, and long-running processes can span days or months without you building a job table and a cron to babysit it.
About Hosting Temporal
Most self-hosted Temporal setups run Elasticsearch next to Postgres, because that is what the
official docker-compose files do. Elasticsearch is there for one feature: advanced visibility —
custom search attributes and filtered workflow queries such as OrderTier="gold".
Since Temporal 1.20 that feature works on PostgreSQL 12+ directly. Elasticsearch is no longer required for it, but the popular Railway templates still ship it, and it is by far the largest thing in the project.
This template drops Elasticsearch and keeps the feature. Measured on Railway on 2026-08-15, both stacks idling in the same environment on the same plan:
| this template | the most-deployed Temporal template | |
|---|---|---|
| Postgres | 239 MiB | 154 MiB |
| Temporal | 668 MiB | 75 MiB |
| Elasticsearch | — | 1305 MiB |
| UI (+ auth proxy) | 9 MiB | 31 MiB |
| Total | 915 MiB | 1565 MiB |
650 MiB less, about 42%, and the Elasticsearch container alone is larger than this entire stack. Two things to be straight about: the other template's Temporal container was still warming up when it was sampled, so the real gap is a little smaller than the table suggests; and this template's Temporal runs heavier on purpose (see history shards below).
Verified on the running deploy, not assumed:
operator cluster healthreportsSERVING- a custom search attribute (
OrderTier, Keyword) was created against Postgres - two workflows were started and
workflow list --query 'OrderTier="gold"'returned exactly one
Why Deploy Temporal on Railway?
Railway runs all four pieces — server, database, UI and the TCP proxy your workers dial — as one project with private networking already wired, so there is no compose file, no VPC and no TLS plumbing to write. Because this template leaves Elasticsearch out, it fits comfortably on the small plans where a 1.5 GB stack would not, and the bill tracks a Postgres rather than a JVM search cluster you were only using to filter a list.
Common Use Cases
- Background jobs that must not be lost when a container restarts mid-run
- Multi-step order, payment, onboarding or provisioning flows with automatic retries
- Long-running processes measured in hours, days or months (trials, dunning, scheduled reminders)
- Orchestrating calls across several services or third-party APIs, with each step made durable
- Replacing a hand-rolled jobs table, cron and retry loop with something that keeps its own state
Dependencies for Temporal Hosting
- PostgreSQL 12 or newer — included in this template, with a volume. Holds both the main and the visibility database.
- A Railway TCP proxy on the Temporal service — included, and how workers outside Railway reach gRPC on port 7233.
- Nothing else. There is deliberately no Elasticsearch, and no external message broker or cache.
Deployment Dependencies
- Temporal server (
temporalio/auto-setup) - Temporal Web UI (
temporalio/ui) - PostgreSQL
- Temporal self-hosting documentation
- Visibility on SQL databases
What you get
- Temporal server (
temporalio/auto-setup, pinned) — schema setup and thedefaultnamespace are created on first boot, so there is no manualtemporal-sql-toolstep. - PostgreSQL with a volume, holding both the main and the visibility database.
- Temporal Web UI, private, reachable only through the auth proxy below.
- Basic-auth proxy on the public HTTPS URL, with a generated 32-character password. The UI is never exposed unauthenticated — anyone who finds the URL without credentials gets a 401, and the Web UI can terminate workflows, so this is not optional.
- gRPC on a Railway TCP proxy, so real workers outside Railway can connect.
Settings chosen for you, and why
NUM_HISTORY_SHARDS=512. This value cannot be changed after the cluster is created — changing it means starting over and losing your workflow history. Temporal's own default in the container image is 4, which is fine for a demo and a wall you hit later. 512 is Temporal's production recommendation. It costs some memory now and saves a migration you cannot do.BIND_ON_IP=::. Railway's private network is IPv6-only; binding IPv4 leaves the UI unable to reach the server, and because Temporal has no HTTP healthcheck the deploy still goes green while the cluster is dead.- Retention 72h on the default namespace, so a busy queue does not quietly grow the database forever. Raise it per namespace when you know what you need.
Connecting a worker
Your workers connect over the TCP proxy, not the web URL:
temporal --address : operator cluster health
Both values are on the Temporal service's Variables tab. In an SDK, that host:port is the
Client target address, and the namespace is default.
Custom search attributes
temporal --address operator search-attribute create --name OrderTier --type Keyword
temporal --address workflow list --query 'OrderTier="gold"'
No Elasticsearch involved — these are columns in Postgres.
When you should use the Elasticsearch version instead
Honest limits. SQL visibility is the right default, not the right answer for everyone:
- Very high workflow volume. Elasticsearch scales visibility queries past what a single Postgres will comfortably serve.
- Full-text search over
Textsearch attributes with real relevance ranking. - An existing Elasticsearch you already run and want Temporal to index into.
For the great majority of Railway deploys — internal tooling, background jobs, order and onboarding flows — Postgres visibility is enough, and the 1.3 GB is pure cost.
Template Content
temporal-auth
ghcr.io/brody192/railway-caddy-basic-auth:mainUSERNAME
postgres
postgres:18.4-alpinePGDATA
POSTGRES_DB
POSTGRES_USER
temporal-ui
temporalio/ui:2.53.1TEMPORAL_UI_PORT
TEMPORAL_UI_PUBLIC_PATH
TEMPORAL_NOTIFY_ON_NEW_VERSION
temporal
temporalio/auto-setup:1.29.7DB
PORT
DBNAME
DB_PORT
ENABLE_ES
BIND_ON_IP
POSTGRES_USER
SKIP_DB_CREATE
TEMPORAL_ADDRESS
DEFAULT_NAMESPACE
VISIBILITY_DBNAME
NUM_HISTORY_SHARDS
TEMPORAL_BROADCAST_ADDRESS
DEFAULT_NAMESPACE_RETENTION
SKIP_DEFAULT_NAMESPACE_CREATION