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

Setting up dev

The host needs a container engine only (docker compose or podman-compose): no node, no pnpm. compose.dev.yaml bind-mounts the checkout and runs Vite (client) + tsx watch (server); node_modules live in volumes.

docker compose -f compose.dev.yaml up --build

http://localhost:5173, then useToken("dev") in the DevTools console. Edits on the host hot-reload: Vite HMR for the client, tsx watch restarts the server. Ports are published on host loopback only (5173, 4991, 40000 tcp+udp).

Every start runs pnpm install --frozen-lockfile into the volumes, so a pulled lockfile change is picked up by restarting the container. Everything else runs inside it as well:

docker compose -f compose.dev.yaml exec caesar pnpm magic   # format + types + lint
docker compose -f compose.dev.yaml exec caesar pnpm test
docker compose -f compose.dev.yaml exec caesar pnpm add -F @caesar/server <pkg>

Dev data lives in apps/server/data/ (gitignored); delete it to start over. docker compose -f compose.dev.yaml down -v also drops the node_modules volumes (pnpm's store lives inside the root one) and the cached pnpm binary.

Rootful docker writes container-created files (apps/server/data, build output) as root into the checkout; rootless podman/docker map them to your user.

Migration from bun to pnpm was done for two reasons:

  • Allows better handling of deps/conf files.
  • Package manager bun was segfaulting/sigill during dev.

Dependencies

pnpm install fetches mediasoup's prebuilt worker from its releases (linux x64/arm64, kernel 6/7, macOS arm64, windows x64), so nothing gets compiled.

Dependencies used by more than one package are pinned once in the catalog: of pnpm-workspace.yaml and referenced as "catalog:" in each package.json, so overlapping packages resolve to a single copy. catalogMode: strict makes pnpm add of a cataloged dependency use the catalog; bump versions there.


Prod-dev testing

compose.prod-dev.yaml builds the checkout with Dockerfile.prod and serves it on https://localhost:8443 (own project, ports and container names, so it runs next to a real prod stack):

docker compose -f compose.prod-dev.yaml up --build
docker compose -f compose.prod-dev.yaml down    # wipes app data

App data lives in the container on purpose, so every run after down starts clean (new owner token in docker logs caesar-prod-dev). The UI shows version dev; prefix CAESAR_BUILD_VERSION=$(git rev-parse --short HEAD) to stamp the commit. WebRTC uses 41000 (tcp+udp).

Self-signed cert from Caddy's local CA, so https://localhost:8443 works without DNS or a public CA. Can also go to DevTools look out for alt-svc to say h3 (HTTP3 working).

Trust the local CA once. Clicking through the warning loads pages, but service workers (sw.js, push) refuse untrusted certs: SecurityError: Failed to register a ServiceWorker ... SSL certificate error. The CA lives in its own volume, so it survives down and rebuilds:

docker cp caesar-caddy-prod-dev:/data/caddy/pki/authorities/local/root.crt caddy-prod-dev-root.crt
sudo trust anchor --store caddy-prod-dev-root.crt # p11-kit (Arch, Fedora)

Restart the browser after. Undo with sudo trust anchor --remove caddy-prod-dev-root.crt; removing the volume (down -v) creates a new CA that needs trusting again.

With podman: podman-compose / podman cp instead, everything else is the same.


Stack

tRPC Mediasoup Drizzle ORM React Radix UI ShadCN UI Tailwind CSS


Pre-commit

.pre-commit-config.yaml runs host-side file checks only (yaml/json syntax, merge markers, large files, symlinks): pre-commit install once. Formatting needs node, so it runs in the dev container (pnpm format, part of pnpm magic) and CI enforces it with format:check.

Discord Alternative per your rules. Fork of Sharkord.

Clone this wiki locally