---
title: "Deploy Hexclave — Self-Hosted Auth, S3 Storage & Webhooks"
description: "Self-hosted auth, teams, analytics, and file uploads with built-in S3."
category: "Authentication"
url: https://railway.com/deploy/hexclave-or-self-hosted-auth-storage
---

# Deploy Hexclave — Self-Hosted Auth, S3 Storage & Webhooks

Self-hosted auth, teams, analytics, and file uploads with built-in S3.

**[Deploy Hexclave — Self-Hosted Auth, S3 Storage & Webhooks on Railway](https://railway.com/template/hexclave-or-self-hosted-auth-storage)**

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

- **Creator:** GH5T
- **Category:** Authentication

## Template content

### minio-init https://avatars.githubusercontent.com/u/695951?s=200&v=4

- **Image:** minio/mc:latest
- **Start command:** `sh -c 'until mc alias set local "$MINIO_ENDPOINT" "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null 2>&1; do echo "waiting for minio..."; sleep 2; done; mc mb --ignore-existing local/"$PUBLIC_BUCKET"; mc mb --ignore-existing local/"$PRIVATE_BUCKET"; mc anonymous set download local/"$PUBLIC_BUCKET"; echo "buckets ready"'`

### svix-db

- **Image:** postgres:16

### MinIO https://avatars.githubusercontent.com/u/695951?s=200&v=4

- **Image:** minio/minio:latest
- **Start command:** `minio server /data --console-address ":9001"`
- **Health check:** /minio/health/live
- **Public domain:** Yes

### svix-server

- **Image:** svix/svix-server
- **Public domain:** Yes

### hexclave-cron https://raw.githubusercontent.com/hexclave/hexclave/ebe7877cc1f81a23d0797272b6dc89f804b3103e/apps/dashboard/public/hexclave-icon.svg

- **Image:** curlimages/curl:latest
- **Start command:** `sh -c 'for p in email-queue-step external-db-sync/sequencer external-db-sync/poller; do (curl -fsS -m 290 -o /dev/null -w "$p -> %{http_code} in %{time_total}s\n" -H "Authorization: Bearer $CRON_SECRET" "$API_URL/api/latest/internal/$p" || echo "$p FAILED") & done; wait'`

### hexclave https://raw.githubusercontent.com/hexclave/hexclave/ebe7877cc1f81a23d0797272b6dc89f804b3103e/apps/dashboard/public/hexclave-icon.svg

- **Image:** stackauth/server:ebe7877

### hexclave-proxy https://raw.githubusercontent.com/hexclave/hexclave/ebe7877cc1f81a23d0797272b6dc89f804b3103e/apps/dashboard/public/hexclave-icon.svg

- **Image:** caddy:2-alpine
- **Start command:** `sh -c 'cat > /etc/caddy/Caddyfile <<EOF
:$PROXY_PORT {
 handle /caddy-health {
 respond "ok" 200
 }
 handle /api/* {
 reverse_proxy $API_UPSTREAM
 }
 handle {
 reverse_proxy $DASHBOARD_UPSTREAM
 }
 handle_errors {
 respond "Hexclave is still starting up — first boot takes a few minutes. Refresh this page shortly." 503
 }
}
EOF
exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile'`
- **Health check:** /caddy-health
- **Public domain:** Yes

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

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

### ClickHouse https://devicons.railway.app/i/clickhouse

- **Image:** clickhouse/clickhouse-server:25.10

## Documentation

# Deploy and Host Hexclave on Railway

Hexclave is a self-hosted authentication and user-management platform (the `stackauth/server` image) — authentication, teams, RBAC, and analytics — an open, self-hosted alternative to hosted auth providers. This template runs the whole stack on Railway with **batteries included**: object storage (MinIO) and webhooks (self-hosted Svix) run in the same project, so there are no external service accounts to wire up for the core features. Everything is pre-configured and links together on the first deploy.

## About Hosting Hexclave

Hosting Hexclave means running its backend + dashboard server alongside the supporting infrastructure it depends on: a Postgres database, a ClickHouse analytics store, a reverse proxy, a cron worker for background jobs, S3-compatible object storage, and a Svix webhooks engine. This template provisions and connects all of it for you, exposing the dashboard on a single generated domain, with every secret generated per-deploy. You only bring your own SMTP, Freestyle, and AI keys when you want those optional features.

The template also pre-sets `STACK_DISABLE_PLAN_LIMITS=true` on the hexclave service. Hexclave's paid-plan quota enforcement (analytics-event caps, session-replay limits, email quotas, seat checks) is backed by its cloud-only **Bulldozer** metering service, which is not part of the self-hosted image. This flag is hexclave's built-in switch to skip those checks *before* the Bulldozer lookup — without it, opening the **Analytics** pages fails with a `500` from the quota gate. Leave it enabled when self-hosting.

| Service | Image | Purpose |
| --- | --- | --- |
| **hexclave** | `stackauth/server` | The app — auth/teams backend + dashboard |
| **hexclave-proxy** | Caddy | Reverse proxy — serves the whole app on one public domain |
| **hexclave-cron** | `stackauth/server` | Scheduled background jobs |
| **Postgres** | `postgres` | Primary database |
| **ClickHouse** | `clickhouse` | Analytics event store |
| **MinIO** + **minio-init** | `minio/minio` | S3-compatible object storage |
| **svix-db** + **svix-server** | `svix/svix-server` | Self-hosted Svix webhooks engine |

## Why Deploy Hexclave 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 Hexclave on Railway, you get the entire multi-service stack — app, databases, object storage, and webhooks — provisioned and networked together in one project, instead of manually standing up and connecting seven services yourself. Railway's private networking, generated domains, and per-service variables handle the wiring, so you go from template to a working auth platform in a single deploy.

## Common Use Cases

- Self-hosting authentication and user management for your SaaS instead of a paid auth provider.
- Running an internal identity platform with teams, RBAC, and organization management.
- Owning your auth data and infrastructure for compliance or data-residency requirements.
- Delivering webhooks and events to your own services from a self-hosted Svix instance.

## Dependencies for Hexclave Hosting

- The `stackauth/server` container image (the Hexclave app).
- Postgres and ClickHouse databases — provisioned by this template.
- S3-compatible object storage (MinIO) — provisioned by this template.
- A Svix webhooks server — provisioned by this template.
- Optional, for extra features: an SMTP provider (email), a Freestyle API key (email rendering), and an OpenRouter key (AI).

### Deployment Dependencies

- [Hexclave self-hosting guide](https://docs.hexclave.com/guides/other/self-host)
- [Svix (webhooks) documentation](https://docs.svix.com)
- [Freestyle (email rendering)](https://www.freestyle.sh)

---

## 1. General setup (first deploy)

**Step 1 — Deploy.** Every service comes up with generated secrets and is wired to the others automatically. The public domain is generated on deploy.

**Step 2 — Log in.** The seed script creates an initial dashboard admin from these variables on the **hexclave** service:

```
HEXCLAVE_SEED_INTERNAL_PROJECT_USER_EMAIL
HEXCLAVE_SEED_INTERNAL_PROJECT_USER_PASSWORD
```

Open the app's public URL and sign in with those credentials.

**Step 3 — Lock the seed. ⚠️** After your first successful login, set the following on the **hexclave** service and redeploy:

```
HEXCLAVE_SKIP_SEED_SCRIPT=true
```

The seed only runs cleanly once. If you skip this, later redeploys crash with *"Environment configuration overrides cannot be changed in a development environment."*

### Custom domain (optional)

1. Attach your domain to the **hexclave-proxy** service and add the DNS record Railway gives you.
2. On the **hexclave** service, point both public URLs at it:

   ```
   NEXT_PUBLIC_HEXCLAVE_DASHBOARD_URL=https://auth.yourcompany.com
   NEXT_PUBLIC_HEXCLAVE_API_URL=https://auth.yourcompany.com
   ```

3. Re-seed once so the domain is registered as **trusted**: set `HEXCLAVE_SKIP_SEED_SCRIPT=false`, redeploy (it merges the domain, then crashes — expected), then set it back to `true` and redeploy.

---

## 2. Email setup (SMTP)

To send transactional email (verification, password reset, magic links, invites) through any SMTP provider, set these on the **hexclave** service:

```
STACK_EMAIL_HOST=smtp.yourprovider.com
STACK_EMAIL_PORT=587
STACK_EMAIL_USERNAME=your-smtp-username
STACK_EMAIL_PASSWORD=your-smtp-password
STACK_EMAIL_SENDER=no-reply@yourdomain.com
```

- Address validation is disabled by default (`HEXCLAVE_EMAILABLE_API_KEY=disable_email_validation`). Leave it unless you have an [Emailable](https://emailable.com) key.
- **SMTP alone is not enough** — Hexclave renders its email templates by executing code at runtime, so you also need the rendering engine in **section 3**.

> **Where you'll see this:** if the rendering engine isn't configured, opening a project's **Email settings** page (dashboard → your project → **Emails**) shows **"Something went wrong"** and bounces you back to the dashboard. That page loading correctly is your sign the engine is set up.

---

## 3. Freestyle setup (email template rendering)

Hexclave renders email templates through an external JavaScript execution engine. The primary engine is **[Freestyle](https://www.freestyle.sh)**.

> **Symptom if this isn't set up:** the **Email settings** page shows *"Something went wrong"* and redirects. Configuring Freestyle fixes it.

**Step 1.** Create a Freestyle account, enable the **code-execution / serverless** product (this may require adding a payment method — a key from an un-provisioned account returns `401 Invalid API key`), and copy an API key.

**Step 2.** On the **hexclave** service, set the key — **use the `STACK_` prefix, not `HEXCLAVE_`:**

```
STACK_FREESTYLE_API_KEY=your-freestyle-key
```

> The code reads this variable under the `STACK_` name, and the `HEXCLAVE_→STACK_` auto-mirroring is **not reliable** for it. Setting `HEXCLAVE_FREESTYLE_API_KEY` alone can silently send an empty key.

**Step 3.** Leave `STACK_VERCEL_SANDBOX_TOKEN` at its pre-set placeholder. The renderer throws on startup if it is completely unset — even when you only use Freestyle. (To use Vercel Sandbox as the engine instead, replace it with a real token and add `STACK_VERCEL_SANDBOX_TEAM_ID` and `STACK_VERCEL_SANDBOX_PROJECT_ID`.)

**Verify a key directly:** `POST https://api.freestyle.sh/execute/v1/script` with header `Authorization: Bearer <key>`. A `200` (or a script error) means the key is accepted; `401` means it isn't.

---

## 4. Webhooks (self-hosted Svix)

Webhooks are **fully self-hosted and pre-wired** — no Svix Cloud account needed. `svix-server` (backed by its own `svix-db` Postgres) runs in the project and is already connected to hexclave.

**Using it:** in the dashboard, open a project → **Webhooks**, add an endpoint URL, and Hexclave delivers events (`user.created`, `team.updated`, session events, …) to it. The management UI is served by your own `svix-server`.

**Already wired for you:**

- In-memory queue and cache — single-instance, **no Redis required**.
- `svix-server` is on a **public** Railway domain, because the dashboard's webhook UI calls it directly **from the browser**.
- hexclave points at it via `STACK_SVIX_SERVER_URL` (private, backend) plus **both** `NEXT_PUBLIC_STACK_SVIX_SERVER_URL` and `NEXT_PUBLIC_HEXCLAVE_SVIX_SERVER_URL` (public, browser). Both public spellings are required — with only one set, the dashboard falls back to `api.svix.com` and 401s.

**🔒 Production hardening.** This template ships a **fixed** `SVIX_JWT_SECRET` and matching admin token so webhooks work the instant you deploy. Because `svix-server` is public, rotate them for a production deployment:

1. Set a new random `SVIX_JWT_SECRET` on the **svix-server** service.
2. Generate a fresh admin token against it: `svix-server jwt generate`.
3. Paste the token into `STACK_SVIX_API_KEY` on the **hexclave** service, then redeploy both.

**Delivery durability.** The in-memory queue means in-flight **retries** are lost if `svix-server` restarts (new events are unaffected). For durable retries, add a Redis service and set `SVIX_QUEUE_TYPE=redis` with a `SVIX_REDIS_DSN` pointing at it.

---

## 5. AI features (optional)

Some Hexclave features use OpenRouter. To enable, set this on the **hexclave** service:

```
HEXCLAVE_OPENROUTER_API_KEY=your-openrouter-key
```

---

## Notes & limitations

- **Analytics:** the Analytics dashboards and queries run on the bundled ClickHouse and work out of the box (with `STACK_DISABLE_PLAN_LIMITS=true`, see *About Hosting*). Only the features that specifically require the cloud-only **Bulldozer** metering service — usage metering, session replay, and enforced plan quotas — stay unavailable on a self-hosted deploy.
- **Secrets:** all service passwords and keys are generated per-deploy, except the Svix secret/token noted in section 4.
- **Source of truth:** for anything not covered here, see the official self-hosting guide — https://docs.hexclave.com/guides/other/self-host

## Similar templates

- [Keycloak](https://railway.com/deploy/mSwigX) — Keycloak template with keywind theme + apple and discord providers
- [lua-protector](https://railway.com/deploy/lua-protector) — Test deployed my project first
- [bknd](https://railway.com/deploy/p4nTYL) — Feature-rich yet lightweight backend

Open this page in a browser: https://railway.com/deploy/hexclave-or-self-hosted-auth-storage
