---
title: "Deploy PeerDB | (Just Updated) Postgres To ClickHouse CDC, Login On From Boot"
description: "PeerDB Postgres to ClickHouse CDC. Login on from boot, catalog kept"
category: "Storage"
url: https://railway.com/deploy/peerdb-or-just-updated-postgres-to-click
---

# Deploy PeerDB | (Just Updated) Postgres To ClickHouse CDC, Login On From Boot

PeerDB Postgres to ClickHouse CDC. Login on from boot, catalog kept

**[Deploy PeerDB | (Just Updated) Postgres To ClickHouse CDC, Login On From Boot on Railway](https://railway.com/template/peerdb-or-just-updated-postgres-to-click)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/peerdb-or-just-updated-postgres-to-click/manifest.json

- **Creator:** SuperSlowSloth
- **Category:** Storage

## Template content

### temporal

- **Image:** temporalio/auto-setup:1.29.7
- **Start command:** `/bin/sh -c 'export DB=postgres12 DB_PORT=5432 POSTGRES_USER=postgres DBNAME=temporal VISIBILITY_DBNAME=temporal_visibility ENABLE_ES=false DEFAULT_NAMESPACE=default DEFAULT_NAMESPACE_RETENTION=72h DYNAMIC_CONFIG_FILE_PATH=config/peerdb-dynamic.yaml PORT=7233 BIND_ON_IP=:: TEMPORAL_BROADCAST_ADDRESS=127.0.0.1 TEMPORAL_ADDRESS=127.0.0.1:7233; printf "limit.maxIDLength:\n  - value: 255\n    constraints: {}\nfrontend.enableUpdateWorkflowExecution:\n  - value: true\n    constraints: {}\n" > /etc/temporal/config/peerdb-dynamic.yaml; ( until temporal operator search-attribute list --namespace default 2>/dev/null | grep -qw MirrorName || temporal operator search-attribute create --namespace default --name MirrorName --type Text >/dev/null 2>&1; do sleep 5; done; echo "[railway] MirrorName search attribute present" ) & exec /etc/temporal/entrypoint.sh autosetup'`

### flow-api

- **Image:** ghcr.io/peerdb-io/flow-api:stable-v0.35.5
- **Start command:** `/bin/sh -c 'export PEERDB_CATALOG_PORT=5432 PEERDB_CATALOG_USER=postgres PEERDB_CATALOG_DATABASE=postgres; export PEERDB_TEMPORAL_NAMESPACE=default PEERDB_CLICKHOUSE_AWS_CREDENTIALS_AWS_REGION=us-east-1 PEERDB_CLICKHOUSE_AWS_S3_BUCKET_NAME=peerdbbucket; until nc -z "${TEMPORAL_HOST_PORT%:*}" "${TEMPORAL_HOST_PORT##*:}"; do echo "[railway] waiting for temporal"; sleep 3; done; exec ./peer-flow api --port 8112 --gateway-port 8113'`

### flow-snapshot-worker

- **Image:** ghcr.io/peerdb-io/flow-snapshot-worker:stable-v0.35.5
- **Start command:** `/bin/sh -c 'export PEERDB_CATALOG_PORT=5432 PEERDB_CATALOG_USER=postgres PEERDB_CATALOG_DATABASE=postgres; export PEERDB_TEMPORAL_NAMESPACE=default PEERDB_CLICKHOUSE_AWS_CREDENTIALS_AWS_REGION=us-east-1 PEERDB_CLICKHOUSE_AWS_S3_BUCKET_NAME=peerdbbucket; until nc -z "${TEMPORAL_HOST_PORT%:*}" "${TEMPORAL_HOST_PORT##*:}"; do echo "[railway] waiting for temporal"; sleep 3; done; exec ./peer-flow snapshot-worker'`

### minio

- **Image:** pgsty/silo:RELEASE.2026-09-16T00-00-00Z
- **Start command:** `/bin/sh -c 'mkdir -p /data/peerdbbucket; exec silo server /data --address :9000 --console-address :9001'`

### peerdb

- **Image:** ghcr.io/brody192/railway-caddy-basic-auth:main
- **Public domain:** Yes

### catalog

- **Image:** postgres:18.4-alpine
- **Start command:** `/bin/sh -c 'export POSTGRES_USER=postgres POSTGRES_DB=postgres; exec docker-entrypoint.sh postgres -c wal_level=logical -c max_wal_senders=4 -c max_replication_slots=4'`

### peerdb-ui

- **Image:** ghcr.io/peerdb-io/peerdb-ui:stable-v0.35.5
- **Start command:** `/bin/sh -c 'export PEERDB_CATALOG_PORT=5432 PEERDB_CATALOG_USER=postgres PEERDB_CATALOG_DATABASE=postgres; export PORT=3000 HOSTNAME=:: PEERDB_EXPERIMENTAL_ENABLE_SCRIPTING=true; export DATABASE_URL="postgres://postgres:${PEERDB_CATALOG_PASSWORD}@${PEERDB_CATALOG_HOST}:5432/postgres"; exec /app/entrypoint.sh node server.js'`

### flow-worker

- **Image:** ghcr.io/peerdb-io/flow-worker:stable-v0.35.5
- **Start command:** `/bin/sh -c 'export PEERDB_CATALOG_PORT=5432 PEERDB_CATALOG_USER=postgres PEERDB_CATALOG_DATABASE=postgres; export PEERDB_TEMPORAL_NAMESPACE=default PEERDB_CLICKHOUSE_AWS_CREDENTIALS_AWS_REGION=us-east-1 PEERDB_CLICKHOUSE_AWS_S3_BUCKET_NAME=peerdbbucket; until nc -z "${TEMPORAL_HOST_PORT%:*}" "${TEMPORAL_HOST_PORT##*:}"; do echo "[railway] waiting for temporal"; sleep 3; done; exec ./peer-flow worker'`

### peerdb-server

- **Image:** ghcr.io/peerdb-io/peerdb-server:stable-v0.35.5
- **Start command:** `/bin/sh -c 'export PEERDB_CATALOG_PORT=5432 PEERDB_CATALOG_USER=postgres PEERDB_CATALOG_DATABASE=postgres; export RUST_LOG=info PEERDB_HOST=:: PEERDB_PORT=9900; exec ./peerdb-server'`

## Documentation

# Deploy and Host PeerDB on Railway

PeerDB is an open-source data movement engine built for Postgres. It streams a Postgres database
into ClickHouse (and other warehouses and queues) with change data capture, so your analytics copy
stays seconds behind production instead of a nightly dump behind it. You create peers and mirrors
from a web UI or from plain SQL over the Postgres wire protocol.

## About Hosting PeerDB

PeerDB is not one container. A working install is a catalog Postgres, a Temporal server that runs
the replication workflows, an object store for staging files, an API, two workers, the SQL server
on port 9900 and the web UI. The upstream `docker-compose` file wires those together and publishes
the UI on a port with **no login at all** (`authentication is disabled` in the API log). Copy that
to a public host and anyone who finds the URL can add peers holding your database credentials.

This template deploys the whole stack as nine Railway services with private networking already
wired, and puts a basic-auth proxy in front of the UI:

- **`peerdb`** is the only service with a public URL. Username `peerdb`, password generated per
  deploy (`PASSWORD` in that service's variables). Without credentials you get a 401.
- **`peerdb-server`** speaks the Postgres protocol on a Railway TCP proxy. Connect with `psql` using
  the ready-made `PEERDB_CONNECT` variable and run `CREATE PEER` / `CREATE MIRROR` as SQL.
- **`catalog`** is Postgres with `wal_level=logical` and a volume, so peers and mirror definitions
  survive redeploys.
- **`temporal`** runs on the same catalog Postgres, with the `MirrorName` search attribute PeerDB
  needs created automatically on boot, and dynamic config applied.
- **`minio`** (a community fork of MinIO, with a volume) provides the staging bucket that ClickHouse
  mirrors use; the `peerdbbucket` bucket is created for you.
- **`flow-api`**, **`flow-worker`**, **`flow-snapshot-worker`** and **`peerdb-ui`** are the upstream
  images, pinned to `stable-v0.35.5`. The Temporal UI and the admin-tools sidecar from the compose
  file are left out; the sidecar's one job is done by the Temporal start command.

Verified on a live deploy of this template: unauthenticated requests get 401, authenticated
requests to the UI and the REST API return 200, `psql` over the TCP proxy runs queries, a Postgres
peer is created (which exercises the SQL server, the API and the catalog), and that peer was still
there after the catalog service was redeployed.

What this template does not include is your data. Bring a **source Postgres** with logical
replication enabled (`wal_level=logical`) and a **ClickHouse** destination, then add them as peers.

## Why Deploy PeerDB on Railway?

Five of the nine services are plumbing you would otherwise wire by hand: private hostnames between
the workers, Temporal and the catalog, a staging bucket, a volume on the right path, and a start
order that waits for Temporal before the workers dial it. Railway gives each service a private
address and one project to look at, so there is no compose file and no reverse proxy to write. The
login on the UI is on from the first boot, not something you remember to add later.

## Common Use Cases

- Stream a production Postgres into ClickHouse for dashboards without running queries on the primary
- Keep a real-time analytics copy of an application database in sync with change data capture
- Run an initial snapshot of large tables, then switch to continuous replication
- Move data between Postgres databases or into a queue such as Kafka with SQL-defined mirrors
- Replace a nightly export job with a pipeline you can inspect in a UI

## Dependencies for PeerDB Hosting

- **PostgreSQL** with logical replication, as the source you replicate from (yours; not included)
- **A ClickHouse server** as the destination for the main use case (yours; not included)
- **A catalog PostgreSQL**, **Temporal** and an **S3-compatible bucket** are all included

### Deployment Dependencies

- [PeerDB on GitHub](https://github.com/PeerDB-io/peerdb)
- [PeerDB documentation](https://docs.peerdb.io/)
- [Temporal (`temporalio/auto-setup`)](https://hub.docker.com/r/temporalio/auto-setup)
- [PostgreSQL](https://hub.docker.com/_/postgres)
- [Postgres logical replication setup for PeerDB](https://docs.peerdb.io/connect/rds_postgres)

## What you get

- **Web UI** at the public URL, behind basic auth (`peerdb` / generated password).
- **SQL interface** on a Railway TCP proxy, with the `psql` command in `PEERDB_CONNECT`.
- **Catalog Postgres** on a volume, **staging bucket** on a volume.
- **Pinned images** (`stable-v0.35.5`), so a redeploy does not change the version underneath you.


## Similar templates

- [TimescaleDB](https://railway.com/deploy/timescaledb-railway-template) — A PostgreSQL-based time-series database for analytics on Railway.
- [Garage S3 Storage](https://railway.com/deploy/garage-s3-storage) — Ultra-light S3 server: fast, open-source, plug-and-play.
- [Redis](https://railway.com/deploy/redis-1) — Self Host Latest Redis with Railway

Open this page in a browser: https://railway.com/deploy/peerdb-or-just-updated-postgres-to-click
