cd /news/developer-tools/freeleaf-an-free-and-open-latex-coll… · home › topics › developer-tools › article
[ARTICLE · art-152757] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

FreeLeaf: An free and open latex collaborative suite

FreeLeaf has launched as a free, open-source, self-hostable collaborative LaTeX editing platform that runs entirely on the user's own infrastructure, with no third-party cloud or account required. The stack combines Django and Django Ninja for the API, React with Vite and TypeScript for the web app, a Node y-websocket service for Yjs collaboration, a sandboxed TeX Live compile worker, Postgres, MinIO and Mailpit, and starts with `docker compose up` across six services. Production deployment requires a separate explicit build of the `freeleaf-texlive:latest` image, which the standard `docker compose -f docker-compose.prod.yml up -d --build` command skips, causing compiles to fail until it is built on the same Docker host the compile service runs on.

read4 min views1 publishedOct 19, 2026
FreeLeaf: An free and open latex collaborative suite
Image: Michielbdejong (auto-discovered)

An open and free, self-hostable, collaborative LaTeX editing platform. Self-host it on your own infrastructure for full digital sovereignty over your documents and data — no third-party cloud, no account with someone else's ToS standing between you and your own research.

Full spec: Plan.md. Working conventions for contributors/agents: CLAUDE.md.

See Status.md for current progress.

Django + Django Ninja (apps/api) · React + Vite + TS ( apps/web) · Node/ y-websocket (apps/collab) · sandboxed TeX Live ( apps/compile) · Postgres · MinIO · Mailpit. See Plan.md §4.

Requires Docker and Docker Compose.

docker compose up

This starts:

Service URL
web (React SPA) http://localhost:5173
api (Django + Ninja) http://localhost:8000/api/health
collab (Yjs sync) ws://localhost:1234 (health: http://localhost:1234/health )
compile (sandboxed TeX Live runner) http://localhost:8100/health
Mailpit (dev inbox for magic links) http://localhost:8025
MinIO console http://localhost:9001

A fresh clone should build and start with no extra setup.

Always open the app at http://localhost:5173, not http://127.0.0.1:5173. Browsers treat the two as different sites, so cookies from the api (at localhost:8000) won't reliably carry over if you mix them — this bites hardest in Safari.

cp .env.prod.example .env.prod
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build
docker compose -f docker-compose.prod.yml build texlive

This builds production images (gunicorn + collected/whitenoise-served static for the api, an nginx-served Vite build for the web) and runs a single public-facing web container that reverse-proxies /api, /admin, /static, and /collab to the internal services — everything is same-origin in production, which avoids the CORS/cross-site-cookie fragility dev's separate :5173/:8000 origins have. Only web is meant to be internet-facing; terminate TLS in front of it (a managed load balancer, or your own Caddy/nginx with certs proxying to web's port) — apps/web/nginx.conf only serves plain HTTP.

The second command is not optional. texlive (the sandboxed compile worker image, built from docker/texlive/Dockerfile) is deliberately excluded from the profile the first command builds — it's never run as a long-lived container, only spawned per compile job by compile via the Docker Engine API, referencing the image by a plain local tag (freeleaf-texlive:latest, no registry). up -d --build skips it entirely, so compiles will fail with no image found until you build it explicitly, on the same Docker host compile runs on. Re-run this build any time docker/texlive/Dockerfile changes — nothing rebuilds it for you automatically.

docker-compose.prod.yml uses name: freeleaf-prod so it never shares containers/volumes with a dev stack running from the same checkout; its example port (8001) is chosen so both can run side by side during testing — map to 80/ 443 for a real deployment.

Running the web app and api on separate subdomains instead (your own reverse proxy, not apps/web/nginx.conf) works too, but DJANGO_ALLOWED_HOSTS, FRONTEND_URL, CORS_ALLOWED_ORIGINS, and CSRF_TRUSTED_ORIGINS stop being the same value — see the comment block at the top of .env.prod.example for exactly which one needs which host. Getting DJANGO_ALLOWED_HOSTS wrong here surfaces as a 400 with nothing useful in the browser; check docker compose -f docker-compose.prod.yml logs api for DisallowedHost first. If you take this route, your own reverse proxy also needs a read/proxy timeout comfortably above 100s — a large document (big images, custom fonts) can legitimately take well over a minute to compile, and apps/web/nginx.conf's own generous timeout for /api never applies here since traffic to the api host bypasses it entirely. A compile that fails right around the one-minute mark with a bare 500 and CORS errors in the browser console (CORS headers never got attached because the request died before Django could build a response) is this timeout being too short, not an actual CORS misconfiguration — see gunicorn's --timeout 100 in docker-compose.prod.yml for the timeout budget the rest of the chain is sized against.

api

cd apps/api
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/python manage.py migrate
.venv/bin/python manage.py runserver

web

pnpm install
pnpm --filter web dev
  • api: cd apps/api && python manage.py <cmd> · testpytest -q · lintruff check .
  • web: pnpm --filter web <script> · lintpnpm lint · typecheckpnpm typecheck

python3 scripts/make_promo_video.py renders a silent, screenshot-driven promo video (for sharing on LinkedIn, etc.) from the screenshots in docs/assets/img — crossfaded scenes with animated on-screen text and a soft procedural music bed (no narration, no bundled audio). Requires only ffmpeg. --dry-run prints the scene plan without rendering.

Compilation of untrusted user LaTeX is sandboxed per Plan.md §7 — no shell-escape, no network, ephemeral containers, resource limits. See docs/security.md (added in Phase 3).

── more in #developer-tools 4 stories · sorted by recency
── more on @freeleaf 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/freeleaf-an-free-and…] indexed:0 read:4min 2026-10-19 · —