---
title: "Deploy Formbricks"
description: "Self-host Formbricks — surveys, NPS, CSAT, responses stay in your Postgres"
category: "Analytics"
url: https://railway.com/deploy/formbricks-self-hosted
---

# Deploy Formbricks

Self-host Formbricks — surveys, NPS, CSAT, responses stay in your Postgres

**[Deploy Formbricks on Railway](https://railway.com/template/formbricks-self-hosted)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/formbricks-self-hosted/manifest.json

- **Creator:** SB
- **Category:** Analytics

## Template content

### hub https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/formbricks.svg

- **Image:** ghcr.io/formbricks/hub

### Postgres https://devicons.railway.app/i/postgresql.svg

- **Image:** ghcr.io/railwayapp-templates/postgres-ssl:18

### cube https://cube.dev/favicon.svg

- **Source:** gridalpha/formbricks-railway

### Redis https://cdn.sanity.io/images/sy1jschh/production/0ce0bfdcfbdbf69662b1116671f97c2dd788b655-157x157.svg

- **Image:** redis:8.2
- **Start command:** `/bin/sh -c "rm -rf $RAILWAY_VOLUME_MOUNT_PATH/lost+found/ && exec docker-entrypoint.sh redis-server --requirepass $REDIS_PASSWORD --save 60 1 --dir $RAILWAY_VOLUME_MOUNT_PATH"`

### hub-worker https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/formbricks.svg

- **Image:** ghcr.io/formbricks/hub

### formbricks https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/formbricks.svg

- **Image:** ghcr.io/formbricks/formbricks
- **Public domain:** Yes

## Buckets

- **formbricks-uploads**

## Documentation

# Deploy and Host Formbricks on Railway

Formbricks is an open-source survey and experience management platform — a self-hosted alternative to Typeform, Qualtrics and SurveyMonkey. Build link surveys, in-app and website surveys, NPS and CSAT programmes, and read the results with no per-response ceiling. This template deploys the full v5 stack, Hub included, with every secret generated once and held stable, so the setup wizard is the first thing you see rather than a migration error.

## What This Template Deploys

| Service | Purpose |
| --- | --- |
| `formbricks` | Next.js web app on port `3000`. Public domain, health-checked at `/health`. |
| `Postgres` | Surveys, responses, contacts, users and organisations. Volume attached. |
| `Redis` | Cache, rate limiting and audit behaviour. |
| `hub-api` | Formbricks Hub API. Mandatory in v5, not optional. |
| `hub-worker` | Hub background processing. No public route. |
| `cube` | Semantic layer behind response analytics. |

Everything talks over Railway's private network and only the web app takes a public domain. Postgres is the sole source of truth — the app and Hub services are replaceable, which is why no volume hangs off them.

## About Hosting

Formbricks v5 is a materially different deployment from v4, and most self-hosting guidance online still describes v4. That gap is where the failures live.

**A three-service stack is a v4 stack.** Formbricks Hub became mandatory for self-hosted v5. Deploy the old app-plus-Postgres-plus-Redis shape, let it pull a `latest` tag that now resolves to v5, and the app starts but cannot do the work the Hub handles. Match architecture to version and pin the tag — Formbricks' own README template broke on this, with users hitting missing-tag errors on both the app and database image.

**Five secrets, all of which must survive redeploys.** `NEXTAUTH_SECRET`, `ENCRYPTION_KEY`, `CRON_SECRET`, `HUB_API_KEY` and `CUBEJS_API_SECRET` are each 32-byte hex, and they fail differently: a changed `ENCRYPTION_KEY` makes 2FA secrets and stored integration credentials undecryptable, while a changed `HUB_API_KEY` silently severs the app from the Hub.

**Without SMTP you have no way back into your own instance.** The setup wizard creates the first admin account without email, so the deploy looks complete. Password reset, verification, invitations and notifications all need SMTP, and you learn that the day someone is locked out. Add `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD` and `MAIL_FROM` before inviting anyone.

**Behind Railway's proxy, every request looks like one IP.** Formbricks reads the client address from forwarded headers, and `TRUSTED_PROXY_HOP_COUNT` says how many proxies to trust. Leave it unset and rate limiting, audit logs and response de-duplication all attribute traffic to the proxy rather than real visitors — a correctness problem that never announces itself.

**File-upload answers need object storage.** Questions that accept files write to local storage by default, which on Railway means a container layer replaced on the next deploy. Configure object storage before publishing a survey with a file question, not after the uploads vanish.

Typical cost: **~$25–40/month** for the full six-service v5 stack at $10/GB/month RAM, $20/vCPU/month CPU and $0.15/GB/month volumes. Formbricks itself is free and open source, with no cap on responses.

## How It Compares

| | Formbricks (self-hosted) | Typeform | Qualtrics | Google Forms |
| --- | --- | --- | --- | --- |
| Response limits | None | Plan tier | Plan tier | None |
| Data location | Your Postgres | Vendor | Vendor | Google |
| Self-hostable | Yes | No | No | No |

The honest edge: Typeform is still the nicer authoring experience and Google Forms is free and needs nothing from you, so for an occasional public survey neither is worth replacing. Formbricks earns its keep when responses are personal data you cannot hand a vendor, when per-response pricing has started shaping what you are willing to ask, or when you want in-app surveys triggered on behaviour rather than a link emailed out.

## Deploy in Under 5 Minutes

1. Click **Deploy** and pick a workspace. All six services come up with the five secrets generated and the URL variables pointed at your Railway domain.
2. Wait out the first-boot migration window. The app answers `/health` only once Prisma finishes, so give it a minute before assuming failure.
3. Open the public domain. Formbricks redirects to its setup wizard, where you create the first admin account and your organisation.
4. Add SMTP variables now, while you remember. Without them nobody can reset a password or accept an invitation.
5. Create a survey from a template, publish it, and submit one response to confirm the path through Hub and Cube works end to end.

> Verify before you rely on it: redeploy the whole project, then open that response again. If it is still there and still renders analytics, Postgres, the Hub link and your secrets are all intact.

## Common Use Cases

- **In-app and website surveys** — trigger NPS or CSAT on user behaviour through the JS widget rather than emailing a link and hoping.
- **Privacy-constrained feedback** — collect open text, emails and account-linked scores where responses are personal data that must stay on your infrastructure.
- **Unlimited-volume research** — run continuous feedback programmes without per-response pricing shaping what you ask.

## Configuration

| Variable | Required | Description |
| --- | --- | --- |
| `WEBAPP_URL`, `NEXTAUTH_URL` | Required | Public HTTPS domain. Links, redirects and the widget are built from these. |
| `ENCRYPTION_KEY` | Generated | Encrypts 2FA secrets and stored integration credentials. Never rotate. |
| `HUB_API_KEY` | Generated | Authenticates the app to the Hub. A mismatch severs them silently. |
| `CUBEJS_API_SECRET` | Generated | Signs requests to the Cube analytics service. |
| `DATABASE_URL` | Auto | Reference variable on the private Postgres hostname. |
| `TRUSTED_PROXY_HOP_COUNT` | Required | Proxies in front of the app. A wrong value breaks rate limiting and audit logs. |
| `SMTP_*`, `MAIL_FROM` | Recommended | Password reset, verification, invitations, notifications. |

> **Never rotate `ENCRYPTION_KEY` on a populated database.** Two-factor secrets and stored integration credentials are encrypted with it and cannot be recovered without it.

> **Set SMTP before inviting anyone.** The setup wizard works without it, which is what makes this easy to walk into — the first person needing a password reset is the one who finds out.

## Dependencies for Formbricks Hosting

- **Railway account** — ~$25–40/month for the full v5 stack; less if you scale the Hub worker and Cube down.
- **Bundled services** — Postgres for durable data, Redis for cache and rate limiting, the Hub API and worker, Cube for analytics. All wired by the template.
- **Volume** — on Postgres only. Formbricks is stateless once uploads go to object storage.
- **Optional but expected** — SMTP for account emails, object storage for file uploads, OAuth providers for SSO.

### Deployment Dependencies

- [Formbricks on GitHub](https://github.com/formbricks/formbricks)
- [Formbricks self-hosting documentation](https://formbricks.com/docs/self-hosting)
- [Formbricks migration guide](https://formbricks.com/docs/self-hosting/advanced/migration)
- [Railway volumes](https://docs.railway.com/volumes)

### Implementation Details

The web application runs the official image on a pinned tag rather than `latest`, listening on `3000` behind the Railway domain with `/health` as the health-check target. Every image in the stack is pinned, which matters more here than usual: Formbricks' own README template referenced tags that did not exist, and self-hosters hit missing-tag errors on both the app and database image before deployment began.

The v5 topology is the part worth understanding. The web app authors and serves surveys; the Hub API and worker handle processing that used to live in the monolith; Cube provides the semantic layer behind analytics. They authenticate with `HUB_API_KEY` and `CUBEJS_API_SECRET`, generated once and referenced across services rather than typed twice. Prisma applies migrations on first boot, which is why the health-check window needs room.

For backups, Postgres holds everything — surveys, responses, contacts, organisations, settings. Dump it on a schedule and store `ENCRYPTION_KEY` separately, because a restored database without it holds integration credentials and 2FA secrets nobody can read. If you enable file-upload questions, back up the object storage bucket too; those files are not in Postgres.

## Frequently Asked Questions

**Why six services instead of three?** Formbricks Hub is mandatory for self-hosted v5. Older three-service templates describe v4, and a v5 image in that shape leaves the app without the Hub it expects.

**Can I skip SMTP?** For a single admin, briefly. Password reset, verification, invitations and notifications all need it, and adding it after someone is locked out is not a good afternoon.

**Where do my responses live?** In your Postgres, on your Railway volume — open text, emails and account-linked scores never leave infrastructure you control.

**Why are rate limits and audit logs attributing everything to one IP?** `TRUSTED_PROXY_HOP_COUNT` is unset or wrong, so Formbricks is reading Railway's proxy address instead of the real client.

## Why Deploy Formbricks 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 Formbricks on Railway you get the v5 stack as it is meant to run — the Hub deployed rather than assumed, five secrets generated once and held stable, proxy hop count set so rate limits mean something, and pinned images instead of a tag that moves under you.

## Similar templates

- [Typesense PHP](https://railway.com/deploy/typesense-php) — official PHP client against Railway
- [Typesense vs Meilisearch](https://railway.com/deploy/typesense-vs-meilisearch) — self-hosted Typesense vs Meilisearch
- [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/formbricks-self-hosted
