Get started
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
Section titled “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 |
| 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 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
Section titled “Step 1: Install Docker”Check what you have:
docker --versiondocker compose versionIf both answer, skip ahead. On a Mac or a Windows PC, install Docker Desktop and leave it on Linux containers, which gives you both.
Debian and Ubuntu need Docker’s apt repository from Install Docker Engine, because the distribution’s own docker.io package ships no Compose plugin. Follow the post-installation steps 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:
sudo apt-get install -y curl opensslSwap 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
Section titled “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.
Step 3: Open the ports
Section titled “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.
On a cloud VM, open them in the provider’s firewall or security group. On a Linux host running firewalld:
sudo firewall-cmd --permanent --add-service=sshsudo firewall-cmd --permanent --add-port=80/tcp --add-port=443/tcp --add-port=443/udp \ --add-port=7881/tcp --add-port=7882/udpsudo firewall-cmd --reloadStep 4: Bring up the instance
Section titled “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:
curl -fsSLO https://fluxer.dev/install.shcurl -fsSLO https://fluxer.dev/install.sh.sha256sha256sum -c install.sh.sha256On macOS, the same downloads and:
shasum -a 256 -c install.sh.sha256On Windows:
Invoke-WebRequest https://fluxer.dev/install.ps1 -OutFile install.ps1Invoke-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 5cc22e143bb19e25b2377277257c7f58533cf8f6d25380d9e5048ed36f197eb9 for install.sh and f4a5592ff552a8765b9d7eafb67b98104d08e894b93bba0e5f9c6daecbc5c6d5 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 first.
Then run it, with the hostname from Step 2. --email becomes the contact address on outgoing web push:
sh install.sh --domain chat.example.com --email you@example.comOn Windows:
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Domain chat.example.com -Email you@example.comThe 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, 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
Section titled “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:
FLUXER_PUBLIC_PORT=8443FLUXER_HTTPS_PORT=8443docker compose up -dFLUXER_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.
Installer flags
Section titled “Installer flags”| Flag | Meaning |
|---|---|
--domain <host> | Hostname the instance answers on. Prompted when absent |
--email <address> | Contact address written as FLUXER_VAPID_EMAIL. Prompted when absent |
--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 has the flags that only --update and --rollback read, and the exit codes.
The script is the reference
Section titled “The script is the reference”Read the Linux and macOS installer or Windows installer before running it.
Step 5: Check that it works
Section titled “Step 5: Check that it works”Watch the stack settle:
cd ~/fluxerdocker compose psdocker compose logs -f apiEvery 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:
base=https://chat.example.comfor 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"doneOn Windows:
$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.
Step 6: Create the first admin
Section titled “Step 6: Create the first admin”Open the hostname in a browser. A fresh instance is unconfigured, so it serves the setup wizard.
Create an account, then choose branding, registration mode, community policy, media expiry, integrations and premium settings. Completing the wizard closes setup access.
Create the owner account with an email address at a domain you control. The first registration that includes an email address receives the wildcard admin ACL, which is full access to the admin dashboard, unless registration is set to hold new accounts for approval. Finishing the wizard grants that same ACL to whichever account completes it, when that account holds none.
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.
Then 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. Issue #1088 tracks that, and is being worked on.
Configuration lists every runtime setting and the environment variable each one overrides. Deployment availability lists the routes that exist only on the hosted deployment.
Backups
Section titled “Backups”Recovery requires .env, a database dump and a backup of seaweedfs-data, which holds uploads, avatars, reports and harvests. See Volumes and buckets for everything that needs backing up.
The dump costs no downtime, so run it on a schedule while the stack serves:
cd ~/fluxermkdir -p backupsdocker compose exec -T postgres pg_dump -U fluxer -d fluxer --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:
mkdir -Force backupsdocker compose exec -T postgres pg_dump -U fluxer -d fluxer --format=custom -f /tmp/fluxer.dumpdocker compose cp postgres:/tmp/fluxer.dump backups/fluxer.dumpdocker compose exec -T postgres rm /tmp/fluxer.dumpThe 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:
docker compose stopdocker 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 -dIn 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 for scheduled backups and Restore a backup for recovery.
Remove the instance
Section titled “Remove the instance”Take the instance down and delete its data:
docker compose down -vUpgrading
Section titled “Upgrading”The default tag v1 tracks the latest compatible release. The same script upgrades the instance:
cd ~/fluxersh 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 covers what the backup holds, rolling back, pinning a release and reclaiming disk.
Getting help
Section titled “Getting help”File issues and follow development on GitHub.