Skip to content
Fluxer API

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.

RequirementValue
HostAny machine that runs Docker with Linux containers. A Linux server, or a Mac or Windows PC with Docker Desktop
RuntimeDocker Engine 24 or newer, or Podman 5 or newer, with the Compose plugin v2.20.2 or newer
ProcessorIntel, AMD and Apple Silicon all work. Every image ships for amd64 and arm64, and Docker pulls the matching one
CPU2 vCPU minimum, 4 vCPU for a small active community. No service sets a CPU limit, so every container sees all of them
RAM8 GB minimum with the shipped defaults, 16 GB for a small active community. 4 GB needs the low-memory overrides
Disk20 GB minimum, more for uploads
Inbound ports80/tcp, 443/tcp and 443/udp for the web tier, plus 7881/tcp and 7882/udp for voice media
DNSAn A record for the hostname pointing at this machine, optionally an AAAA record
OutboundWorking DNS resolution from inside the containers
Host toolscurl 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.

Check what you have:

Terminal window
docker --version
docker compose version

If 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:

Terminal window
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.

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.

Open these ports on the host:

PortProtocolUsed by
22tcpSSH, on a host you reach remotely
80tcpACME HTTP validation and the redirect to HTTPS
443tcpThe web app, HTTP API, Gateway and admin dashboard
443udpHTTP/3 for the same traffic
7881tcpLiveKit media, published by the LiveKit container
7882udpLiveKit 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:

Terminal window
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

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:

Terminal window
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:

Terminal window
shasum -a 256 -c install.sh.sha256

On Windows:

Terminal window
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 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:

Terminal window
sh install.sh --domain chat.example.com --email you@example.com

On Windows:

Terminal window
powershell -ExecutionPolicy Bypass -File .\install.ps1 -Domain chat.example.com -Email you@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, 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.

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=8443
FLUXER_HTTPS_PORT=8443
Terminal window
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.

FlagMeaning
--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-interactiveNever prompt. A missing required value is an error
--dry-runPrint the plan. Write nothing and start nothing
--no-startWrite everything and skip docker compose up -d
--updateUpgrade: record, back up, refresh, pull, recreate, verify
--rollbackRestore the images and stack files that the newest --update run recorded
--allow-rootPermit 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.

Read the Linux and macOS installer or Windows installer before running it.

Watch the stack settle:

Terminal window
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:

Terminal window
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:

Terminal window
$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.

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.

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:

Terminal window
cd ~/fluxer
mkdir -p backups
docker 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:

Terminal window
mkdir -Force backups
docker compose exec -T postgres pg_dump -U fluxer -d fluxer --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:

Terminal window
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 for scheduled backups and Restore a backup for recovery.

Take the instance down and delete its data:

Terminal window
docker compose down -v

The default tag v1 tracks the latest compatible release. The same script upgrades the instance:

Terminal window
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 covers what the backup holds, rolling back, pinning a release and reclaiming disk.

File issues and follow development on GitHub.