Files
fluxer/fluxer_docs/src/content/docs/operator/get-started.mdx
T

312 lines
18 KiB
Plaintext

---
# SPDX-License-Identifier: AGPL-3.0-or-later
title: Get started
description: Bring up a Fluxer instance with Docker Compose, from a fresh host to first login.
---
import InstallerChecksum from '@/components/InstallerChecksum.astro';
:::tip[Plutonium and donations fund Fluxer]
We are grateful to everyone supporting the project through [Fluxer Plutonium](https://fluxer.app/plutonium) or [donations](https://fluxer.app/donate). All of our code is free and open source on [GitHub](https://github.com/fluxerapp/fluxer). The Operator Pass is coming, and adds a direct line to the team for help and feedback.
:::
By the end you have a Fluxer instance on a hostname you own, running the web app, HTTP API, Gateway, admin dashboard, uploads, search and voice signalling in Docker containers on one machine. Allow about 30 minutes, most of it spent downloading images.
## What you need
| Requirement | Value |
| --- | --- |
| Host | Any machine that runs Docker with Linux containers. A Linux server, or a Mac or Windows PC with Docker Desktop |
| Runtime | Docker Engine 24 or newer, or Podman 5 or newer, with the Compose plugin v2.20.2 or newer |
| Processor | Intel, AMD and Apple Silicon all work. Every image ships for `amd64` and `arm64`, and Docker pulls the matching one |
| CPU | 2 vCPU minimum, 4 vCPU for a small active community. No service sets a CPU limit, so every container sees all of them |
| RAM | 8 GB minimum with the shipped defaults, 16 GB for a small active community. 4 GB needs the [low-memory overrides](/operator/configuration/#resources) |
| Disk | 20 GB minimum, more for uploads |
| Inbound ports | 80/tcp, 443/tcp and 443/udp for the web tier, plus 7881/tcp and 7882/udp for voice media |
| DNS | An `A` record for the hostname pointing at this machine, optionally an `AAAA` record |
| Outbound | Working DNS resolution from inside the containers |
| Host tools | `curl` and `openssl` on Linux. macOS ships both. The Windows installer needs neither |
Memory limits are ceilings, not reservations. On a 4 GB host, lower the `api` and `worker` limits first. With Docker Desktop, check the RAM assigned to its Linux virtual machine rather than the host's total.
The stack is one Compose project. The edge and LiveKit are the only services that publish ports.
The steps below use the bundled Caddy, which obtains and renews a TLS certificate for the hostname and needs no configuration. To put your own proxy in front, add the `docker-compose.proxy.yml` overlay. The stack then leaves 80 and 443 alone and publishes one plain HTTP port for your proxy to forward to. [Behind your own reverse proxy](/operator/reverse-proxy/) has the overlay, the requirements, and worked configuration for nginx, Caddy, Traefik, HAProxy, Apache, a Cloudflare Tunnel and Nginx Proxy Manager.
## Step 1: Install Docker
Check what you have:
```bash
docker --version
docker compose version
```
If both answer, skip ahead. On a Mac or a Windows PC, install [Docker Desktop](https://docs.docker.com/desktop/) and leave it on Linux containers, which gives you both.
Debian and Ubuntu need Docker's apt repository from [Install Docker Engine](https://docs.docker.com/engine/install/), because the distribution's own `docker.io` package ships no Compose plugin. Follow the [post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/) to add your user to the `docker` group, then log out and back in so the commands below run without `sudo`.
Linux also needs `curl` and `openssl`, which the steps below use:
```bash
sudo apt-get install -y curl openssl
```
Swap `apt-get` for `dnf`, `zypper`, `pacman` or `apk` elsewhere. The package names are the same, and `curl --version` and `openssl version` both answer once the install finishes.
## Step 2: Point DNS at the host
Choose the hostname the instance answers on. Step 4 writes it into `.env` as `FLUXER_DOMAIN`, and every example below uses `chat.example.com` in its place.
Create an `A` record for that hostname pointing at the machine's public address, and an `AAAA` record when the machine has a public IPv6 address. A hosting provider shows that address in its console. On a home connection the address belongs to the router, and the router's status page shows it.
:::caution[Finish setup before anyone else reaches the hostname]
An unconfigured instance takes registrations from anyone, and lets any account that registers read and change the instance configuration. Registration defaults to `open`. The window opens the moment DNS and the ports are live, so finish the wizard in [Step 6](#first-run) in the same sitting that brings the stack up.
:::
## Step 3: Open the ports
Open these ports on the host:
| Port | Protocol | Used by |
| --- | --- | --- |
| 22 | tcp | SSH, on a host you reach remotely |
| 80 | tcp | ACME HTTP validation and the redirect to HTTPS |
| 443 | tcp | The web app, HTTP API, Gateway and admin dashboard |
| 443 | udp | HTTP/3 for the same traffic |
| 7881 | tcp | LiveKit media, published by the LiveKit container |
| 7882 | udp | LiveKit media, published by the LiveKit container |
A machine behind a home router needs 80, 443 and both LiveKit ports forwarded to it. The certificate is issued from the public internet, and voice media arrives on the LiveKit ports directly. Running your own reverse proxy changes only the first two. The proxy holds them, and the stack binds a plain HTTP port on the loopback. 7881 and 7882 have to reach the host either way, because voice media never goes through a proxy.
For another port, open that one instead of 443 and read [Serving on another port](#serving-on-another-port).
On a cloud VM, open them in the provider's firewall or security group. On a Linux host running firewalld:
```bash
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-port=80/tcp --add-port=443/tcp --add-port=443/udp \
--add-port=7881/tcp --add-port=7882/udp
sudo firewall-cmd --reload
```
## Step 4: Bring up the instance
The installer downloads the stack files, writes `.env`, generates every secret, and starts the instance. Download it, check the published SHA-256, read it, then run it.
On Linux:
```bash
curl -fsSLO https://fluxer.dev/install.sh
curl -fsSLO https://fluxer.dev/install.sh.sha256
sha256sum -c install.sh.sha256
```
On macOS, the same downloads and:
```bash
shasum -a 256 -c install.sh.sha256
```
On Windows:
```powershell
Invoke-WebRequest https://fluxer.dev/install.ps1 -OutFile install.ps1
Invoke-WebRequest https://fluxer.dev/install.ps1.sha256 -OutFile install.ps1.sha256
(Get-FileHash .\install.ps1 -Algorithm SHA256).Hash -eq (Get-Content .\install.ps1.sha256).Split()[0]
```
`install.sh: OK` on Linux and macOS, and `True` on Windows, mean the file arrived whole. The digest of the copy this site serves is <InstallerChecksum script="install.sh" /> for `install.sh` and <InstallerChecksum script="install.ps1" /> for `install.ps1`.
Read the script before you run it. It writes `.env`, and that file holds every secret the instance has.
The run below uses the bundled TLS. Add `--tls proxy` when something else terminates TLS, and read [Behind your own reverse proxy](/operator/reverse-proxy/) first.
Then run it with the hostname from Step 2. `--email` is optional and sets the contact address on outgoing web push. It defaults to `admin@` and the hostname.
```bash
sh install.sh --domain chat.example.com
```
On Windows:
```powershell
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Domain chat.example.com
```
The run prints one line per phase and ends with the URL to open.
`~/fluxer` then holds `docker-compose.yml`, `docker-compose.proxy.yml`, `tunnel.compose.yml`, `external-object-store.compose.yml`, `Caddyfile` and `.env.example`, plus a `.env` readable only by you. Every value that ships as `CHANGE_ME` in `.env.example` is a fresh random value. Every command from here on runs in that directory.
### Serving on another port
The installer writes an `.env` for `https` on `443` and has no flag for another port. The two lines below in `.env` move the instance to another port. `FLUXER_PUBLIC_PORT` sets the port of the public address, and `FLUXER_HTTPS_PORT` sets the host port the edge publishes HTTPS on:
```ini
FLUXER_PUBLIC_PORT=8443
FLUXER_HTTPS_PORT=8443
```
```bash
docker compose up -d
```
`FLUXER_PUBLIC_PORT` is the port of the public address, and every endpoint the services advertise follows it. It does not move what the host publishes, so `FLUXER_HTTPS_PORT` has to name the same port. Move `FLUXER_PUBLIC_SCHEME` with them when the new port serves plain HTTP, and set `FLUXER_HTTP_PORT` instead of `FLUXER_HTTPS_PORT`. `.env.example` ships beside `.env` and writes out both complete recipes.
Host 80 stays published on the `https` recipe and still answers the ACME challenge. Let's Encrypt only ever connects to the public 80 or 443, so the certificate is issued if a router in front forwards public 80 to this host. Serve your own certificate from the `Caddyfile` when it cannot.
Leave `FLUXER_PUBLIC_ORIGIN` commented out, or keep it consistent with the public scheme, domain and port. Conflicting addresses can cause sign-in failures and setup loops. See [The public origin](/operator/configuration/#the-public-origin).
### Installer flags
| Flag | Meaning |
| --- | --- |
| `--domain <host>` | Hostname the instance answers on. Prompted when absent |
| `--email <address>` | Contact address on outgoing web push, written as `FLUXER_VAPID_EMAIL`. Default `admin@` and the hostname |
| `--dir <path>` | Working directory. Default `~/fluxer`, or the current directory when it holds an instance |
| `--ref <git ref>` | Ref the stack files come from. Defaults to the image tag, and to `main` when that tag is `v1` or `latest` |
| `--image-tag <tag>` | Image tag the stack runs. Default `v1` |
| `--tls <mode>` | `bundled` or `proxy`. Default `bundled` |
| `--edge-bind <addr:port>` | Plain HTTP bind under `--tls proxy`. Default `127.0.0.1:8080` |
| `--non-interactive` | Never prompt. A missing required value is an error |
| `--dry-run` | Print the plan. Write nothing and start nothing |
| `--no-start` | Write everything and skip `docker compose up -d` |
| `--update` | Upgrade: record, back up, refresh, pull, recreate, verify |
| `--rollback` | Restore the images and stack files that the newest `--update` run recorded |
| `--allow-root` | Permit running as root |
| `--engine <command>` | Container engine to drive. Default `docker`, or `podman` when `docker` is absent |
Run `--dry-run` first to see what a set of flags does. The PowerShell script takes the same flags as `-Domain`, `-Email` and so on, and has no `-AllowRoot`. [Upgrading](/operator/upgrading/#run-the-upgrade) has the flags that only `--update` and `--rollback` read, and the exit codes.
### The script is the reference
Read the [Linux and macOS installer](https://fluxer.dev/install.sh) or [Windows installer](https://fluxer.dev/install.ps1) before running it.
## Step 5: Check that it works
Watch the stack settle:
```bash
cd ~/fluxer
docker compose ps
docker compose logs -f api
```
Every service reads `running` or `healthy` except `seaweedfs-init`, which creates the upload buckets and then reads `exited (0)`.
Then the public probes. Replace the origin with your own, port included when the instance answers on one:
```bash
base=https://chat.example.com
for p in /_health /api/_health /gateway/_health /media/_health /.well-known/fluxer /; do
printf '%s %s\n' "$(curl -sS -o /dev/null -w '%{http_code}' "$base$p")" "$p"
done
```
On Windows:
```powershell
$base = 'https://chat.example.com'
foreach ($p in '/_health', '/api/_health', '/gateway/_health', '/media/_health', '/.well-known/fluxer', '/') {
'{0} {1}' -f (Invoke-WebRequest "$base$p" -UseBasicParsing).StatusCode, $p
}
```
Every path returns 200. `/.well-known/fluxer` lists the endpoints clients use, with your own hostname in them.
<a id="first-run"></a>
## Step 6: Create the first admin
Open the hostname in a browser. A fresh instance is unconfigured, so it serves the setup wizard.
The wizard first asks how members sign in. Usernames is the default and needs no email setup at all. Email suits an instance that sends mail through SMTP. The choice is fixed once the owner account exists. [Sign-in methods](/operator/sign-in/) explains both.
Then create the owner account, and choose branding, registration mode, community policy, media expiry, integrations and premium settings. Completing the wizard closes setup access.
The first registration receives the wildcard admin ACL, which is full access to the admin dashboard, unless registration is set to hold new accounts for approval. On a username instance that is the first registration with a password. On an email instance it is the first registration that includes an email address. Finishing the wizard grants that same ACL to whichever account completes it, when that account holds none.
On a username instance the wizard shows the owner's [recovery kit](/operator/sign-in/#recovery-kits) next. Store it somewhere safe. Without the kit, a forgotten owner password needs another admin to issue a [reset link](/operator/sign-in/#admin-reset-links). Register a passkey or a second admin account before you open registration.
### On an email instance
Create the owner account with an email address at a domain you control. Email delivery is optional, and [Email](/operator/configuration/#email) sets it up at any time.
Once email is on, the address goes through a DNS check before the account exists. Email counts as on when the switch is set and the provider is `smtp` with a complete SMTP configuration, so `FLUXER_EMAIL_ENABLED=true` on its own does not turn the check on. The address domain has to publish an `MX` record, or an `A` or `AAAA` record as a fallback. A name that resolves on your own network alone, such as a `.lan`, `.internal` or `home.arpa` name, publishes neither, and the account form answers `That email domain cannot receive mail.` whatever the address looks like. Use a domain with public records, or leave email off.
`.env.example` ships `FLUXER_EMAIL_ENABLED=false`, which marks every address verified at creation and sends no mail at all. A forgotten owner password therefore has no email reset. Record it, and register a passkey or a second admin account before you open registration.
### The admin dashboard
Sign in to the admin dashboard at `https://chat.example.com/admin` with the account holding the wildcard ACL. The **Instance Config** page has everything the wizard asked, plus registration mode, approvals and integration keys. **Limit Config** holds the instance limits published to clients. **Voice Regions** and **Voice Servers** come seeded, so voice needs no setup there.
The desktop client opens the hosted web app for its release channel, so reach your own instance in a browser. [This feedback post](https://feedback.fluxer.com/p/641) tracks that, and is being worked on.
[Configuration](/operator/configuration/) lists every runtime setting and the environment variable each one overrides. [Deployment availability](/http-api/deployment-availability/) lists the routes that exist only on the hosted deployment.
## Backups
Recovery requires `.env`, a database dump and a backup of `seaweedfs-data`, which holds uploads, avatars, reports and harvests. See [Volumes and buckets](/operator/configuration/#volumes-and-buckets) for everything that needs backing up.
The dump costs no downtime, so run it on a schedule while the stack serves. It runs inside the `postgres` container and takes the role and database from that container's environment, which follows `FLUXER_POSTGRES_USERNAME` and `FLUXER_POSTGRES_DATABASE`. It is for the bundled database. A database outside the stack is backed up with its own tools:
```bash
cd ~/fluxer
mkdir -p backups
docker compose exec -T postgres sh -c 'pg_dump -U $POSTGRES_USER -d $POSTGRES_DB --format=custom' \
> "backups/fluxer-$(date -u +%Y%m%dT%H%M%SZ).dump"
```
PowerShell writes UTF-16 through `>`, which corrupts a binary dump. On Windows, write the dump inside the container and copy it out:
```powershell
mkdir -Force backups
docker compose exec -T postgres sh -c 'pg_dump -U $POSTGRES_USER -d $POSTGRES_DB --format=custom -f /tmp/fluxer.dump'
docker compose cp postgres:/tmp/fluxer.dump backups/fluxer.dump
docker compose exec -T postgres rm /tmp/fluxer.dump
```
The uploads copy stops the stack, because a live copy can catch a file mid-write. Compose prefixes volume names with the project name, which the stack sets to `fluxer`:
```bash
docker compose stop
docker run --rm -v fluxer_seaweedfs-data:/data -v "$PWD/backups:/backup" alpine:3.22 \
tar czf /backup/seaweedfs-data.tgz -C /data .
docker compose up -d
```
In PowerShell write `${PWD}` in place of `$PWD`.
You are done when `backups` holds a dump and a tarball and every service reads `running` again.
`sh install.sh --update` takes both backups before upgrading. See [What the backup covers](/operator/upgrading/#what-the-backup-covers) for scheduled backups and [Restore a backup](/operator/upgrading/#restore-a-backup) for recovery.
### Remove the instance
:::danger[This deletes every account, message and upload]
`-v` deletes every named volume. Every account, message, upload, search index and certificate the instance holds is gone, and no `docker compose up -d` brings any of it back. Take the copies above first. To upgrade instead, run `sh install.sh --update`, below.
:::
Take the instance down and delete its data:
```bash
docker compose down -v
```
## Upgrading
The default tag `v1` tracks the latest compatible release. The same script upgrades the instance:
```bash
cd ~/fluxer
sh install.sh --update
```
`--update` records the running images, backs up the database and the uploads, refreshes the stack files, pulls, recreates, and verifies. It leaves every secret in `.env` untouched. On Windows it is `.\install.ps1 -Update`. Run `sh install.sh --update --dry-run` first to see the plan.
[Upgrading](/operator/upgrading/) covers what the backup holds, rolling back, pinning a release and reclaiming disk.
## Getting help
File issues and follow development on [GitHub](https://github.com/fluxerapp/fluxer).