---
name: sblurp-host
description: Set up a Sblurp community server for the user, end to end, on Render, Railway, Fly.io, DigitalOcean, any VPS with Docker Compose, or Podman. Use when the user asks to host, run, self-host, deploy or set up a Sblurp community or server, or to fix one that does not work.
---

# Set up a Sblurp community server

Sblurp is group chat. Each community runs its own server: one container with a SQLite database in `/data`. People sign in with their sblurp.chat account and use the app at app.sblurp.chat. The user does not run sblurp.chat or the web client.

The docs at https://sblurp.chat/docs are the source of truth. Add `.md` to any docs address to get it as Markdown, for example https://sblurp.chat/docs/render.md. Read the page for the host you pick before you start. Do not invent flags, variables or commands that the docs do not list.

## Safety rules

- Never ask the user to paste a password, API token or secret key into the chat. Let them type secrets into the host's dashboard or into a prompt on their own machine.
- Prefer a browser login for command-line tools, such as `fly auth login`.
- Tell the user before any step that costs money.
- Never scale the server past one machine, instance or replica. SQLite allows one writer.

## 1. Ask the user

1. Which host. In order of preference: Render, Railway, Fly.io, DigitalOcean, any VPS, Docker Compose on their own machine, Podman. If they have no preference, use Render. If you can run commands on their machine, Fly.io is also easy.
2. The community name, for `SBLURP_NAME`.
3. The address, such as `chat.example.com`. They need a domain where they can add DNS records.
4. Whether they want off-site backups to Cloudflare R2 (free under 10 GB). If yes, set this up before the first deploy (step 3).

One community costs about $4 to $8 a month. See https://sblurp.chat/docs/hosting.

## 2. Settings every setup needs

```sh
SBLURP_NAME=My Community
SBLURP_BASE_URL=https://chat.example.com
SBLURP_TRUSTED_ISSUERS=https://sblurp.chat
```

- `SBLURP_BASE_URL` must be exactly the address people add in the app: `https://`, the host name, no trailing path. Sign-in tokens are made for that exact address.
- `SBLURP_TRUSTED_ISSUERS` must include `https://sblurp.chat`, or nobody can sign in.
- The data must be on a persistent disk mounted at `/data`. Without one, a restart deletes the community.
- The image is `ghcr.io/OWNER/sblurp-server:stable`. `OWNER` is the GitHub owner of the public server repository. If you cannot find it in the docs, stop and ask the user. The tag picks a track: `stable`, `beta`, `nightly` or a pinned version.
- Set the health check path to `/healthz`.

## 3. Backups to R2 (optional, before the first deploy)

Guide the user through this in the Cloudflare dashboard. They keep the values. You do not need to see them.

1. In R2, make a bucket, such as `sblurp-backups`.
2. Make an R2 API token with "Object Read & Write" for that bucket only. Note the access key ID, the secret and the account ID.
3. Make a backup password with `openssl rand -base64 32`. The user must keep it in a password manager. Without it the file backups cannot be read.
4. The user adds these as environment variables, secrets, or lines in `.env`:

   ```sh
   AWS_ACCESS_KEY_ID=...
   AWS_SECRET_ACCESS_KEY=...
   R2_ENDPOINT=https://ACCOUNT_ID.r2.cloudflarestorage.com
   R2_BUCKET=sblurp-backups
   BACKUP_PASSWORD=...
   BACKUP_NAME=my-community
   ```

Without `AWS_ACCESS_KEY_ID` the container runs with no backups. Details: https://sblurp.chat/docs/backups.md.

## 4. Deploy

### Render (https://sblurp.chat/docs/render.md)

Dashboard only. Walk the user through it.

1. New, Web Service, Existing image: `ghcr.io/OWNER/sblurp-server:stable`.
2. Instance type Starter or larger. Never Free: it cannot have a disk.
3. Environment variables: `PORT=8080` and the three settings above, plus the R2 values if any.
4. Under Advanced, add a disk at `/data`, 1 GB.
5. Health check path `/healthz`. Deploy.
6. In Settings, add the custom domain and the `CNAME` record that Render shows.

### Railway (https://sblurp.chat/docs/railway.md)

1. Hobby plan or higher. Create, Docker Image: `ghcr.io/OWNER/sblurp-server:stable`.
2. Variables: `PORT=8080` and the three settings above, plus the R2 values if any.
3. Add a volume at `/data`.
4. Settings: healthcheck path `/healthz`. Serverless must be off.
5. Networking: add the custom domain with target port 8080 and the `CNAME` record that Railway shows. Deploy.

### Fly.io (https://sblurp.chat/docs/fly.md)

1. `fly auth login` (browser login).
2. Copy `server/fly.toml` from the repository. Change `app`, `SBLURP_NAME`, `SBLURP_BASE_URL` and `BACKUP_NAME`.
3. Run, with the user's app name:

   ```sh
   fly apps create my-community
   fly volumes create sblurp_data -a my-community -r sjc -s 1 --snapshot-retention 14 -y
   fly ips allocate-v4 --shared -a my-community
   fly ips allocate-v6 -a my-community
   ```

4. DNS: an `A` record with the shared IPv4 and an `AAAA` record with the IPv6. On Cloudflare, "DNS only" (grey cloud).
5. `fly certs add chat.example.com -a my-community`, then `fly certs check chat.example.com -a my-community` until "Issued".
6. For R2, have the user run `fly secrets set --stage -a my-community AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... R2_ENDPOINT=... BACKUP_PASSWORD=...` in their own terminal.
7. `fly deploy --ha=false --image ghcr.io/OWNER/sblurp-server:stable`

### DigitalOcean, any VPS, or Docker Compose (https://sblurp.chat/docs/docker.md)

Do not use DigitalOcean App Platform. It has no persistent disk.

1. A Linux server with 1 GB of memory, a public IPv4 address, Docker and the Compose plugin. On DigitalOcean, use a Basic Droplet with the Docker Marketplace image.
2. DNS: an `A` record that points to the server. Open ports 80 and 443 (on the DigitalOcean Docker image: `ufw allow 80,443/tcp`).
3. Copy `docker-compose.yml`, `Caddyfile` and `env.example` from `deploy/compose/` in the server repository to `/opt/sblurp`.
4. `cd /opt/sblurp && cp env.example .env`. In `.env`, set `SBLURP_DOMAIN=chat.example.com`, the three settings above, `SBLURP_TAG=stable`, and the R2 values if any.
5. `docker compose --profile caddy up -d`. Caddy gets the TLS certificate.

The user patches the operating system. If they have their own proxy, leave out `--profile caddy` and read https://sblurp.chat/docs/proxies.md: h2c to the server, no buffering, no compression, idle timeout over 25 seconds.

### Podman (https://sblurp.chat/docs/podman.md)

Follow that page. It uses a Quadlet file with automatic updates and rollback.

## 5. Check it and become the owner

1. `curl https://chat.example.com/healthz` must answer `ok`.
2. Tell the user to open https://app.sblurp.chat, sign in, click Add a community, enter the address and join. The first person to join becomes the owner. They must do this before they share the address.
3. Have them post from a second browser or a phone. The message must appear at once in the first.

## 6. If something is wrong

Read https://sblurp.chat/docs/troubleshooting.md. The usual causes:

- Nobody can sign in: `SBLURP_BASE_URL` does not match the address in the app exactly, `SBLURP_TRUSTED_ISSUERS` lacks `https://sblurp.chat`, or the server cannot reach `https://sblurp.chat/.well-known/jwks.json`.
- Messages appear only after a reload: the proxy buffers the stream. With Caddy, use `h2c://` and `flush_interval -1`, and no `encode`.
- No certificate: check `dig +short chat.example.com`, set Cloudflare records to "DNS only", open ports 80 and 443, read `docker compose logs caddy`.
- Always "reconnecting": check `/healthz`, make the proxy idle timeout longer than 25 seconds, and on Fly keep exactly one machine, always on.
- Someone else became the owner: start again with an empty data folder, then join first.
- Logs: `docker compose logs -f server` (Compose), `journalctl -u sblurp-server -f` (Podman), `fly logs -a my-community` (Fly.io), or the host dashboard.

## 7. Tell the user

- How to update: https://sblurp.chat/docs/updates.md. Render: Manual Deploy, Deploy latest reference. Railway: Redeploy. Fly: run the deploy command again. Compose: `docker compose pull && docker compose up -d`.
- Where the backups are, and that they must keep the backup password safe.
- What the server sees: admins can read all messages, including DMs. Messages are encrypted in transit, not end to end. Members must know this.
