---
title: "Deploy Betterlytics"
description: "Betterlytics is a cookieless analytics platform GDPR-compliant."
category: "Analytics"
url: https://railway.com/deploy/betterlytics
---

# Deploy Betterlytics

Betterlytics is a cookieless analytics platform GDPR-compliant.

**[Deploy Betterlytics on Railway](https://railway.com/template/betterlytics)**

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

## Template content

### clickhouse https://devicons.railway.com/i/clickhouse.svg

- **Source:** OpenSource-Templates/Betterlytics

### betterlytics https://betterlytics.io/icon0.svg

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

### postgres https://devicons.railway.com/i/postgresql.svg

- **Source:** OpenSource-Templates/Betterlytics

## Documentation

# Deploy and Host Betterlytics on Railway

Betterlytics is a privacy-first, cookieless Google Analytics alternative. A tracking script under 2KB captures pageviews, funnels, geography, Core Web Vitals, and errors without cookies or consent banners. This template self-hosts the full stack on Railway — Next.js dashboard, ingestion backend, Postgres, and ClickHouse.

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

## About Hosting Betterlytics

Hosting Betterlytics means running three containers on Railway’s private network: a public app (nginx + Next.js dashboard + ingestion backend), Postgres for accounts and site config, and ClickHouse for high-volume events. Railway terminates TLS, so the app speaks HTTP internally (`HTTP_SCHEME=http`, `FORCE_HTTP_SCHEME=https`). You attach two volumes — Postgres at `/var/lib/postgresql` and ClickHouse at `/var/lib/clickhouse` — set one shared `SECRET_BASE` on all three services plus `DOMAIN` on the app, and let the entrypoint wait for both databases, run migrations, then start supervisord. First boot can take a few minutes. After that, add a site in the UI and drop `/analytics.js` on your pages.

## Common Use Cases

- Replace Google Analytics on a SaaS, blog, or marketing site with cookieless, GDPR-friendly stats and no consent banner
- Agency / multi-site analytics — one Railway project, many sites in the dashboard, data that never leaves your account
- Product analytics plus uptime in one place — enable `ENABLE_UPTIME_MONITORING` instead of bolting on a second tool
- Own the event log — query ClickHouse over the private network for custom reports, funnels, or warehouse syncs
- EU / privacy-sensitive products — no third-party analytics vendor, no cookies, optional MaxMind geolocation with your own key

## Dependencies for Betterlytics Hosting

- **Betterlytics image:** `ghcr.io/betterlytics/betterlytics-selfhost:latest` (dashboard + ingestion backend + nginx + supervisord)
- **PostgreSQL 17.5** (`postgres:17.5`) — users, sites, NextAuth
- **ClickHouse 25.8** (`clickhouse/clickhouse-server:25.8`) — event store, IPv6 listen for Railway private networking
- **Railway private networking** — `postgres.railway.internal:5432` and `clickhouse.railway.internal:8123`
- **Two persistent volumes (drives)** — see Implementation Details
- **OpenSSL** in the DB images — derives passwords from `SECRET_BASE`

Name the database services `postgres` and `clickhouse`. Set each service **Root Directory** to its folder (`betterlytics/`, `postgres/`, `clickhouse/`). Keep Postgres and ClickHouse **private**; only the app gets a public domain.

Upstream: [Self-hosting guide](https://betterlytics.io/docs/installation/self-hosting) · [betterlytics.io](https://betterlytics.io/) · [self-host repo](https://github.com/betterlytics/betterlytics-selfhost)

### Implementation Details

Clone the template to see how it works:

```text
betterlytics/     # public web service
  Dockerfile      # FROM ghcr.io/betterlytics/betterlytics-selfhost:latest
  entrypoint.sh   # derive secrets, wait, migrate, supervisord
  nginx.conf      # :8080 → dashboard :3000 + backend :3001
  railway.toml    # healthcheck /, 300s timeout
postgres/
  Dockerfile      # postgres:17.5 + openssl + derive wrapper
clickhouse/
  Dockerfile      # clickhouse-server:25.8 + IPv6 config + init users
  config.xml      # listen 0.0.0.0 and ::, 60% RAM cap

```

## Topology

**Name the services exactly as written.** Private DNS is `.railway.internal`. If you rename them, the app cannot find the databases.

| Service | Role | Root Directory | Volume | Memory | Public |
| --- | --- | --- | --- | --- | --- |
| **postgres** | Dashboard database | `postgres` | `/var/lib/postgresql` | 1 GB –2 GB | No |
| **clickhouse** | Analytics events | `clickhouse` | `/var/lib/clickhouse` | 2 GB – 4 GB | No |
| **betterlytics** | Dashboard + tracker | `betterlytics` | *None* | 512 MB floor, 1 GB better | Yes |

#### Volumes (drives) — what to mount

Attach **before the first successful start**. Skip this and init lands on ephemeral disk; the next deploy wipes accounts and events.

| Service | Mount path | What is stored | Start size |
| --- | --- | --- | --- |
| **postgres** | `/var/lib/postgresql` | Accounts, sites, settings, sessions. `PGDATA` is already this path. | 1 GB |
| **clickhouse** | `/var/lib/clickhouse` | All pageviews/events, tables, SQL users (`admin` / `backend` / `dashboard`). | 5 GB (20 GB+ as traffic grows) |
| **betterlytics** | *do not mount anything* | Stateless. Railway terminates TLS. | — |

Do **not** mount `/etc/letsencrypt`, certbot webroot, or `/etc/clickhouse-server/users.d`. Those are VPS/compose paths. SQL users persist inside `/var/lib/clickhouse`.

Railway: each DB service → **Settings** → **Volumes** → **Add Volume** → paste the mount path exactly.


**Recommended on betterlytics:**

| Variable | Default | Description |
| --- | --- | --- |
| `ADMIN_EMAIL` | — | First dashboard login |
| `ADMIN_PASSWORD` | — | First dashboard password |
| `DEFAULT_LANGUAGE` | `en` | UI language |
| `ENABLE_UPTIME_MONITORING` | `false` | Set `true` for built-in uptime checks |
| `DATA_RETENTION_DAYS` | `365` | Event retention in ClickHouse. `-1` = keep forever |

**Optional email** (`ENABLE_EMAILS=true`), then MailerSend **or** SMTP: `MAILER_SEND_API_TOKEN` / `SMTP_HOST` `SMTP_PORT` `SMTP_USER` `SMTP_PASSWORD` `SMTP_FROM`.

**Optional geolocation:** `ENABLE_GEOLOCATION=true`, `GEOLOCATION_MODE=country` or `full`, plus `MAXMIND_ACCOUNT_ID` and `MAXMIND_LICENSE_KEY`. New events only; IPs are not stored.

**If you rename services:** `POSTGRES_HOST` (default `postgres.railway.internal`), `CLICKHOUSE_HOST` (default `clickhouse.railway.internal`), `CLICKHOUSE_PORT` (must be `8123`, not `9000`).

**Do not set** (derived from `SECRET_BASE`): `POSTGRES_PASSWORD`, `POSTGRES_SITECONFIG_RO_PASSWORD`, `CLICKHOUSE_PASSWORD`, `CLICKHOUSE_BACKEND_PASSWORD`, `CLICKHOUSE_DASHBOARD_PASSWORD`, `NEXTAUTH_SECRET`, `TOTP_SECRET_ENCRYPTION_KEY`.

**Baked in — leave alone:** `HTTP_SCHEME=http`, `FORCE_HTTP_SCHEME=https`, `PORT=8080`, `CLICKHOUSE_DB=analytics`, `POSTGRES_DB=dashboard`, `POSTGRES_USER=user`.

## Traps

**Five ways this still fails:**

* **Do not mix `SECRET_BASE` with extra DB passwords:** `CLICKHOUSE_PASSWORD`, `POSTGRES_PASSWORD`, and `DATABASE_URL` are ignored or overwritten. ClickHouse still stores the first-boot password on the volume. Use `SECRET_BASE` only, then wipe the ClickHouse volume once.
* **App waits forever for Postgres:** Postgres can be healthy while the app never connects. Railway private DNS is IPv6. Redeploy app from the updated kit (`wait-for.cjs`). Do not restart Postgres to fix this.
* **Do not set `HTTP_SCHEME=https`:** That turns on Let’s Encrypt inside the container. Railway already terminates TLS. Keep `HTTP_SCHEME=http` and `FORCE_HTTP_SCHEME=https`.
* **Do not use Railway’s Postgres plugin:** The app derives the database password from `SECRET_BASE`. The kit’s `postgres` service is the matching pair.
* **ClickHouse on 512 MB will die:** Raise the `clickhouse` service memory to 2 GB before the first deploy.
* **`DOMAIN` after the first domain generate:** If you generated `*.up.railway.app` after the first deploy, update `DOMAIN` and redeploy so login and tracking URLs match.

## Why Deploy Betterlytics 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 Betterlytics 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

- [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.
- [Bugsink](https://railway.com/deploy/bugsink) — Self-hosted Error Tracking. Sentry-SDK compatible

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