Deploy Monoscope
API-first monitoring and observability platform for engineers.
monoscope
Just deployed
timescaledb
Just deployed
/var/lib/postgresql
Deploy and Host Monoscope on Railway
Monoscope is an open-source observability platform. It ingests logs, traces, and metrics over HTTP and OpenTelemetry, stores them in TimescaleDB, and lets you query in natural language. This template self-hosts the official compose stack on Railway — the Haskell server (ghcr.io/monoscope-tech/monoscope:latest) plus TimescaleDB 2.29 on PostgreSQL 18.
About Hosting Monoscope
Hosting Monoscope means running two containers on Railway’s private network: a public app (HTTP UI + API on $PORT, OTLP gRPC on 4317) and TimescaleDB for metadata and telemetry. Railway terminates TLS, so the app speaks HTTP inside the container (HOST_URL is still the public https:// origin). You attach one volume — TimescaleDB at /var/lib/postgresql — generate POSTGRES_PASSWORD once on timescaledb, point DATABASE_URL and TIMEFUSION_PG_URL at timescaledb.railway.internal, and let the entrypoint wait over IPv6, then exec monoscope-server. Migrations run on boot (MIGRATE_AND_INITIALIZE_ON_START=True). First boot can take a few minutes. After that, log in with basic auth and send OTLP to the private gRPC port.
Common Use Cases
- Self-host OpenTelemetry for a SaaS or internal platform without sending traces to a third-party vendor
- Keep years of logs, traces, and metrics in your own TimescaleDB volume on Railway
- Natural-language incident search (optional
OPENAI_API_KEY) plus live tail over SSE - MCP / agent workflows against
/api/v1/mcpwith an API key you create in the UI - Private OTLP collection from other Railway services via
monoscope.railway.internal:4317
Dependencies for Monoscope Hosting
- Monoscope image:
ghcr.io/monoscope-tech/monoscope:latest(HTTP UI/API + OTLP gRPC + background jobs) - TimescaleDB 2.29 on PostgreSQL 18 (
timescale/timescaledb:2.29.2-pg18) — metadata, users, and the self-host telemetry store - Railway private networking —
timescaledb.railway.internal:5432(IPv6) - One persistent volume (drive) — see Implementation Details
- Basic auth on the public UI (Auth0 is optional)
Name the database service timescaledb. Set each service Root Directory to its folder (monoscope/, timescaledb/). Keep TimescaleDB private; only monoscope gets a public HTTP domain.
Upstream: Monoscope · Self-host compose · Configuration · License AGPL-3.0
Implementation Details
Clone the template to see how it works:
monoscope/ # public web service
Dockerfile # FROM ghcr.io/monoscope-tech/monoscope:latest
entrypoint.sh # IPv6 wait-for TimescaleDB, then exec monoscope-server
railway.toml # healthcheck /ping, 300s timeout
timescaledb/
Dockerfile # timescale/timescaledb:2.29.2-pg18 + PGDATA
entrypoint.sh # listen_addresses=*, TimescaleDB preload
railway.toml # restart on failure
This kit follows the official docker-compose.yml (TimescaleDB + app). It does not run TimeFusion or MinIO. Telemetry is written to TimescaleDB (ENABLE_POSTGRES_TELEMETRY_WRITES=True, TimeFusion reads/writes off). TIMEFUSION_PG_URL still has to be a real Postgres URL because the process always opens a second pool — it is set to the same TimescaleDB URL.
Patroni from timescale/timescaledb-ha is omitted on purpose. Railway is the orchestrator; the non-HA image uses the official Postgres entrypoint, PGDATA=/var/lib/postgresql/data, and will not boot-loop on a volume mount.
Topology
Name the services exactly as written. Private DNS is .railway.internal. If you rename them, update DB_HOST, DATABASE_URL, and TIMEFUSION_PG_URL.
| Service | Role | Root Directory | Volume | Memory | Public |
|---|---|---|---|---|---|
| timescaledb | TimescaleDB (Postgres 18) | timescaledb | /var/lib/postgresql | 1 GB floor, 2 GB better | No |
| monoscope | UI, HTTP API, OTLP gRPC | monoscope | None | 1 GB floor, 2 GB better | Yes (HTTP) |
Volumes (drives) — what to mount
Attach before the first successful start. Skip this and init lands on ephemeral disk; the next deploy wipes projects, users, and telemetry.
| Service | Mount path | What is stored | Start size |
|---|---|---|---|
| timescaledb | /var/lib/postgresql | Roles, projects, API keys, telemetry (otel_logs_and_spans). PGDATA=/var/lib/postgresql/data is already set. | 5 GB (20 GB+ as ingest grows) |
| monoscope | do not mount anything | Stateless. Railway terminates TLS. Profiling eventlogs stay ephemeral. | — |
Do not mount /etc/letsencrypt, SSL certs, or /opt/monoscope/static. Those are VPS/compose paths. Do not mount /var/lib/postgresql/data (the data directory itself) — mount the parent /var/lib/postgresql so initdb can create data on a clean volume.
Railway: timescaledb → Settings → Volumes → Add Volume → paste /var/lib/postgresql exactly.
Environment variables
Generate secrets once on the owning service. Other services reference them. Do not paste the same ${{ secret(32) }} on two services — that mints two different values.
timescaledb (owner of the database password):
| Variable | Value | Description |
|---|---|---|
POSTGRES_USER | postgres | Superuser. Keep in sync with DATABASE_URL. |
POSTGRES_DB | monoscope | Database name. Keep in sync with DATABASE_URL. |
POSTGRES_PASSWORD | ${{ secret(32) }} | Generated once here. Referenced by monoscope. |
PGDATA | /var/lib/postgresql/data | Must sit under the volume mount. |
TZ | UTC | Database timezone. |
monoscope (public app):
| Variable | Value | Description |
|---|---|---|
DB_HOST | ${{timescaledb.RAILWAY_PRIVATE_DOMAIN}} | Private DNS used by the IPv6 wait-for probe. |
DB_PORT | 5432 | TimescaleDB port. |
DATABASE_URL | host=${{timescaledb.RAILWAY_PRIVATE_DOMAIN}} user=postgres password=${{timescaledb.POSTGRES_PASSWORD}} dbname=monoscope port=5432 sslmode=disable | libpq keyword form — safe with IPv6 hostnames and unescaped passwords. |
TIMEFUSION_PG_URL | host=${{timescaledb.RAILWAY_PRIVATE_DOMAIN}} user=postgres password=${{timescaledb.POSTGRES_PASSWORD}} dbname=monoscope port=5432 sslmode=disable | Second pool; same TimescaleDB on this self-host path. |
HOST_URL | https://${{RAILWAY_PUBLIC_DOMAIN}} | Public HTTPS origin. Railway terminates TLS. |
GRPC_PORT | 4317 | In-container OTLP gRPC. Not the Railway HTTP $PORT. |
MIGRATIONS_DIR | /opt/monoscope/static/migrations/ | Path inside the image. |
ENVIRONMENT | PROD | DEV / STAGING / PROD. |
LOGGING_DESTINATION | StdOut | Container logs. |
LOG_LEVEL | Info | Trace is too noisy for production. |
BASIC_AUTH_ENABLED | True | Self-host login. |
BASIC_AUTH_USERNAME | admin | First UI login. |
BASIC_AUTH_PASSWORD | ${{ secret(32) }} | Generated once on this service. Copy from Variables after first deploy. |
API_KEY_ENCRYPTION_SECRET_KEY | ${{ secret(32) }} | Min 32 chars. Rotating it invalidates stored API keys. |
MIGRATE_AND_INITIALIZE_ON_START | True | Run SQL migrations before serving. |
ENABLE_BACKGROUND_JOBS | True | Anomaly jobs, reports, maintenance. |
ENABLE_PUBSUB_SERVICE | False | No Kafka/Pub/Sub on this kit. |
ENABLE_OTLP_GRPC_SERVICE | True | Bind gRPC on GRPC_PORT. |
ENABLE_POSTGRES_TELEMETRY_WRITES | True | Write telemetry into TimescaleDB. |
ENABLE_TIMEFUSION_WRITES | False | No TimeFusion service in this kit. |
ENABLE_TIMEFUSION_READS | False | Query TimescaleDB, not TimeFusion. |
ENABLE_BROWSER_MONITORING | True | Browser spans in the UI. |
SHOW_DEMO_PROJECT | False | Hide the demo project. |
AUTH0_LOGOUT_REDIRECT | https://${{RAILWAY_PUBLIC_DOMAIN}} | Used only if you enable Auth0. |
AUTH0_CALLBACK | https://${{RAILWAY_PUBLIC_DOMAIN}}/auth_callback | Used only if you enable Auth0. |
SLACK_REDIRECT_URI | https://${{RAILWAY_PUBLIC_DOMAIN}}/slack/oauth/callback/ | Used only if you set Slack OAuth. |
Baked in — leave alone: PORT (Railway injects it; the app binds 0.0.0.0:$PORT), MIGRATIONS_DIR, ENABLE_PUBSUB_SERVICE=False.
Do not set: HTTP_SCHEME=https, in-container TLS certs, or Railway’s managed Postgres plugin as a substitute for timescaledb.
Optional (set on monoscope when you need the feature):
| Variable | Description |
|---|---|
OPENAI_API_KEY | Natural-language queries and AI agents |
OPENAI_BASE_URL | Compatible OpenAI endpoint |
SMTP_HOST SMTP_PORT SMTP_USERNAME SMTP_PASSWORD SMTP_SENDER | Email reports |
SENDGRIDAPIKEY | SendGrid instead of SMTP |
POSTMARK_TOKEN | Postmark instead of SMTP |
SLACK_CLIENT_ID SLACK_CLIENT_SECRET | Slack OAuth alerts |
AUTH0_DOMAIN AUTH0_CLIENT_ID AUTH0_SECRET | SSO; then set BASIC_AUTH_ENABLED=False |
If you rename services: change DB_HOST and the host= / interpolation prefix in DATABASE_URL to the new private DNS name.
Why Deploy Monoscope 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 Monoscope 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.
Template Content
monoscope
OpenSource-Templates/Monoscopetimescaledb
OpenSource-Templates/Monoscope