---
title: "Deploy Monoscope"
description: "API-first monitoring and observability platform for engineers."
category: "Analytics"
url: https://railway.com/deploy/monoscope
---

# Deploy Monoscope

API-first monitoring and observability platform for engineers.

**[Deploy Monoscope on Railway](https://railway.com/template/monoscope)**

- **Creator:** OpenSource Templates
- **Category:** Analytics
- **Total deploys:** 6

## Template content

### monoscope https://monoscope.tech/static/apple-touch-icon.png

- **Source:** OpenSource-Templates/Monoscope
- **Public domain:** Yes

### timescaledb https://devicons.railway.com/i/timescale-light.svg

- **Source:** OpenSource-Templates/Monoscope

## Documentation

# 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.

[![Deploy on Railway](https://railway.app/button.svg)](https://railway.com/new/template/monoscope)

## 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/mcp` with 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](https://github.com/monoscope-tech/monoscope) · [Self-host compose](https://github.com/monoscope-tech/monoscope/blob/master/docker-compose.yml) · [Configuration](https://github.com/monoscope-tech/monoscope/blob/master/docs/configuration.md) · [License AGPL-3.0](https://github.com/monoscope-tech/monoscope/blob/master/LICENSE)

### Implementation Details

Clone the template to see how it works:

```text
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.

## Similar templates

- [Betterlytics](https://railway.com/deploy/betterlytics) — Betterlytics is a cookieless analytics platform GDPR-compliant.
- [Finance Tracker](https://railway.com/deploy/finance-tracker-1) — Private multi-user household finance ledger with budgets and CSV import.
- [Matomo Analytics + MariaDB](https://railway.com/deploy/matomo-analytics-mariadb) — Privacy-friendly analytics with MariaDB and persistent volumes.

Open this page in a browser: https://railway.com/deploy/monoscope
