Tempest Navigator 4.0 - Reference Documentation

Reference file: budget.md

Budget Deployment

This page describes the lowest-cost hosted shape for a single-user Tempest PDS: Railway Hobby for compute and durable SQLite state, plus Cloudflare R2 Standard storage for blob objects and backup uploads. The target is < $10 a month.

For the step-by-step deploy flow, use Deployment Guide.

Provider prices and limits change.

The numbers below were checked on 2026-06-12 against the public Railway and Cloudflare R2 docs.

Target shape

Railway service
  Phoenix release
  /var/lib/tempest mounted as a Railway volume
  account.sqlite
  sequencer.sqlite
  repos/
  backup workspace

Cloudflare R2 bucket: tempest-blobs
  blobs/<did>/<cid>
  temp/blobs/<did>/<cid>

Cloudflare R2 bucket or prefix: tempest-backups
  backups/tempest-backup-*.zip

R2 reduces pressure on the Railway volume, but it does not replace the volume. Blob metadata, account state, sessions, signing keys, OAuth state, repo SQLite files, and the sequencer still live under TEMPEST_DATA_DIR.

Durable volume contents

The Railway volume must preserve these paths as one coherent data set:

account.sqlite
account.sqlite-wal
account.sqlite-shm
sequencer.sqlite
sequencer.sqlite-wal
sequencer.sqlite-shm
repos/
blobs/
tmp/
backups/
oauth_jwks.json

When R2 is enabled for blobs, blobs/ should stay small or empty after normal operation, but the directory still belongs to the durable data layout. Per-DID repo databases live under repos/ and may also have SQLite WAL/SHM sidecar files during runtime. Backups should checkpoint SQLite before copying files.

Budget limits

Railway Hobby currently includes a small monthly usage credit and bills additional usage. It is large enough for the compute side of a single-user PDS: up to 8 vCPU and 8 GB RAM per replica, higher per-service aggregate limits, and 100 GB ephemeral disk. The important persistent storage limit is volume storage: Hobby volume storage is currently up to 5 GB.

Railway ephemeral disk must not be used for TEMPEST_DATA_DIR. It is suitable for build and runtime scratch only.

Cloudflare R2 Standard storage currently has a free tier of:

  • 10 GB-month storage per month
  • 1 million Class A operations per month
  • 10 million Class B operations per month
  • free egress

Use R2 Standard storage for the budget profile. The R2 free tier does not apply to Infrequent Access storage.

For a single user, the likely first limits are blob storage size and blob read operations, not Phoenix CPU or memory. If the account uploads images regularly, watch total R2 GB-months and Class B reads. If the repo grows without many blobs, watch Railway volume usage.

Railway deployment

Create one Railway service for Tempest. Keep replicas at 1. Railway volumes do not support multiple active replicas, and SQLite should have exactly one writer.

Attach one Railway volume and mount it at:

/var/lib/tempest

Set the service variables:

PHX_SERVER=true
SECRET_KEY_BASE=...
TEMPEST_HOSTNAME=tempest.example.com
TEMPEST_PUBLIC_URL=https://tempest.example.com
TEMPEST_DATA_DIR=/var/lib/tempest
TEMPEST_BLOB_MAX_BYTES=10000000
TEMPEST_BLOB_STORE=s3
TEMPEST_BACKUP_STORE=s3
TEMPEST_SMTP_ENABLED=false
TEMPEST_CRAWLERS=https://bsky.network,https://vsky.network
POOL_SIZE=5

Railway supplies PORT; config/runtime.exs reads it automatically. Do not set TEMPEST_HOSTNAME to a URL. It must be the bare external host, without scheme, path, or port.

Prefer a stable custom domain for TEMPEST_HOSTNAME. A temporary Railway domain can prove that the service boots, but AT Protocol identity, OAuth metadata, and external crawlers should be verified against the final HTTPS hostname.

After deploy, check:

curl -fsS https://tempest.example.com/xrpc/_health
curl -fsS https://tempest.example.com/xrpc/com.atproto.server.describeServer
curl --no-buffer \
  "wss://tempest.example.com/xrpc/com.atproto.sync.subscribeRepos?cursor=0"

R2 blob storage

Create an R2 bucket for blobs. Public bucket access is not required for the default profile because Tempest serves com.atproto.sync.getBlob through the Phoenix app after reading the object from R2.

Create an R2 token with Object Read & Write scoped to the blob bucket. Record the Access Key ID and Secret Access Key when Cloudflare shows them; the secret is not shown again.

Set:

TEMPEST_BLOB_S3_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
TEMPEST_BLOB_S3_BUCKET=tempest-blobs
TEMPEST_BLOB_S3_REGION=auto
TEMPEST_BLOB_S3_ACCESS_KEY_ID=...
TEMPEST_BLOB_S3_SECRET_ACCESS_KEY=...

If the bucket uses an R2 jurisdiction, use that jurisdiction's endpoint instead of the default account endpoint.

R2 backup uploads

R2 can also store zipped Tempest backups. This protects against Railway volume loss, but restore still starts from a local backup archive or extracted backup directory.

Create either a separate backup bucket or a separate token scoped to the backup bucket. Set:

TEMPEST_BACKUP_S3_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
TEMPEST_BACKUP_S3_BUCKET=tempest-backups
TEMPEST_BACKUP_S3_REGION=auto
TEMPEST_BACKUP_S3_ACCESS_KEY_ID=...
TEMPEST_BACKUP_S3_SECRET_ACCESS_KEY=...

Create and upload a backup from the running release environment:

mix pds.backup.create --upload-s3

The backup task checkpoints SQLite files, copies durable files into a backup directory, zips the directory, and uploads the zip to the configured S3/R2 backup store.

Restore drill

A budget deployment is not proven until restore has been tested.

  1. Create an R2-uploaded backup from the live service.
  2. Download and extract the backup zip into a temporary workspace.
  3. Stop the Tempest service or deploy to a separate test service.
  4. Attach an empty Railway volume.
  5. Restore into the mounted data directory:
mix pds.backup.restore --input /path/to/extracted-backup --target /var/lib/tempest
  1. Start the service and rerun the deployed health, describeServer, blob-read, DID/handle, and WebSocket checks.

Do not count R2 blob storage as a complete backup by itself. The SQLite files and repo databases are the authoritative state tying accounts, repo commits, and blob metadata together.

The full deployment restore runbook lives in deployment observability.

Cost controls

Set alerts or calendar checks for:

  • Railway volume usage near 4 GB
  • Railway memory usage above the minimum needed for steady state
  • R2 Standard storage approaching 10 GB-month
  • R2 Class B operations if blobs are served heavily
  • successful backup uploads and successful restore drills

Use Cloudflare R2 Standard storage for blobs and backups unless there is a clear reason to pay for Infrequent Access retrieval and minimum-duration semantics.

References

Start tempest docs / tempest.desertthunder.dev 2026-06-24 18:44:18Z