Skip to content
h8d13 edited this page Oct 7, 2026 · 27 revisions

Step by step, deploy your own server.

It is very simple really:

Prerequisites:

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 --system

Firewall

Recommended: 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 status

The 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 docker publishes ports through its own iptables chain that bypasses ufw. So ufw protects against accidental host listeners, not against the ports compose publishes. The real surface is docker-compose.yaml ports: blocks. Rootless podman publishes through a userspace forwarder (regular host sockets), so ufw does apply there.


Get Caesar

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.


vCPUs depending on VPS

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_WORKERS and 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.


Domains

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.

Wiring up to Caesar

Set CAESAR_SITE in .env (the rest of the file is optional knobs with their defaults):

CAESAR_SITE=sub.example.com

Compose reads .env from the directory of docker-compose.yaml automatically, 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.

Custom Caddyfile

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

Volumes 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.

Starting the container

docker compose up -d

This 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.

Keeping it updated

docker compose pull && docker compose up -d --force-recreate

Data 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

Running your own build

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-recreate

Forks get their own ghcr.io/<you>/caesar from the Image workflow on push; change image: accordingly.

Discord Alternative per your rules. Fork of Sharkord.

Clone this wiki locally