alf: the atproto Latency Fabric alf.fly.dev/
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 474 lines 12 kB view raw view rendered
1# Deploying ALF 2 3This guide covers running ALF in production across several common platforms. 4 5> **Important:** `SERVICE_URL` must be an HTTPS URL in all production deployments. ATProto OAuth requires that ALF's client metadata be served over HTTPS. Without a valid HTTPS `SERVICE_URL`, the OAuth authorization flow will fail. 6 7--- 8 9## Environment variable reference 10 11| Variable | Required | Default | Description | 12|----------|----------|---------|-------------| 13| `PORT` | No | `3005` | HTTP port ALF listens on | 14| `SERVICE_URL` | **Yes (production)** | `http://localhost:3005` | Public HTTPS URL of this deployment | 15| `ENCRYPTION_KEY` | **Yes** | — | 64-char hex string (32 bytes) for AES-256-GCM encryption of stored tokens | 16| `DATABASE_TYPE` | No | `sqlite` | `sqlite` or `postgres` | 17| `DATABASE_PATH` | No | `./data/alf.db` | SQLite file path (ignored when using Postgres) | 18| `DATABASE_URL` | If postgres | — | PostgreSQL connection string | 19| `PLC_ROOT` | No | `https://plc.directory` | ATProto PLC directory | 20| `HANDLE_RESOLVER_URL` | No | `https://api.bsky.app` | Handle-to-DID resolver | 21| `POST_PUBLISH_WEBHOOK_URL` | No | — | URL to POST to after each successful publish | 22 23Generate an encryption key before any deployment: 24 25```bash 26node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" 27``` 28 29Store the resulting 64-character hex string as `ENCRYPTION_KEY`. Treat it like a private key — if it is lost, all stored OAuth tokens become unreadable. 30 31--- 32 33## Docker (standalone) 34 35### Prerequisites 36 37- Docker installed and running 38- A domain with HTTPS (or a reverse proxy such as Caddy or nginx providing TLS termination in front of port 3005) 39 40### Steps 41 42```bash 43# 1. Build the image 44git clone https://github.com/your-org/alf.git 45cd alf 46docker build -t alf . 47 48# 2. Create a data directory for the SQLite database 49mkdir -p ./data 50 51# 3. Run the container 52docker run -d \ 53 --name alf \ 54 --restart unless-stopped \ 55 -p 3005:3005 \ 56 -e ENCRYPTION_KEY=your-64-char-hex-key \ 57 -e SERVICE_URL=https://alf.example.com \ 58 -e DATABASE_TYPE=sqlite \ 59 -e DATABASE_PATH=/data/alf.db \ 60 -v $(pwd)/data:/data \ 61 alf 62 63# 4. Verify 64curl https://alf.example.com/health 65# {"status":"ok","service":"alf"} 66``` 67 68### Using PostgreSQL instead of SQLite 69 70```bash 71docker run -d \ 72 --name alf \ 73 --restart unless-stopped \ 74 -p 3005:3005 \ 75 -e ENCRYPTION_KEY=your-64-char-hex-key \ 76 -e SERVICE_URL=https://alf.example.com \ 77 -e DATABASE_TYPE=postgres \ 78 -e DATABASE_URL=postgresql://user:pass@db.example.com:5432/alf \ 79 alf 80``` 81 82### Production checklist 83 84- `SERVICE_URL` is set to your public HTTPS URL 85- `ENCRYPTION_KEY` is a securely generated 64-char hex string 86- The `/data` volume (or Postgres) is backed up regularly 87- A reverse proxy handles TLS in front of port 3005 88 89--- 90 91## Docker Compose 92 93The repository includes a `docker-compose.yml` that runs ALF with a persistent named volume for the SQLite database. 94 95### Prerequisites 96 97- Docker and Docker Compose v2 installed 98- A domain with HTTPS termination (handled externally — the Compose file exposes port 3005) 99 100### Steps 101 102```bash 103# 1. Clone the repository 104git clone https://github.com/your-org/alf.git 105cd alf 106 107# 2. Create your .env file 108cp .env.example .env 109``` 110 111Edit `.env` and set at minimum: 112 113```dotenv 114ENCRYPTION_KEY=your-64-char-hex-key 115SERVICE_URL=https://alf.example.com 116``` 117 118For Postgres, also set: 119 120```dotenv 121DATABASE_TYPE=postgres 122DATABASE_URL=postgresql://user:pass@db.example.com:5432/alf 123``` 124 125```bash 126# 3. Start ALF 127docker compose up -d 128 129# 4. Verify 130curl http://localhost:3005/health 131# {"status":"ok","service":"alf"} 132``` 133 134### Updating 135 136```bash 137git pull 138docker compose build 139docker compose up -d 140``` 141 142### Production checklist 143 144- `SERVICE_URL` is set to your public HTTPS URL in `.env` 145- `.env` is not committed to version control 146- `ENCRYPTION_KEY` is a securely generated 64-char hex string 147- The `alf-data` named volume is included in your backup strategy 148- TLS is terminated upstream (nginx, Caddy, Traefik, etc.) 149 150--- 151 152## Fly.io 153 154### Prerequisites 155 156- [`flyctl`](https://fly.io/docs/hands-on/install-flyctl/) installed and authenticated (`fly auth login`) 157- A Fly.io account 158 159### Steps 160 161```bash 162# 1. Clone the repository 163git clone https://github.com/your-org/alf.git 164cd alf 165 166# 2. Create a new Fly app (accept defaults or customise as prompted) 167fly launch 168 169# 3. Set secrets (never put these in fly.toml) 170fly secrets set ENCRYPTION_KEY=your-64-char-hex-key 171fly secrets set SERVICE_URL=https://alf.your-app.fly.dev 172 173# If using Postgres: 174fly secrets set DATABASE_TYPE=postgres 175fly secrets set DATABASE_URL=postgresql://user:pass@your-fly-pg.internal:5432/alf 176 177# 4. Deploy 178fly deploy 179 180# 5. Verify 181curl https://alf.your-app.fly.dev/health 182# {"status":"ok","service":"alf"} 183``` 184 185### Persistent storage for SQLite 186 187If you are using SQLite (the default), attach a Fly volume so the database survives restarts and deployments: 188 189```bash 190fly volumes create alf_data --region <your-region> --size 1 191``` 192 193Add the following to your `fly.toml`: 194 195```toml 196[mounts] 197 source = "alf_data" 198 destination = "/data" 199``` 200 201Then set `DATABASE_PATH=/data/alf.db` as a secret or in `fly.toml` under `[env]`. 202 203For multi-region or multi-instance deployments, use Postgres (`fly postgres create`) rather than SQLite. 204 205### Production checklist 206 207- `SERVICE_URL` is set to your `https://your-app.fly.dev` URL (or custom domain with HTTPS) 208- `ENCRYPTION_KEY` is set as a secret (not in `fly.toml`) 209- A Fly volume or Fly Postgres is configured for persistence 210- Health check passes: `fly status` 211 212--- 213 214## Railway 215 216### Prerequisites 217 218- A [Railway](https://railway.app) account 219- Your ALF repository pushed to GitHub 220 221### Steps 222 2231. Go to [railway.app](https://railway.app) and click **New Project**. 2242. Select **Deploy from GitHub repo** and choose your ALF repository. 2253. Railway will detect the `Dockerfile` and build automatically. 2264. Click on your service, then go to **Variables** and add: 227 228| Variable | Value | 229|----------|-------| 230| `ENCRYPTION_KEY` | your-64-char-hex-key | 231| `SERVICE_URL` | `https://<your-railway-app>.up.railway.app` (set after domain is assigned) | 232| `DATABASE_TYPE` | `sqlite` or `postgres` | 233| `DATABASE_URL` | (if using Postgres — see below) | 234 2355. To add a managed Postgres database: click **New** in your project, choose **Database > Add PostgreSQL**. Railway will inject `DATABASE_URL` automatically; you only need to set `DATABASE_TYPE=postgres`. 236 2376. For SQLite persistence, add a **Volume** to your service and mount it at `/data`, then set `DATABASE_PATH=/data/alf.db`. 238 2397. Once deployed, copy the public URL Railway assigns and update `SERVICE_URL` to that HTTPS URL. 240 2418. Trigger a redeploy so the updated `SERVICE_URL` takes effect. 242 243### Production checklist 244 245- `SERVICE_URL` is set to the Railway-provided HTTPS URL or your custom domain 246- `ENCRYPTION_KEY` is set in the Variables panel 247- Persistent storage (volume or Postgres) is configured 248- Health check URL (`/health`) is configured in the Railway service settings 249 250--- 251 252## Render 253 254### Prerequisites 255 256- A [Render](https://render.com) account 257- Your ALF repository pushed to GitHub 258 259### Steps 260 2611. Go to the Render dashboard and click **New > Web Service**. 2622. Connect your GitHub repository. 2633. Render will detect the `Dockerfile`. Set: 264 - **Name:** `alf` (or your preferred name) 265 - **Region:** choose the region closest to your users 266 - **Instance type:** Starter or above (Starter is sufficient for low traffic) 2674. Under **Environment Variables**, add: 268 269| Key | Value | 270|-----|-------| 271| `ENCRYPTION_KEY` | your-64-char-hex-key | 272| `SERVICE_URL` | `https://<your-render-app>.onrender.com` (update after deploy) | 273| `DATABASE_TYPE` | `sqlite` or `postgres` | 274 2755. For Postgres: click **New > PostgreSQL** in the Render dashboard to create a managed database. Copy the **Internal Database URL** into `DATABASE_URL` and set `DATABASE_TYPE=postgres`. 276 2776. For SQLite persistence: add a **Disk** to your service, mounted at `/data`, with at least 1 GB. Then set `DATABASE_PATH=/data/alf.db`. 278 2797. Click **Create Web Service**. After the first deploy completes, copy the Render-provided URL and update `SERVICE_URL` in your environment variables. Render will trigger an automatic redeploy. 280 281### Production checklist 282 283- `SERVICE_URL` is set to the Render-provided HTTPS URL or your custom domain 284- `ENCRYPTION_KEY` is set in the environment variables panel 285- A Render Disk (for SQLite) or Render PostgreSQL is attached 286- The health check path is set to `/health` in the Render service settings 287 288--- 289 290## Bare-metal / VPS 291 292This section covers running ALF directly on a Linux server (Ubuntu, Debian, etc.) as a systemd service. 293 294### Prerequisites 295 296- Node.js 24 or later (required — ALF uses `"engines": { "node": ">=24" }`) 297- npm 10 or later (bundled with Node 24) 298- A reverse proxy (Caddy or nginx) for HTTPS termination 299- A domain name pointed at your server's IP 300 301### Install Node.js 24 302 303```bash 304# Using NodeSource (recommended) 305curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - 306sudo apt-get install -y nodejs 307 308node --version # should print v24.x.x 309``` 310 311### Clone and build ALF 312 313```bash 314sudo mkdir -p /opt/alf 315sudo chown $USER:$USER /opt/alf 316 317git clone https://github.com/your-org/alf.git /opt/alf 318cd /opt/alf 319 320npm ci --omit=dev 321npm run build 322``` 323 324### Create a dedicated user 325 326```bash 327sudo useradd --system --no-create-home --shell /usr/sbin/nologin alf 328sudo chown -R alf:alf /opt/alf 329 330# Create the data directory 331sudo mkdir -p /var/lib/alf 332sudo chown alf:alf /var/lib/alf 333``` 334 335### Configure environment 336 337```bash 338sudo nano /etc/alf/env 339``` 340 341Set the following (create `/etc/alf/` first: `sudo mkdir -p /etc/alf`): 342 343```dotenv 344PORT=3005 345SERVICE_URL=https://alf.example.com 346ENCRYPTION_KEY=your-64-char-hex-key 347DATABASE_TYPE=sqlite 348DATABASE_PATH=/var/lib/alf/alf.db 349# Or for Postgres: 350# DATABASE_TYPE=postgres 351# DATABASE_URL=postgresql://user:pass@localhost:5432/alf 352PLC_ROOT=https://plc.directory 353HANDLE_RESOLVER_URL=https://api.bsky.app 354``` 355 356Restrict permissions on the env file: 357 358```bash 359sudo chmod 600 /etc/alf/env 360sudo chown root:alf /etc/alf/env 361``` 362 363### systemd unit file 364 365Create `/etc/systemd/system/alf.service`: 366 367```ini 368[Unit] 369Description=ALF — Atproto Latency Fabric 370After=network.target 371 372[Service] 373Type=simple 374User=alf 375Group=alf 376WorkingDirectory=/opt/alf 377EnvironmentFile=/etc/alf/env 378ExecStart=/usr/bin/node /opt/alf/dist/index.js 379Restart=on-failure 380RestartSec=5 381StandardOutput=journal 382StandardError=journal 383SyslogIdentifier=alf 384 385# Hardening 386NoNewPrivileges=true 387PrivateTmp=true 388ProtectSystem=full 389ReadWritePaths=/var/lib/alf 390 391[Install] 392WantedBy=multi-user.target 393``` 394 395Enable and start the service: 396 397```bash 398sudo systemctl daemon-reload 399sudo systemctl enable alf 400sudo systemctl start alf 401sudo systemctl status alf 402``` 403 404View logs: 405 406```bash 407journalctl -u alf -f 408``` 409 410### Reverse proxy with Caddy (recommended) 411 412Install Caddy and add to `/etc/caddy/Caddyfile`: 413 414``` 415alf.example.com { 416 reverse_proxy localhost:3005 417} 418``` 419 420Caddy handles HTTPS automatically via Let's Encrypt. Reload: 421 422```bash 423sudo systemctl reload caddy 424``` 425 426### Reverse proxy with nginx 427 428```nginx 429server { 430 listen 80; 431 server_name alf.example.com; 432 return 301 https://$host$request_uri; 433} 434 435server { 436 listen 443 ssl; 437 server_name alf.example.com; 438 439 ssl_certificate /etc/letsencrypt/live/alf.example.com/fullchain.pem; 440 ssl_certificate_key /etc/letsencrypt/live/alf.example.com/privkey.pem; 441 442 location / { 443 proxy_pass http://localhost:3005; 444 proxy_http_version 1.1; 445 proxy_set_header Host $host; 446 proxy_set_header X-Real-IP $remote_addr; 447 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 448 proxy_set_header X-Forwarded-Proto $scheme; 449 } 450} 451``` 452 453Use `certbot --nginx -d alf.example.com` to obtain a Let's Encrypt certificate. 454 455### Updating 456 457```bash 458cd /opt/alf 459git pull 460npm ci --omit=dev 461npm run build 462sudo systemctl restart alf 463``` 464 465### Production checklist 466 467- Node.js 24 or later is installed (`node --version`) 468- `SERVICE_URL` is set to your public HTTPS URL 469- `ENCRYPTION_KEY` is a securely generated 64-char hex string 470- `/etc/alf/env` has permissions `600` (readable only by root and the `alf` user) 471- `/var/lib/alf` is backed up regularly (contains the SQLite database) 472- The systemd service is enabled and set to restart on failure 473- A reverse proxy with TLS is in front of port 3005 474- Health check passes: `curl https://alf.example.com/health`