Repository navigation
Home
Step by step, deploy your own server.
It is very simple really:
Any linux server (amd64 or arm64) with one of:
| engine | packages |
|---|---|
| Docker |
docker + compose plugin (docker compose) |
| Podman, rootless |
podman podman-compose
|
Commands below use docker compose; with podman swap in podman-compose, the
rest is identical. Optionally install sqlite3 and binutils for debugging.
Rootless (podman or rootless-docker) can't bind 80/443 by default. Allow it once, persisted across reboots:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/90-caesar.conf
sudo sysctl --systemRecommended: install ufw and disable your VPS provider built-in firewall. Reason being that a lot of cheap VPS providers do not support HTTP/3-Quic protocol.
Setting up the firewall locally:
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH adjust if you moved sshd off 22
ufw allow 443 # caddy: https (tcp) + http/3 (udp)
ufw allow 80/tcp # caddy ACME http-01 + http->https redirect
ufw enable && ufw statusThe catch is to not expose the CAESAR_PORT: "4991" as this is our reverse proxy through caddy.
You can test this by curl -v this port directly, it should hang at Trying...
Note: rootful
dockerpublishes ports through its owniptableschain that bypassesufw. So ufw protects against accidental host listeners, not against the ports compose publishes. The real surface isdocker-compose.yamlports:blocks. Rootless podman publishes through a userspace forwarder (regular host sockets), so ufw does apply there.
One file, no clone, no build tools:
mkdir caesar && cd caesar
curl -fsSLO https://raw.githubusercontent.com/h8d13/caesar/master/docker-compose.yaml
curl -fsSL -o .env https://raw.githubusercontent.com/h8d13/caesar/master/.env.example
curl -fsSLO https://raw.githubusercontent.com/h8d13/caesar/master/config.ini.env holds deploy settings, config.ini the rate limits; both ship with
defaults, see Configuration.
It runs the published image from
ghcr.io/h8d13/caesar
(amd64 + arm64). Pick the tag with CAESAR_IMAGE_TAG in .env: latest
(default, newest release), x.y.z to pin, edge for master. Each build carries
a GitHub provenance attestation (commit + workflow run):
gh attestation verify oci://ghcr.io/h8d13/caesar:latest --owner h8d13
Data lives in named volumes (caesar_caesar_data, caddy certs in
caesar_caddy_data), owned by the container's user whatever the engine:
no chown, nothing to create on the host.
Each mediasoup worker gets one port. Defaults fit a cheap 4 vCPUs server:
3 workers (CAESAR_WEBRTC_WORKERS in .env) on the published range in
docker-compose.yaml:
ports:
- "40000-40002:40000-40002/tcp"
- "40000-40002:40000-40002/udp"Note: one vCPU left free, you could also try 4. More workers: raise
CAESAR_WEBRTC_WORKERSand widen that range to match (your hardware becomes the limit).
See also net-buffers helper. This increases memory for media-soup in case you plan to run multiple screen-share/webcams at the same time.
Caddy runs caddy reverse-proxy straight from
docker-compose.yaml:
one site, CAESAR_SITE to caesar:CAESAR_PORT, automatic HTTPS, no config file.
Anything beyond that: Custom Caddyfile.
For further config refer to Caddy documentation: https://caddyserver.com/docs/
If you have a domain + a VPS, you can point to it by adding a A record:
www IN A XX.XX.XX.XXX
@ IN A XX.XX.XX.XXX
CAESAR_SITE is one canonical host, not a list: it is also the WebAuthn RP ID
(hardware 2FA / passkeys are bound to it), so the server refuses to boot on a
value containing a space or a comma. Serve the www record with its own
redirect block in a Custom Caddyfile:
example.com {
reverse_proxy caesar:4991
}
www.example.com {
redir https://example.com{uri} 301
}Both records then work, certs are issued for both, and the RP ID stays
example.com. Skip the www A record if you don't want the alias.
Set CAESAR_SITE in .env (the rest of the file is optional knobs with
their defaults):
CAESAR_SITE=sub.example.comCompose reads
.envfrom the directory ofdocker-compose.yamlautomatically, and passes every variable in it to the server.
It can be domains, IPs directly, Tailscale nodes, whatever. One host, no port:
the port belongs to Caddy, which publishes 80/443 (host:port only works for
prod-dev on :8443).
Both containers read it, for different reasons: Caddy as the site address (cert
name + which Host it answers), the server as the WebAuthn RP ID and expected
origin.
Full env var reference: Configuration.
Needed for aliases (www), extra instances or
on-demand TLS.
Two files next to docker-compose.yaml (gitignored if you run from a clone):
caddy/Caddyfile, e.g. the www example above. The primary site must match
CAESAR_SITE and proxy to caesar:<CAESAR_PORT>.
docker-compose.override.yaml, auto-loaded by docker compose and
podman-compose:
services:
caddy:
command: caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:roVolumes merge with the base file, certs in caddy_data stay. Apply with
docker compose up -d caddy. Later edits reload live:
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile.
docker compose up -dThis runs Caesar behind Caddy on 443. Access at: https://yourdomain.ext or with www depending on how you set it up.
Then docker logs caesar on first run will give you a unique one-time use UUID.
Login through the main entry page: First user is always allowed without an invite.
Go to DevTools > Console: useToken("UUID") this sets you as the server owner.
docker compose pull && docker compose up -d --force-recreateData volumes are kept. --force-recreate because podman-compose leaves a
running container on its old image when only the pulled tag changed.
Optionally remove old images
docker image prune
Test the checkout first with prod-dev. To deploy it,
build the image under a tag of your choice and point CAESAR_IMAGE_TAG at it:
docker build -f Dockerfile.prod -t ghcr.io/h8d13/caesar:local \
--build-arg CAESAR_BUILD_VERSION=$(git rev-parse --short HEAD) .
echo 'CAESAR_IMAGE_TAG=local' >> .env
docker compose up -d --force-recreateForks get their own ghcr.io/<you>/caesar from the Image workflow on push;
change image: accordingly.
Made with 🖤 CHANGELOG