ryOS ryOS / Docs
GitHub Launch

Self-hosting ryOS on a VPS or Coolify (without Vercel)

ryOS can run without Vercel as a single Bun production server. That server handles:

  • API routes under api/
  • built frontend assets from dist/
  • SPA deep links like /chats and /ipod/:id
  • docs clean URLs like /docs/overview
  • optional local websocket realtime

This makes it a good fit for:

  • a plain VPS + systemd
  • Coolify Dockerfile deployments
  • self-hosted containers behind Traefik / Nginx / Caddy


1) Prerequisites

  • Linux VPS or container host
  • Bun installed (or Docker/Coolify)
  • a public domain + TLS if exposing to the internet
  • Redis if using self-hosted Redis / local websocket fanout

2) Build + run locally in production mode

bun install
bun run build
APP_PUBLIC_ORIGIN="https://your-domain.com" \
API_ALLOWED_ORIGINS="https://your-domain.com" \
bun run start

Important runtime envs:

  • PORT or API_PORT — listening port (defaults to 3000)
  • API_HOST — bind host (defaults to 0.0.0.0)
  • APP_PUBLIC_ORIGIN — canonical browser origin, e.g. https://your-domain.com
  • API_ALLOWED_ORIGINS — comma-separated allowed browser origins for /api/; supports wildcard subdomain patterns (e.g. .example.com), and to allow all origins
  • STORAGE_PROVIDER — optional explicit storage backend (s3, vercel-blob); auto-detected if unset


3) Redis backend options

ryOS supports two Redis modes.

Option A — Standard Redis / Valkey / self-hosted Redis

Recommended for Coolify / VPS deployments:

REDIS_URL="redis://default:password@redis:6379/0"

This mode also enables Redis pub/sub for local websocket fanout.

Option B — Upstash REST

Keeps compatibility with the existing Vercel-oriented setup:

REDIS_KV_REST_API_URL="https://..."
REDIS_KV_REST_API_TOKEN="..."

4) Realtime backend options

ryOS supports two realtime modes.

Option A — Pusher

REALTIME_PROVIDER="pusher"
PUSHER_APP_ID="..."
PUSHER_KEY="..."
PUSHER_SECRET="..."
PUSHER_CLUSTER="us3"

Option B — Local websocket

Recommended for self-hosted deployments:

REALTIME_PROVIDER="local"
REALTIME_WS_PATH="/ws"

Notes:

  • local websocket mode works best with REDIS_URL
  • with REDIS_URL, websocket events can fan out across instances via Redis pub/sub
  • without REDIS_URL, local websocket delivery falls back to in-process fanout only


5) Object storage backend

ryOS supports two object storage backends for cloud sync (backups, wallpapers, images).

Option A — S3-compatible (recommended for self-hosted)

Works with MinIO, Cloudflare R2, Backblaze B2, AWS S3, or any S3-compatible provider:

STORAGE_PROVIDER=s3
S3_BUCKET=ryos-sync
S3_REGION=us-east-1
S3_ENDPOINT=https://s3.your-provider.com
S3_PUBLIC_ENDPOINT=https://s3.your-provider.com  # public-facing URL (for presigned URLs)
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
S3_FORCE_PATH_STYLE=true   # required for MinIO and some providers

Option B — Vercel Blob

Keeps compatibility with the existing Vercel-oriented setup:

STORAGE_PROVIDER=vercel-blob    # optional, auto-detected from token
BLOB_READ_WRITE_TOKEN=vercel_blob_...

When STORAGE_PROVIDER is not set explicitly, the backend is auto-detected: Vercel Blob if BLOB_READ_WRITE_TOKEN is present, S3 if the S3 environment variables are set.


6) Coolify deployment

The repository includes a Dockerfile suitable for Coolify Dockerfile deployments. The image includes a health check endpoint at /health.

Suggested Coolify settings

  • Build Pack: Dockerfile
  • Port Exposes: 3000 (or whatever you set via PORT)
  • Command override: not required
  • Environment variables: set them in the Coolify UI

Recommended environment set for a fully self-hosted stack:

NODE_ENV=production
PORT=3000
API_HOST=0.0.0.0
APP_PUBLIC_ORIGIN=https://your-domain.com
API_ALLOWED_ORIGINS=https://your-domain.com
REDIS_URL=redis://default:password@redis:6379/0
REALTIME_PROVIDER=local
REALTIME_WS_PATH=/ws
STORAGE_PROVIDER=s3
S3_BUCKET=ryos-sync
S3_REGION=us-east-1
S3_ENDPOINT=https://s3.your-provider.com
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key

Coolify's reverse proxy supports WebSockets, so /ws can stay behind the normal app domain.

Version / build number (avoid "dev")

The app shows a version like 10.3 (abc1234) from version.json. Without a commit SHA at build time, it falls back to dev. To show the real commit:

  1. In Coolify → your app → Advanced → enable Include Source Commit in Build
  1. The build script reads SOURCE_COMMIT (Coolify) or VERCEL_GIT_COMMIT_SHA (Vercel) or GIT_COMMIT_SHA (generic CI)

No extra env vars needed — Coolify injects SOURCE_COMMIT when that option is enabled.

Coolify deployments are auto-detected (via COOLIFY_ environment variables) and displayed in the Admin app's Server page.


7) VPS + systemd example

Example unit file:

[Unit]
Description=ryOS self-hosted server
After=network.target

[Service]
Type=simple
WorkingDirectory=/srv/ryos
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=API_HOST=127.0.0.1
Environment=APP_PUBLIC_ORIGIN=https://your-domain.com
Environment=API_ALLOWED_ORIGINS=https://your-domain.com
Environment=REDIS_URL=redis://127.0.0.1:6379/0
Environment=REALTIME_PROVIDER=local
Environment=REALTIME_WS_PATH=/ws
EnvironmentFile=/srv/ryos/.env.local
ExecStart=/usr/local/bin/bun run start
Restart=always
RestartSec=3
User=www-data
Group=www-data

[Install]
WantedBy=multi-user.target

8) Reverse proxy example (optional)

If you still want Nginx or Caddy in front, just proxy the single Bun server:

server {
  listen 443 ssl http2;
  server_name your-domain.com;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_read_timeout 300s;
  }
}

No separate static file server is required.


9) Notes and caveats

  • /api/sync/* endpoints use the switchable storage adapter (api/_utils/storage.ts), which supports both Vercel Blob and S3-compatible storage. See section 5 for configuration.
  • For local development without Vercel:
    • bun run dev
    • or bun run dev:api + bun run dev:vite
  • API tests can target standalone mode:
    • API_URL=http://localhost:3000 bun run test:new-api

10) Troubleshooting Docker bridge networking

If you deploy a multi-container self-hosted stack with REDIS_URL and see:

  • container-to-container Redis connections timing out
  • local websocket fanout failing across instances
  • Docker DNS resolving service names correctly, but TCP never connects

then the issue is usually the host's Docker bridge / iptables setup, not ryOS itself.

This showed up in nested Docker testing when the host had mixed iptables-nft and iptables-legacy state, and bridge traffic was blocked even though containers and DNS were healthy.

Quick checks

From an app container, verify Redis TCP directly:

docker exec <app-container> node -e "const net=require('net'); const s=net.createConnection(6379,'redis'); s.on('connect',()=>{console.log('connected'); s.destroy(); process.exit(0)}); s.on('error',e=>{console.error(e.message); process.exit(1)}); setTimeout(()=>{console.error('timeout'); process.exit(2)},3000);"

If that times out, inspect both iptables frontends:

update-alternatives --display iptables
sudo iptables -S FORWARD
sudo iptables-legacy -S FORWARD
sudo iptables-nft -S FORWARD

Known fix for mixed iptables backend hosts

If Docker bridge traffic is being blocked by legacy forwarding rules, this restores bridge connectivity immediately:

sudo iptables-legacy -P FORWARD ACCEPT

After applying it, re-test Redis TCP between containers before debugging ryOS.

Important note

This is a host-level Docker networking issue. If you see the timeout only on a specific VM, VPS image, or nested-container environment, fix the Docker / iptables configuration there rather than changing ryOS application code.