Self-Hosting Guide
Deploy your own Fluxer server with Docker, configure it with JSON, and put it behind Caddy as a reverse proxy.
Contents
1. Prerequisites
Before starting, make sure you have the following installed on your server:
- A Linux server (Ubuntu 22.04+ or Debian 12+ recommended)
- Docker and Docker Compose (v2)
- Caddy (v2.7+)
- A domain name pointed at your server's IP address
- Ports 80 and 443 open for HTTPS
Note: Caddy automatically provisions and renews TLS certificates via Let's Encrypt. No manual certificate setup is needed.
Install Docker
# Install Docker (Ubuntu/Debian)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Verify installation
docker --version
docker compose version
Install Caddy
# Install Caddy (Ubuntu/Debian)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
2. Ports & Network
Understanding which ports are used and how traffic flows is important before deploying.
Required Ports
| Port | Protocol | Service | Exposure |
|---|---|---|---|
80 |
TCP | Caddy (HTTP) | Public — redirects to HTTPS |
443 |
TCP | Caddy (HTTPS) | Public — serves all external traffic |
3000 |
TCP | Fluxer API server | Localhost only (127.0.0.1) |
5432 |
TCP | PostgreSQL (optional) | Internal Docker network only |
Traffic Flow
All external traffic goes through Caddy. The Fluxer server is never exposed directly to the internet.
User (browser/client)
│
│ HTTPS (:443)
▼
Caddy (reverse proxy)
│
│ ── HTTP redirect (:80 → :443)
│
│ HTTP (:3000, localhost only)
▼
Fluxer Server (Docker)
│
│ Internal Docker network
▼
PostgreSQL / SQLite
Redirects
Caddy handles the following redirects automatically:
- HTTP → HTTPS — All traffic on port 80 is redirected to port 443. Caddy does this by default when you specify a domain in the Caddyfile.
- www → non-www (optional) — Add a redirect block if you want
www.fluxer.example.comto redirect tofluxer.example.com.
Optional www redirect in your Caddyfile:
www.fluxer.example.com {
redir https://fluxer.example.com{uri} permanent
}
Firewall Setup
If you use ufw, open only the ports Caddy needs:
# Allow HTTP (for ACME challenges and redirect)
sudo ufw allow 80/tcp
# Allow HTTPS
sudo ufw allow 443/tcp
# Allow SSH (don't lock yourself out)
sudo ufw allow 22/tcp
# Enable firewall
sudo ufw enable
# Verify
sudo ufw status
Do not open port 3000 in your firewall. The Fluxer server binds to 127.0.0.1:3000 and should only be accessible through the Caddy reverse proxy. Exposing it directly bypasses TLS and security headers.
Port 5432 (PostgreSQL) does not need a firewall rule. It runs inside the Docker network and is not exposed to the host at all.
3. Directory Structure
Create a directory for your Fluxer deployment:
mkdir -p /opt/fluxer/{config,data}
cd /opt/fluxer
Your final directory structure will look like this:
/opt/fluxer/
├── docker-compose.yml
├── config/
│ └── config.json
└── data/
├── db/ # Database files
├── uploads/ # User uploads
└── logs/ # Server logs
4. Configuration (config.json)
The main configuration file controls how your Fluxer server behaves. Create it at /opt/fluxer/config/config.json.
Minimal Configuration
A basic configuration to get started:
{
"server": {
"name": "My Fluxer Server",
"host": "0.0.0.0",
"port": 3000,
"publicUrl": "https://fluxer.example.com"
},
"database": {
"type": "sqlite",
"path": "/data/db/fluxer.db"
},
"storage": {
"uploadsPath": "/data/uploads",
"maxFileSize": "50MB"
},
"auth": {
"registrationEnabled": true,
"requireEmailVerification": false
}
}
Full Configuration Reference
A more complete configuration with all common options:
{
"server": {
"name": "My Fluxer Server",
"host": "0.0.0.0",
"port": 3000,
"publicUrl": "https://fluxer.example.com",
"trustedProxies": ["127.0.0.1", "172.16.0.0/12"]
},
"database": {
"type": "sqlite",
"path": "/data/db/fluxer.db"
},
"storage": {
"uploadsPath": "/data/uploads",
"maxFileSize": "50MB",
"allowedFileTypes": ["image/*", "video/*", "audio/*", "application/pdf"]
},
"auth": {
"registrationEnabled": true,
"requireEmailVerification": false,
"sessionTimeout": "30d",
"maxSessionsPerUser": 10
},
"email": {
"enabled": false,
"smtp": {
"host": "smtp.example.com",
"port": 587,
"secure": true,
"user": "noreply@example.com",
"password": "your-smtp-password"
},
"from": "Fluxer <noreply@example.com>"
},
"rateLimit": {
"enabled": true,
"windowMs": 60000,
"maxRequests": 100
},
"voice": {
"enabled": true,
"maxParticipants": 25
},
"logging": {
"level": "info",
"path": "/data/logs"
}
}
Configuration Options
server.publicUrl — Must match the domain you configure in Caddy. This is used for generating invite links, email links, and API responses.
Security: If you enable email verification, make sure to configure the email.smtp section with valid SMTP credentials. Without it, users won't be able to verify their accounts.
5. Docker Compose Setup
Create a docker-compose.yml in /opt/fluxer/:
version: "3.8"
services:
fluxer:
image: ghcr.io/fluxer/fluxer-server:latest
container_name: fluxer_server
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
volumes:
- ./config:/usr/src/app/config:ro
- ./data:/data
environment:
- NODE_ENV=production
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
Why 127.0.0.1:3000? — Binding to 127.0.0.1 ensures the port is only accessible locally. Caddy will handle all external traffic and TLS termination.
Using PostgreSQL instead of SQLite
For larger deployments, you may want to use PostgreSQL. Add a database service to your compose file:
version: "3.8"
services:
fluxer:
image: ghcr.io/fluxer/fluxer-server:latest
container_name: fluxer_server
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
volumes:
- ./config:/usr/src/app/config:ro
- ./data:/data
environment:
- NODE_ENV=production
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
container_name: fluxer_db
restart: unless-stopped
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: fluxer
POSTGRES_USER: fluxer
POSTGRES_PASSWORD: change-this-password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U fluxer"]
interval: 10s
timeout: 5s
retries: 5
Update your config.json database section to match:
{
"database": {
"type": "postgres",
"host": "postgres",
"port": 5432,
"name": "fluxer",
"user": "fluxer",
"password": "change-this-password"
}
}
Important: Change the default database password before deploying. Use a strong, randomly generated password.
6. Caddy Reverse Proxy
Caddy handles HTTPS and proxies requests to the Fluxer container. Edit the Caddyfile at /etc/caddy/Caddyfile.
Basic Setup
fluxer.example.com {
reverse_proxy 127.0.0.1:3000
encode gzip zstd
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
log {
output file /var/log/caddy/fluxer-access.log
format json
}
}
With WebSocket Support
If your Fluxer server uses WebSockets for real-time features, the basic config above already works — Caddy handles WebSocket upgrades automatically. For explicit configuration:
fluxer.example.com {
reverse_proxy 127.0.0.1:3000 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Timeouts for long-lived connections
transport http {
read_timeout 300s
write_timeout 300s
}
}
encode gzip zstd
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
Strict-Transport-Security "max-age=31536000; includeSubDomains"
-Server
}
log {
output file /var/log/caddy/fluxer-access.log {
roll_size 100mb
roll_keep 5
}
format json
}
}
Multiple Services
If you're also hosting a web client on a different subdomain:
# API / Server
fluxer.example.com {
reverse_proxy 127.0.0.1:3000
encode gzip zstd
}
# Web Client
app.example.com {
root * /var/www/fluxer-web
file_server
try_files {path} /index.html
encode gzip zstd
}
After editing the Caddyfile, reload Caddy:
sudo systemctl reload caddy
7. Starting the Server
Start the containers
cd /opt/fluxer
docker compose up -d
Check the logs
# Follow logs in real time
docker compose logs -f fluxer
# Check container status
docker compose ps
Verify Caddy
# Check Caddy status
sudo systemctl status caddy
# Test the endpoint
curl -I https://fluxer.example.com/api/health
Done! Your Fluxer server should now be accessible at https://fluxer.example.com. Caddy handles TLS certificates automatically.
8. Updating
To update your Fluxer server to the latest version:
cd /opt/fluxer
# Pull the latest image
docker compose pull
# Restart with the new image
docker compose up -d
# Verify it's running
docker compose ps
docker compose logs -f fluxer
Backups: Always back up your data/ directory before updating, especially the database. For SQLite: cp data/db/fluxer.db data/db/fluxer.db.bak. For PostgreSQL: docker exec fluxer_db pg_dump -U fluxer fluxer > backup.sql
9. Troubleshooting
Container won't start
# Check logs for errors
docker compose logs fluxer
# Verify config is valid JSON
python3 -m json.tool config/config.json
# Check file permissions
ls -la config/ data/
502 Bad Gateway from Caddy
- Ensure the Fluxer container is running:
docker compose ps - Verify the port binding matches the Caddyfile:
docker compose port fluxer 3000 - Check if the health endpoint responds locally:
curl http://127.0.0.1:3000/api/health
TLS certificate issues
- Ensure your domain's DNS A record points to your server
- Check ports 80 and 443 are open:
sudo ss -tlnp | grep -E ':80|:443' - Review Caddy logs:
sudo journalctl -u caddy --no-pager -n 50
Database permission errors
# Fix ownership for SQLite
sudo chown -R 1000:1000 /opt/fluxer/data/db
# Fix ownership for uploads
sudo chown -R 1000:1000 /opt/fluxer/data/uploads
Check resource usage
docker stats fluxer_server
Need help? Join the community or open an issue on GitHub.