mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-03 05:10:25 +03:00
workspace: reconcile latest Fluxer into the open source release and continue development there
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
# Get Started
|
||||
|
||||
Run your own Fluxer instance with Docker Compose. This guide takes you from a fresh server to a working self-hosted instance with the web app, API, gateway, admin dashboard, media uploads, search, storage, and voice signaling behind one public hostname.
|
||||
|
||||
## What you'll run
|
||||
|
||||
The self-hosted stack is one Docker Compose project:
|
||||
|
||||
- **Caddy** terminates public HTTP(S) or receives traffic from a Cloudflare Tunnel.
|
||||
- **App proxy** serves the Fluxer web client and injects instance bootstrap data.
|
||||
- **API** handles accounts, auth, communities, messages, uploads, admin APIs, and instance discovery.
|
||||
- **Admin dashboard** is required and is served at `/admin`.
|
||||
- **Gateway** handles WebSocket sessions, presence, dispatch, push fanout, and realtime events.
|
||||
- **Messages service** builds message responses and serves message history.
|
||||
- **Media proxy** handles attachment upload relay, media metadata, thumbnails, and object reads.
|
||||
- **Static proxy** serves Fluxer fonts, icons, emoji, badges, default avatars, and voice client assets from the same hostname.
|
||||
- **LiveKit** handles voice and video signaling and WebRTC media.
|
||||
- **Postgres**, **Valkey**, **NATS**, **Meilisearch**, and **SeaweedFS** provide data, cache, events, search, and S3-compatible object storage.
|
||||
|
||||
The app bundle is served by the self-host app-proxy image; shared static assets are served by the standalone `static-proxy` container. The stack does not depend on Fluxer's public static asset host.
|
||||
|
||||
## Requirements
|
||||
|
||||
- A Linux server or VM that can run Docker Engine.
|
||||
- Docker Engine plus the Docker Compose plugin.
|
||||
- A hostname for the instance, for example `chat.example.com`.
|
||||
- Either public inbound `80/tcp` and `443/tcp`, or a Cloudflare Tunnel that routes the hostname to the Caddy container.
|
||||
- For production voice and video media, a public path to `7881/tcp` and `7882/udp`.
|
||||
- At least 2 vCPU, 4 GB RAM, and 20 GB disk. Use 4 vCPU and 8 GB RAM or more for a small active community.
|
||||
|
||||
The stack idles around a few GB of memory, and startup is the heaviest point because all service images initialize at once.
|
||||
|
||||
## Step 1: Install Docker
|
||||
|
||||
Install Docker Engine from Docker's official instructions for your distribution:
|
||||
|
||||
- [Install Docker Engine](https://docs.docker.com/engine/install/)
|
||||
- [Install the Compose plugin](https://docs.docker.com/compose/install/linux/)
|
||||
- [Linux post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/)
|
||||
|
||||
Confirm the versions:
|
||||
|
||||
```bash
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
Use Docker Engine 24 or newer and the Compose v2 plugin.
|
||||
|
||||
## Step 2: Download the stack
|
||||
|
||||
Create a working directory and download the stack files:
|
||||
|
||||
```bash
|
||||
mkdir fluxer
|
||||
cd fluxer
|
||||
|
||||
base=https://raw.githubusercontent.com/fluxerapp/fluxer/main/deploy/self-hosting
|
||||
curl -fsSLO "$base/docker-compose.yml"
|
||||
curl -fsSLO "$base/Caddyfile"
|
||||
curl -fsSLO "$base/livekit.yaml"
|
||||
curl -fsSL "$base/.env.example" -o .env
|
||||
```
|
||||
|
||||
You should now have:
|
||||
|
||||
```text
|
||||
Caddyfile
|
||||
docker-compose.yml
|
||||
livekit.yaml
|
||||
.env
|
||||
```
|
||||
|
||||
## Step 3: Configure `.env`
|
||||
|
||||
Set the public hostname at the top of `.env`.
|
||||
|
||||
For a normal public server where Caddy obtains certificates directly:
|
||||
|
||||
```ini
|
||||
FLUXER_DOMAIN=chat.example.com
|
||||
FLUXER_PUBLIC_SCHEME=https
|
||||
FLUXER_PUBLIC_PORT=443
|
||||
FLUXER_CADDY_SITE_ADDRESS=chat.example.com
|
||||
FLUXER_VAPID_EMAIL=admin@example.com
|
||||
```
|
||||
|
||||
For a Cloudflare Tunnel where Cloudflare terminates HTTPS and forwards HTTP to Caddy:
|
||||
|
||||
```ini
|
||||
FLUXER_DOMAIN=chat.example.com
|
||||
FLUXER_PUBLIC_SCHEME=https
|
||||
FLUXER_PUBLIC_PORT=443
|
||||
FLUXER_CADDY_SITE_ADDRESS=:80
|
||||
FLUXER_VAPID_EMAIL=admin@example.com
|
||||
```
|
||||
|
||||
`FLUXER_PUBLIC_SCHEME` and `FLUXER_PUBLIC_PORT` describe what users see in their browser. `FLUXER_CADDY_SITE_ADDRESS` describes what Caddy listens on inside the stack.
|
||||
|
||||
Generate the required secrets:
|
||||
|
||||
```bash
|
||||
for key in POSTGRES_PASSWORD MEILI_MASTER_KEY FLUXER_S3_SECRET_KEY \
|
||||
FLUXER_SUDO_MODE_SECRET FLUXER_CONNECTION_INITIATION_SECRET \
|
||||
FLUXER_GATEWAY_RPC_AUTH_TOKEN FLUXER_MEDIA_PROXY_SECRET_KEY \
|
||||
FLUXER_ADMIN_SECRET_KEY_BASE FLUXER_ADMIN_OAUTH_CLIENT_SECRET \
|
||||
LIVEKIT_API_SECRET; do
|
||||
sed -i "s|^$key=.*|$key=$(openssl rand -hex 32)|" .env
|
||||
done
|
||||
|
||||
sed -i "s|^FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=.*|FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=$(openssl rand -base64 32)|" .env
|
||||
|
||||
VAPID=$(docker run --rm node:24-alpine npx --yes web-push generate-vapid-keys --json)
|
||||
pub=$(printf '%s' "$VAPID" | grep -o '"publicKey":"[^"]*"' | cut -d'"' -f4)
|
||||
priv=$(printf '%s' "$VAPID" | grep -o '"privateKey":"[^"]*"' | cut -d'"' -f4)
|
||||
sed -i "s|^FLUXER_VAPID_PUBLIC_KEY=.*|FLUXER_VAPID_PUBLIC_KEY=$pub|" .env
|
||||
sed -i "s|^FLUXER_VAPID_PRIVATE_KEY=.*|FLUXER_VAPID_PRIVATE_KEY=$priv|" .env
|
||||
```
|
||||
|
||||
Keep these defaults unless you know you need to change them:
|
||||
|
||||
- `LIVEKIT_API_KEY=fluxer`; the secret is `LIVEKIT_API_SECRET`.
|
||||
- `FLUXER_S3_ACCESS_KEY=fluxer`; the secret is `FLUXER_S3_SECRET_KEY`.
|
||||
- Email starts disabled. Enable SMTP later from `.env` and the admin dashboard.
|
||||
|
||||
!!! warning "Keep `.env` private"
|
||||
`.env` contains every secret for the instance. Do not commit it, paste it into support tickets, or put it in screenshots.
|
||||
|
||||
## Step 4: Publish the hostname
|
||||
|
||||
=== "Direct public server"
|
||||
|
||||
Create DNS records for the hostname:
|
||||
|
||||
- `A` record from `chat.example.com` to the server IPv4 address.
|
||||
- Optional `AAAA` record from `chat.example.com` to the server IPv6 address.
|
||||
|
||||
Leave `FLUXER_CADDY_SITE_ADDRESS=chat.example.com`. Caddy will request and renew certificates automatically when `80/tcp` and `443/tcp` can reach the server.
|
||||
|
||||
=== "Cloudflare Tunnel"
|
||||
|
||||
Use this when the server should not expose public web ports.
|
||||
|
||||
1. Set `FLUXER_CADDY_SITE_ADDRESS=:80`.
|
||||
2. In Cloudflare, create a Tunnel public hostname for your Fluxer domain.
|
||||
3. If `cloudflared` runs inside the Compose project, point the public hostname service to `http://caddy:80`.
|
||||
4. If `cloudflared` runs directly on the host, point the public hostname service to `http://127.0.0.1:80`.
|
||||
|
||||
A temporary Compose override keeps the tunnel next to Caddy without saving the token in your main stack:
|
||||
|
||||
```bash
|
||||
cat > cloudflared.compose.yml <<'YAML'
|
||||
services:
|
||||
cloudflared:
|
||||
image: cloudflare/cloudflared:latest
|
||||
restart: unless-stopped
|
||||
command: tunnel run --token ${CLOUDFLARED_TOKEN:?set CLOUDFLARED_TOKEN}
|
||||
depends_on:
|
||||
- caddy
|
||||
YAML
|
||||
|
||||
export CLOUDFLARED_TOKEN='paste-your-tunnel-token-here'
|
||||
docker compose -f docker-compose.yml -f cloudflared.compose.yml up -d cloudflared
|
||||
```
|
||||
|
||||
!!! warning "Voice media is not carried by a normal public hostname tunnel"
|
||||
The web app, API, admin dashboard, gateway WebSocket, media proxy HTTP routes, and LiveKit signaling can work through the tunnel. LiveKit WebRTC media still needs reachable `7881/tcp` and `7882/udp`, or a TURN deployment.
|
||||
|
||||
## Step 5: Open the firewall
|
||||
|
||||
If you are using a direct public server, allow inbound:
|
||||
|
||||
- `22/tcp` or your SSH port.
|
||||
- `80/tcp` and `443/tcp` for Caddy.
|
||||
- `7881/tcp` and `7882/udp` for LiveKit media.
|
||||
|
||||
If you are using a Cloudflare Tunnel for web traffic, you can block inbound `80/tcp` and `443/tcp` at the provider firewall. Keep LiveKit media closed too unless you are intentionally exposing voice/video media or using a TURN server.
|
||||
|
||||
!!! warning "Provider firewall first"
|
||||
Docker-published ports can bypass host firewalls such as UFW because Docker installs its own packet-filtering rules. Prefer your cloud provider's firewall or security group for internet-facing policy.
|
||||
|
||||
## Step 6: Start the stack
|
||||
|
||||
Start Fluxer:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
If you are using the Cloudflare override from above, start both files together:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f cloudflared.compose.yml up -d
|
||||
```
|
||||
|
||||
Watch the startup:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
docker compose logs -f api
|
||||
```
|
||||
|
||||
The first start can take several minutes while images download and services initialize. `seaweedfs-init` exits after creating object-storage buckets; that is expected.
|
||||
|
||||
## Step 7: Verify the instance
|
||||
|
||||
Set your domain in the shell:
|
||||
|
||||
```bash
|
||||
export FLUXER_DOMAIN=chat.example.com
|
||||
```
|
||||
|
||||
Check every public HTTP entry point:
|
||||
|
||||
```bash
|
||||
for path in /_health /api/_health /gateway/_health /media/_health /admin/_health; do
|
||||
curl -fsS -o /tmp/fluxer-check -w "$path %{http_code}\n" "https://$FLUXER_DOMAIN$path"
|
||||
done
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
/_health 200
|
||||
/api/_health 200
|
||||
/gateway/_health 200
|
||||
/media/_health 200
|
||||
/admin/_health 200
|
||||
```
|
||||
|
||||
Check instance discovery:
|
||||
|
||||
```bash
|
||||
curl -fsS "https://$FLUXER_DOMAIN/api/.well-known/fluxer" | jq '.features.self_hosted, .endpoints.admin, .endpoints.gateway, .endpoints.media, .endpoints.static_cdn'
|
||||
```
|
||||
|
||||
You should see `true`, an admin URL ending in `/admin`, a gateway URL ending in `/gateway`, a media URL ending in `/media`, and a static asset URL equal to the instance origin.
|
||||
|
||||
Check the web app, admin login page, app bundle, and static asset container:
|
||||
|
||||
```bash
|
||||
curl -fsSI "https://$FLUXER_DOMAIN" | sed -n '1,8p'
|
||||
curl -fsSI "https://$FLUXER_DOMAIN/admin/login" | sed -n '1,8p'
|
||||
|
||||
asset=$(curl -fsS "https://$FLUXER_DOMAIN" | grep -o 'src="[^"]*/assets/[^"]*"' | head -n1 | cut -d'"' -f2)
|
||||
case "$asset" in
|
||||
http*) curl -fsSI "$asset" | sed -n '1,8p' ;;
|
||||
/*) curl -fsSI "https://$FLUXER_DOMAIN$asset" | sed -n '1,8p' ;;
|
||||
esac
|
||||
|
||||
curl -fsSI "https://$FLUXER_DOMAIN/fonts/ibm-plex.css?v=3" | sed -n '1,8p'
|
||||
curl -fsSI "https://$FLUXER_DOMAIN/web/favicon-32x32.png" | sed -n '1,8p'
|
||||
```
|
||||
|
||||
If you are using Cloudflare Tunnel and see HTTP `530`, the tunnel connector is not currently connected or the public hostname route points at the wrong service.
|
||||
|
||||
## Step 8: Create the owner account
|
||||
|
||||
Open the web app:
|
||||
|
||||
```text
|
||||
https://chat.example.com
|
||||
```
|
||||
|
||||
Register the first account. On a self-hosted instance, the first accepted registration receives wildcard admin access. Use that account for the initial admin login:
|
||||
|
||||
```text
|
||||
https://chat.example.com/admin
|
||||
```
|
||||
|
||||
Complete the initial setup from the admin dashboard. At minimum, review:
|
||||
|
||||
- Branding and instance name.
|
||||
- Registration mode: open, approval, or closed.
|
||||
- Email delivery.
|
||||
- Captcha policy if you open public registration.
|
||||
- Single-community mode if you want one default community instead of many user-created communities.
|
||||
- Voice regions and LiveKit reachability if you are enabling voice.
|
||||
|
||||
## Email
|
||||
|
||||
Email is disabled by default. To enable SMTP, set these in `.env` and restart `api`, `worker`, and `admin`:
|
||||
|
||||
```ini
|
||||
FLUXER_EMAIL_ENABLED=true
|
||||
FLUXER_EMAIL_PROVIDER=smtp
|
||||
FLUXER_EMAIL_FROM_EMAIL=noreply@example.com
|
||||
FLUXER_EMAIL_FROM_NAME=Fluxer
|
||||
FLUXER_EMAIL_SMTP_HOST=smtp.example.com
|
||||
FLUXER_EMAIL_SMTP_PORT=587
|
||||
FLUXER_EMAIL_SMTP_USERNAME=example
|
||||
FLUXER_EMAIL_SMTP_PASSWORD=example-secret
|
||||
FLUXER_EMAIL_SMTP_SECURE=true
|
||||
```
|
||||
|
||||
Then test the SMTP configuration from `/admin/instance-config`.
|
||||
|
||||
## Voice and video
|
||||
|
||||
Fluxer uses LiveKit for voice and video. Caddy routes `/livekit` to LiveKit's HTTP/WebSocket signaling port, but browser media flows over WebRTC:
|
||||
|
||||
- `7882/udp` is the normal media path.
|
||||
- `7881/tcp` is the TCP fallback path.
|
||||
- `7880/tcp` stays private behind Caddy for signaling.
|
||||
|
||||
On a VPS with `7881/tcp` and `7882/udp` open, LiveKit can usually auto-detect the public IP. Behind NAT, Cloudflare Tunnel, or restrictive networks, add a TURN server and configure LiveKit for it.
|
||||
|
||||
## Backups
|
||||
|
||||
Back up these items before upgrades and on a regular schedule:
|
||||
|
||||
- `.env`
|
||||
- `postgres-data`
|
||||
- `seaweedfs-data`
|
||||
|
||||
For a cold backup:
|
||||
|
||||
```bash
|
||||
docker compose stop api worker gateway admin app-proxy media-proxy static-proxy livekit
|
||||
docker run --rm -v fluxer_postgres-data:/data -v "$PWD/backups:/backup" alpine tar czf /backup/postgres-data.tgz -C /data .
|
||||
docker run --rm -v fluxer_seaweedfs-data:/data -v "$PWD/backups:/backup" alpine tar czf /backup/seaweedfs-data.tgz -C /data .
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
For production, prefer a Postgres-native dump plus object-storage backup so you do not need to stop the instance.
|
||||
|
||||
## Upgrading
|
||||
|
||||
The default image tag is `v1`, which tracks the latest compatible release:
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The `fluxer-static` image is part of the default stack, so static asset updates are picked up by the same pull-and-restart flow.
|
||||
|
||||
To pin a specific release, set `FLUXER_IMAGE_TAG` in `.env` to the release tag you want, then pull and restart.
|
||||
|
||||
## Getting help
|
||||
|
||||
- File issues and follow development on [GitHub](https://github.com/fluxerapp/fluxer).
|
||||
- For direct access to the team, see [Operator Pass](operator-pass.md).
|
||||
Reference in New Issue
Block a user