- Clone of github.com/fluxerapp/fluxer (official upstream) - SELF_HOSTING.md: full VM rebuild procedure, architecture overview, service reference, step-by-step setup, troubleshooting, seattle reference - dev/.env.example: all env vars with secrets redacted and generation instructions - dev/livekit.yaml: LiveKit config template with placeholder keys - fluxer-seattle/: existing seattle deployment setup scripts
11 KiB
Fluxer — Self-Hosting & VM Rebuild Guide
This document covers how to spin up the Fluxer dev instance from scratch on a new VM.
The running reference deployment is at st.vish.gg (seattle VM, 66.94.122.170 / 100.82.197.124).
Architecture overview
All services run as Docker containers via dev/compose.yaml, connected on a shared Docker network (fluxer-shared).
Caddy acts as the internal reverse proxy, routing all traffic on port 8088 to the appropriate service.
Cloudflared tunnels external HTTPS traffic (no open inbound ports required).
Internet
└── Cloudflare Tunnel (cloudflared)
└── Caddy :8088
├── /api/* → api:8080 (Node.js REST API)
├── /gateway/* → gateway:8080 (Erlang WebSocket gateway)
├── /media/* → media:8080 (media proxy)
├── /admin/* → admin:8080 (Elixir admin panel)
├── /marketing/* → marketing:8080 (Elixir marketing site)
├── /metrics/* → metrics:8080 (Rust metrics service)
├── /livekit/* → livekit:7880 (LiveKit voice/video)
├── /s3/* → minio:9000 (S3-compatible object storage)
└── /* → /app/dist (built frontend SPA)
Services
| Container | Image/Build | Purpose |
|---|---|---|
caddy |
caddy:2 |
Internal reverse proxy |
cloudflared |
cloudflare/cloudflared |
Tunnel (no inbound ports) |
api |
node:24-bookworm-slim |
REST API (tsx watch) |
worker |
node:24-bookworm-slim |
Background job worker |
gateway |
erlang:28-slim |
WebSocket gateway (compiled with rebar3) |
media |
build from fluxer_media_proxy/ |
Media proxy |
admin |
build from fluxer_admin/ |
Admin panel |
marketing |
build from fluxer_marketing/ |
Marketing site |
docs |
node:24-bookworm-slim |
Docs site |
metrics |
build from fluxer_metrics/ |
Metrics aggregator |
postgres |
postgres:17 |
Primary database |
cassandra |
scylladb/scylla:latest |
Time-series / message data |
redis |
valkey/valkey:latest |
Cache / pub-sub |
minio |
minio/minio |
S3-compatible object storage |
meilisearch |
getmeili/meilisearch:v1.25.0 |
Full-text search |
clamav |
clamav/clamav:latest |
Virus scanning (can be disabled) |
livekit |
livekit/livekit-server:latest |
Voice & video |
cassandra-migrate |
debian:bookworm-slim |
One-shot DB migration runner |
Prerequisites
- Debian/Ubuntu VM (tested on Debian 12 bookworm)
- Docker + Docker Compose plugin installed
gitinstalled- A Cloudflare tunnel token (from Cloudflare Zero Trust dashboard)
- Domain pointed at Cloudflare (e.g.
st.vish.gg) - At least 4 vCPU / 8GB RAM recommended (ScyllaDB + ClamAV are hungry)
Install Docker (if not present)
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
Step-by-step rebuild
1. Clone the repo
git clone https://github.com/fluxerapp/fluxer /root/fluxer
cd /root/fluxer
2. Create the shared Docker network
Must be created before starting any containers — it's declared external: true in the compose file.
docker network create fluxer-shared
3. Build the frontend app
Caddy serves the pre-built SPA from fluxer_app/dist/. This must be built before starting Caddy,
otherwise it will mount an empty directory and serve nothing.
# Install pnpm
corepack enable pnpm
# Install workspace deps
pnpm install
# Build the app (outputs to fluxer_app/dist/)
cd fluxer_app
pnpm build
cd ..
Note: The build requires Node 22+ and pnpm. If the VM doesn't have Node installed globally, run the build inside a container:
docker run --rm -v $(pwd):/workspace -w /workspace/fluxer_app node:24-bookworm-slim \ bash -lc "corepack enable pnpm && CI=true pnpm install && pnpm build"
4. Build the cassandra-migrate binary
The Cassandra migration runner is a Rust binary that must be pre-compiled.
# Install Rust if needed
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Build
cd scripts/cassandra-migrate
cargo build --release
cd ../..
The compose file expects the binary at scripts/cassandra-migrate/target/release/cassandra-migrate.
5. Set up the environment file
cp dev/.env.example dev/.env
Edit dev/.env and fill in:
| Variable | What to set |
|---|---|
FLUXER_APP_ENDPOINT |
Your public URL e.g. https://st.vish.gg |
FLUXER_API_PUBLIC_ENDPOINT |
https://st.vish.gg/api |
FLUXER_GATEWAY_ENDPOINT |
wss://st.vish.gg/gateway |
FLUXER_MARKETING_ENDPOINT |
https://st.vish.gg |
FLUXER_ADMIN_ENDPOINT |
https://st.vish.gg/admin |
FLUXER_MEDIA_ENDPOINT |
https://st.vish.gg/media |
FLUXER_INVITE_ENDPOINT |
https://st.vish.gg |
CLOUDFLARE_TUNNEL_TOKEN |
Token from Cloudflare Zero Trust dashboard |
SECRET_KEY_BASE |
Generate: openssl rand -hex 64 |
GATEWAY_RPC_SECRET |
Generate: openssl rand -hex 32 |
GATEWAY_ADMIN_SECRET |
Generate: openssl rand -hex 32 |
MEDIA_PROXY_SECRET_KEY |
Generate: openssl rand -hex 32 |
SUDO_MODE_SECRET |
Generate: openssl rand -hex 32 |
LIVEKIT_API_KEY |
Generate: openssl rand -hex 16 |
LIVEKIT_API_SECRET |
Generate: openssl rand -hex 32 |
VAPID_PUBLIC_KEY |
Generate with web-push: npx web-push generate-vapid-keys |
VAPID_PRIVATE_KEY |
(paired with above) |
ADMIN_OAUTH2_CLIENT_ID |
OAuth2 app ID (from Fluxer admin panel after first boot) |
ADMIN_OAUTH2_CLIENT_SECRET |
OAuth2 secret |
PASSKEY_RP_ID |
Your domain e.g. st.vish.gg |
PASSKEY_ALLOWED_ORIGINS |
https://st.vish.gg |
Everything else can stay at its default dev value on first boot.
6. Set up the LiveKit config
cp dev/livekit.yaml dev/livekit.yaml
Edit dev/livekit.yaml — replace the API key/secret with the values you set in .env:
keys:
"<LIVEKIT_API_KEY>": "<LIVEKIT_API_SECRET>"
webhook:
api_key: "<LIVEKIT_API_KEY>"
urls:
- "http://api:8080/webhooks/livekit"
7. Start the stack
cd /root/fluxer/dev
docker compose up -d
On first boot, cassandra-migrate will run automatically and apply schema migrations.
ScyllaDB takes ~90 seconds to become healthy before migrations run.
Watch progress:
docker compose logs -f cassandra-migrate
docker compose logs -f api
8. Verify everything is up
docker compose ps
Expected running containers: caddy, cloudflared, api, worker, gateway, media, admin,
marketing, docs, metrics, clamav, meilisearch, redis, minio, livekit
Expected exited (one-shot): cassandra-migrate, minio-setup
The app should be accessible at your configured domain via the Cloudflare tunnel.
Managing the stack
Start / stop
cd /root/fluxer/dev
# Start all
docker compose up -d
# Stop all (keep volumes)
docker compose down
# Stop and wipe all data
docker compose down -v
View logs
# All services
docker compose logs -f
# Specific service
docker compose logs -f api
docker compose logs -f gateway
Rebuild a specific service after code changes
# Rebuild and restart one service
docker compose up -d --build media
# Rebuild frontend app and restart caddy
cd /root/fluxer
pnpm --filter fluxer_app build
docker compose -f dev/compose.yaml restart caddy
Update from upstream
cd /root/fluxer
git pull origin main
# Rebuild frontend
pnpm install
pnpm --filter fluxer_app build
# Restart services that auto-install deps (api, worker, docs)
docker compose -f dev/compose.yaml restart api worker docs
# Rebuild services with Dockerfiles
docker compose -f dev/compose.yaml up -d --build media admin marketing metrics
Configuration reference
Key files
| File | Purpose |
|---|---|
dev/compose.yaml |
Main docker compose for the dev stack |
dev/.env |
All environment variables (not committed — see .env.example) |
dev/Caddyfile.dev |
Caddy reverse proxy routing config |
dev/livekit.yaml |
LiveKit server config (API keys, webhook URL) |
Port mappings (host)
| Port | Service |
|---|---|
8088 |
Caddy (all traffic enters here, then routed internally) |
7880 |
LiveKit HTTP |
7882/udp |
LiveKit RTC |
7999/udp |
LiveKit RTC |
9042 |
ScyllaDB CQL (direct access) |
8123 |
ClickHouse (only with --profile clickhouse) |
All other services communicate internally over fluxer-shared Docker network.
Optional profiles
# Enable ClickHouse-backed metrics
docker compose --profile clickhouse up -d
# Enable search (meilisearch is included by default in dev)
docker compose --profile search up -d
Cloudflare tunnel setup
- Go to Cloudflare Zero Trust → Networks → Tunnels → Create tunnel
- Name it (e.g.
fluxer-seattle) - Copy the tunnel token → set as
CLOUDFLARE_TUNNEL_TOKENindev/.env - Add a public hostname:
- Subdomain:
st(or your chosen subdomain) - Domain:
vish.gg - Service:
http://localhost:8088
- Subdomain:
- The
cloudflaredcontainer will connect automatically ondocker compose up
Troubleshooting
Gateway won't start
The Erlang gateway compiles from source on first start (rebar3 compile) — this takes 2-5 minutes.
Watch with docker compose logs -f gateway. If it exits, check for missing deps or compilation errors.
ScyllaDB/Cassandra migration fails
ScyllaDB needs ~90 seconds to fully initialise. The cassandra-migrate container has a sleep 30
but on slow machines it may still fail. Restart it manually:
docker compose run --rm cassandra-migrate
API exits immediately
Almost always a missing or malformed .env value. Check:
docker compose logs api | head -30
Common causes: DATABASE_URL unreachable, missing SECRET_KEY_BASE, malformed REDIS_URL.
Frontend shows blank page
The fluxer_app/dist/ directory is empty — the app hasn't been built yet.
Run the build step (Step 3 above) then restart Caddy:
docker compose restart caddy
ClamAV takes forever to start
ClamAV downloads its virus definition database on first start (~500MB). This is normal.
It won't be marked healthy until the download completes (~5-10 min on first boot).
ClamAV can be disabled by setting CLAMAV_ENABLED=false in .env — the other services don't depend on it.
Seattle reference deployment
The running instance at st.vish.gg uses this exact setup. Key details:
| Property | Value |
|---|---|
| Host | Seattle VM (66.94.122.170 / Tailscale 100.82.197.124) |
| SSH | ssh root@seattle or ssh root@seattle-tailscale |
| Install path | /root/fluxer/ |
| Compose path | /root/fluxer/dev/ |
| Domain | st.vish.gg |
| Tunnel | Cloudflare Zero Trust (cloudflared) |
AUTO_JOIN_INVITE_CODE |
FRIENDS (anyone with this code can register) |
To check status on seattle:
ssh seattle-tailscale "cd /root/fluxer/dev && docker compose ps"
ssh seattle-tailscale "cd /root/fluxer/dev && docker compose logs --tail 20 api"