# Overleaf Self-hosted LaTeX editor — the Community Edition of overleaf.com. Real-time collaborative editing, compiles server-side, keeps a full TeX Live install so you don't maintain one locally. ## Quick start ```bash cp .env.example .env echo "OVERLEAF_INVITE_TOKEN_SECRET=$(openssl rand -base64 32)" >> .env $EDITOR .env # set OVERLEAF_SITE_URL docker compose up -d ``` First boot takes a minute or two. Then create the first admin account — there is no signup form until a user exists: ```bash docker compose exec sharelatex bash -c \ 'cd /overleaf/services/web && node modules/server-ce-scripts/scripts/create-user.mjs --admin --email=you@example.com' ``` It prints an activation URL. Open it, set a password, done. ## Ports | Port | Proto | Purpose | |------|-------|---------| | 8020 | http | Web UI (container listens on 80) | Mongo and Redis are reachable only on the internal compose network. ## Why it looks like this **Four containers, not one.** The app, MongoDB, Redis, and a one-shot replica-set initializer. Overleaf is a small distributed system pretending to be an app. **Mongo runs as a replica set** (`--replSet overleaf`) even though there's one node. Overleaf uses transactions and change streams, which Mongo only provides in replica-set mode. Something has to call `rs.initiate()` exactly once, which is what [`init/mongodb-init-replica-set.js`](init/mongodb-init-replica-set.js) does — Mongo runs anything in `/docker-entrypoint-initdb.d/` on first start against an empty data directory. Skip this and Overleaf hangs at boot with errors that don't mention replica sets. `extra_hosts: mongo:127.0.0.1` is part of the same trick: the init script registers the member as `mongo:27017`, so during initialization the container has to resolve its own service name. **No `SANDBOXED_COMPILES`.** Upstream's sample `docker-compose.yml` ships these *enabled*: ```yaml SANDBOXED_COMPILES: "true" DOCKER_RUNNER: "true" SANDBOXED_COMPILES_SIBLING_CONTAINERS: "true" ``` Directly above them, its own comment says sandboxed compiles are Server Pro only and "must be commented out to avoid compile issues" on Community Edition. Copy that file as-is and your documents don't compile. They're absent here on purpose. **The image is pinned to `6.2.2`.** Upstream's sample is unpinned. This is a database-backed service with schema migrations between majors — an unattended `docker compose pull` picking up a new major is not something you want to discover on a Sunday. **`container_name: overleaf-mongo` / `overleaf-redis`**, while the *service* names stay `mongo` and `redis`. The service names are load-bearing — `OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex` resolves them — but upstream's `container_name: mongo` would claim a very generic name on the host and collide with the next stack that wants it. **`stop_grace_period: 60s`** so in-flight compiles finish instead of being killed mid-run. ## Behind your own domain Set the public URL — this is the setting people miss: ```bash # .env OVERLEAF_SITE_URL=https://overleaf.example.com ``` Then uncomment `OVERLEAF_SECURE_COOKIE` in `compose.yaml`. Caddy, which handles WebSockets automatically (Overleaf needs them for collaborative editing and the compile log): ```caddy overleaf.example.com { reverse_proxy 192.168.1.50:8020 } ``` That's the whole config — Caddy gets the certificate itself. See [reverse proxy](../docs/reverse-proxy.md). A [Cloudflare Tunnel](../cloudflare-tunnel/) also works and passes WebSockets, but mind the ~100 MB upload cap if you bring in large figures or datasets. **Order matters:** change `OVERLEAF_SITE_URL` *before* creating accounts, or the activation links you've already sent point at the wrong host. ## Running it on OpenMediaVault Two things differ from a plain `docker compose up`, and both cost me an evening. **Copy `init/mongodb-init-replica-set.js` across before the first start.** OMV's compose plugin deploys the compose file and the env file, not the rest of the directory. If that script isn't on disk when Mongo first starts, Docker creates it as an empty *directory* — bind mounts do that for missing paths — Mongo skips it, and the replica set is never initialized. The symptom points nowhere near the cause: Mongo reports **healthy** while the app restart-loops with `500_check_db_access.sh failed with status 1`. Mongo's healthcheck only runs `db.stats().ok`, which succeeds fine without a replica set. If you've already started it, fix it in place — the entrypoint won't re-run once the data directory has data: ```bash docker exec overleaf-mongo mongosh --quiet --eval \ 'rs.initiate({_id:"overleaf",members:[{_id:0,host:"mongo:27017"}]})' docker restart overleaf ``` **Use `docker exec`, not `docker compose`, for one-off commands.** OMV names the env file `.env`, and Compose only auto-loads a file called `.env`. So a manual compose command fails on the required variables even though the running stack is perfectly configured: ``` required variable OVERLEAF_INVITE_TOKEN_SECRET is missing a value ``` The container already has the environment, so talk to it directly: ```bash docker exec overleaf bash -c \ 'cd /overleaf/services/web && node modules/server-ce-scripts/scripts/create-user.mjs --admin --email=you@example.com' ``` If you'd rather have compose work by hand too, symlink it once — OMV keeps passing its own `--env-file` and won't notice: ```bash ln -s overleaf.env .env ``` ## Gotchas - **`OVERLEAF_SECURE_COOKIE` is tested with `!= null`.** Setting it to `"false"` turns secure cookies **on**. The only way to disable it is to leave it unset — and with it wrongly on over plain HTTP, login silently fails to stick. - **Mongo 8.0 needs AVX.** Mongo 5.0+ won't start on CPUs without it, and the failure is an immediate exit with nothing useful logged. Check first: `grep -o avx2 /proc/cpuinfo | head -1` - **`start_period` on Mongo's healthcheck is load-bearing.** Without it, slow checks during startup count against `retries` straight away — five of them (~50s) mark Mongo unhealthy, and the app's `condition: service_healthy` refuses to start it. Mongo then recovers on its next check, so by morning everything looks fine except the app, which is simply down. This bites on backup scripts that stop and start containers: Mongo comes back while the disks are still busy, `mongosh` is a Node process and answers slowly, and the stack loses a race nobody knew it was running. Upstream's sample has no `start_period` either. - **A healthy Mongo does not mean the replica set is up.** The healthcheck runs `db.stats().ok`, which passes without one — so `depends_on: service_healthy` is satisfied and the app starts anyway, then fails its own DB check. - **The replica-set script only runs on an empty data directory.** If Mongo has data but no replica set, initialize by hand: `docker compose exec mongo mongosh --eval 'rs.initiate({_id:"overleaf",members:[{_id:0,host:"mongo:27017"}]})'` - **No signup page until the first user exists.** Use the `create-user.mjs` script above; the web UI won't offer to make one. - **Missing LaTeX packages** — see the section below. The short version is `tlmgr --usermode install `, not plain `tlmgr install`. - **Back up `DATA_PATH`.** `data/mongo` holds every project. `data/overleaf` holds uploaded files and compile output. - **Upgrading across majors** may need migrations, and Mongo itself can't jump several versions at once. Read the release notes rather than pulling blind. ## Installing LaTeX packages so they stay installed The image ships a reduced TeX Live, so a document can fail on a package that exists upstream — `booktabs` is a common first casualty. The obvious fix works and then quietly undoes itself: ```bash docker exec overleaf tlmgr install booktabs # works now, gone after an upgrade ``` TeX Live lives at `/usr/local/texlive/2026/`, **inside the image**. A `docker restart` keeps those files; recreating the container after `docker compose pull` does not. A document that compiled last month stops compiling and nothing tells you why. The fix is user-mode installs plus a `TEXMFHOME` that both users agree on. That second half matters more than it looks: **tlmgr runs as root, but the compiles run as `www-data`**, and by default those two resolve `TEXMFHOME` differently: ``` root /root/texmf <- where a plain `tlmgr --usermode install` lands www-data /var/www/texmf <- where LaTeX actually looks ``` So the install succeeds, reports no error, and the package still isn't found — and `/root` isn't even readable by `www-data`, so no amount of mounting that path helps. This stack therefore sets: ```yaml TEXMFHOME: /var/lib/overleaf/texmf ``` which is inside the persisted volume, so both users agree and the packages survive image updates. Then: ```bash docker exec overleaf tlmgr --usermode init-usertree # once docker exec overleaf tlmgr --usermode install booktabs ``` Verify as the user that actually compiles, not as root: ```bash docker exec -u www-data overleaf kpsewhich booktabs.sty # /var/lib/overleaf/texmf/... correct, persisted # /usr/local/texlive/... inside the image, lost on update # nothing installed somewhere the compiler can't see ``` **Caveats.** User mode handles ordinary style packages cleanly. Packages that install fonts or need map-file updates may also want `updmap-user`, and a few that expect to modify the main tree won't work this way — install those with a custom image instead: ```dockerfile FROM sharelatex/sharelatex:6.2.2 RUN tlmgr install ``` That's the more reproducible option in general, since the package list ends up in version control. The trade is rebuilding on every upstream release. If you stay with user mode, keep a list of what you've installed — the volume survives, but a list is what lets you rebuild from nothing. ## Health checks The image ships its own: ```bash docker compose exec sharelatex bash -c 'cd /overleaf/services/web && node modules/server-ce-scripts/scripts/check-mongodb.mjs' docker compose exec sharelatex bash -c 'cd /overleaf/services/web && node modules/server-ce-scripts/scripts/check-redis.mjs' docker compose exec mongo mongosh --quiet --eval 'rs.status().members.map(m => m.name + " " + m.stateStr)' ``` ## Links - Upstream repo: - Their sample compose: - The Toolkit (upstream's recommended installer): - Image: