Cleaned, updated and tested Overleaf (sharelatex)
This commit is contained in:
@@ -10,6 +10,12 @@
|
||||
!*/.env.example
|
||||
!*/README.md
|
||||
|
||||
# Support files a stack needs at runtime: init scripts, config fragments.
|
||||
# Keep them in a per-service `init/` directory so they are never mistaken for
|
||||
# application state.
|
||||
!*/init/
|
||||
!*/init/**
|
||||
|
||||
# Docs are not service data.
|
||||
!docs/**
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ handled separately — see [Exposing services](#exposing-services).
|
||||
| [cloudflare-tunnel](cloudflare-tunnel/) | — | Outbound connector. Exposes other services without opening a port |
|
||||
| [jellyfin](jellyfin/) | 8096 | Media streaming. Also 7359/udp for auto-discovery |
|
||||
| [openwebui](openwebui/) | 3033 | LLM chat UI. One `OWUI_TAG` switches between CPU, CUDA and bundled-Ollama |
|
||||
| [overleaf](overleaf/) | 8020 | LaTeX editor. Needs Mongo + Redis |
|
||||
| [overleaf](overleaf/) | 8020 | LaTeX editor. Four containers: app, Mongo (replica set), Redis, init |
|
||||
|
||||
<!-- One row per service. Add as you go — the table is the index people scan. -->
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Copy to .env and adjust. .env is git-ignored; this file is not.
|
||||
|
||||
# ─── Required ───────────────────────────────────────────────────────
|
||||
# Signs project invite tokens. Generate your own -- do not reuse anyone's:
|
||||
#
|
||||
# openssl rand -base64 32
|
||||
#
|
||||
OVERLEAF_INVITE_TOKEN_SECRET=
|
||||
|
||||
# ─── Public address ─────────────────────────────────────────────────
|
||||
# Every generated link is built from this: account activation, password
|
||||
# reset, project invites. Overleaf defaults to http://localhost, so links
|
||||
# come out wrong the moment you use a port or a domain -- and you only find
|
||||
# out when someone can't reset their password.
|
||||
#
|
||||
# local, non-standard port http://192.168.1.50:8020
|
||||
# behind a reverse proxy https://overleaf.example.com
|
||||
#
|
||||
OVERLEAF_SITE_URL=http://localhost:8020
|
||||
|
||||
# ─── Basics ─────────────────────────────────────────────────────────
|
||||
#OVERLEAF_APP_NAME=Overleaf
|
||||
#PORT=8020
|
||||
|
||||
# Where Mongo, Redis and Overleaf keep their data. Projects live here --
|
||||
# this is the directory to back up.
|
||||
#DATA_PATH=./data
|
||||
|
||||
# ─── HTTPS ──────────────────────────────────────────────────────────
|
||||
# Serving over TLS? Uncomment OVERLEAF_SECURE_COOKIE in compose.yaml.
|
||||
# Careful: the code checks `!= null`, so setting it to "false" turns it ON.
|
||||
# The only way to disable it is to leave it unset.
|
||||
@@ -0,0 +1,190 @@
|
||||
# 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 `<stack>.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`
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
## 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: <https://github.com/overleaf/overleaf>
|
||||
- Their sample compose: <https://github.com/overleaf/overleaf/blob/main/docker-compose.yml>
|
||||
- The Toolkit (upstream's recommended installer): <https://github.com/overleaf/toolkit>
|
||||
- Image: <https://hub.docker.com/r/sharelatex/sharelatex>
|
||||
@@ -0,0 +1,78 @@
|
||||
name: overleaf
|
||||
|
||||
services:
|
||||
sharelatex:
|
||||
image: sharelatex/sharelatex:6.2.2
|
||||
container_name: overleaf
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
# Overleaf needs time to finish in-flight compiles on shutdown.
|
||||
stop_grace_period: 60s
|
||||
|
||||
ports:
|
||||
- "${PORT:-8020}:80"
|
||||
|
||||
volumes:
|
||||
- ${DATA_PATH:-./data}/overleaf:/var/lib/overleaf
|
||||
|
||||
environment:
|
||||
OVERLEAF_APP_NAME: ${OVERLEAF_APP_NAME:-Overleaf}
|
||||
|
||||
# These hostnames are compose SERVICE names, not container names.
|
||||
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex
|
||||
# One setting, two variable names. Upstream's own comment: "unfortunately".
|
||||
OVERLEAF_REDIS_HOST: redis
|
||||
REDIS_HOST: redis
|
||||
|
||||
# Required for invite tokens. openssl rand -base64 32
|
||||
OVERLEAF_INVITE_TOKEN_SECRET: ${OVERLEAF_INVITE_TOKEN_SECRET:?generate with openssl rand -base64 32}
|
||||
|
||||
# Public address. Every generated link -- activation, password reset,
|
||||
# project invite -- is built from this. Defaults to http://localhost,
|
||||
# which is wrong the moment you use a port or a domain.
|
||||
OVERLEAF_SITE_URL: ${OVERLEAF_SITE_URL:-http://localhost:8020}
|
||||
|
||||
ENABLED_LINKED_FILE_TYPES: "project_file,project_output_file"
|
||||
ENABLE_CONVERSIONS: "true"
|
||||
EMAIL_CONFIRMATION_DISABLED: "true"
|
||||
|
||||
# Serving over HTTPS? Uncomment. The value is NOT parsed -- the code
|
||||
# tests `!= null`, so even "false" switches it ON. Comment it out to
|
||||
# disable, never set it to false.
|
||||
#OVERLEAF_SECURE_COOKIE: "true"
|
||||
|
||||
# DO NOT add SANDBOXED_COMPILES / DOCKER_RUNNER here. Upstream's sample
|
||||
# ships them enabled, but they are Server Pro only and break compiles on
|
||||
# Community Edition. See the README.
|
||||
|
||||
mongo:
|
||||
image: mongo:8.0
|
||||
container_name: overleaf-mongo
|
||||
restart: unless-stopped
|
||||
# Overleaf uses transactions, which require replica-set mode even on one node.
|
||||
command: "--replSet overleaf"
|
||||
volumes:
|
||||
- ${DATA_PATH:-./data}/mongo:/data/db
|
||||
- ./init/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js:ro
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: sharelatex
|
||||
# rs.initiate() registers the member as "mongo:27017", so during init the
|
||||
# container has to resolve that name to itself.
|
||||
extra_hosts:
|
||||
- mongo:127.0.0.1
|
||||
healthcheck:
|
||||
test: echo 'db.stats().ok' | mongosh localhost:27017/test --quiet
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:6.2
|
||||
container_name: overleaf-redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${DATA_PATH:-./data}/redis:/data
|
||||
@@ -1,60 +0,0 @@
|
||||
volumes:
|
||||
web:
|
||||
mongo:
|
||||
redis:
|
||||
mongo-init-replica:
|
||||
|
||||
services:
|
||||
web:
|
||||
image: sharelatex/sharelatex:4.2.3@sha256:6c6498de22775f1227eed7cf472c764aa851f0fe5b9e640d989ea8a90837159e
|
||||
depends_on:
|
||||
mongo:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
links:
|
||||
- mongo
|
||||
- redis
|
||||
ports:
|
||||
- 8020:80
|
||||
volumes:
|
||||
- ./data/sharelatex:/var/lib/sharelatex
|
||||
environment:
|
||||
SHARELATEX_APP_NAME: Overleaf
|
||||
SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex
|
||||
SHARELATEX_REDIS_HOST: redis
|
||||
REDIS_HOST: redis
|
||||
ENABLED_LINKED_FILE_TYPES: "project_file,project_output_file"
|
||||
ENABLE_CONVERSIONS: "true"
|
||||
EMAIL_CONFIRMATION_DISABLED: "true"
|
||||
TEXMFVAR: /var/lib/sharelatex/tmp/texmf-var
|
||||
|
||||
mongo:
|
||||
image: mongo:4.4@sha256:52c42cbab240b3c5b1748582cc13ef46d521ddacae002bbbda645cebed270ec0
|
||||
command: "--replSet overleaf"
|
||||
expose:
|
||||
- 27017
|
||||
volumes:
|
||||
- ./data/mongo:/data/db
|
||||
healthcheck:
|
||||
test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:6.2@sha256:5610a8ffeaf386ecc7b6c8f070662a7a9d01bc7eafe237152a7d16e05b84afc9
|
||||
expose:
|
||||
- 6379
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
|
||||
mongo-init-replica:
|
||||
image: mongo:4.4
|
||||
depends_on:
|
||||
- mongo
|
||||
entrypoint: >
|
||||
bash -c "
|
||||
until mongo --host mongo --eval 'print(\"waiting for mongo\")'; do sleep 2; done;
|
||||
mongo --host mongo --eval 'rs.initiate({_id: \"overleaf\", members: [{_id: 0, host: \"mongo:27017\"}]})'"
|
||||
restart: "on-failure"
|
||||
@@ -0,0 +1,8 @@
|
||||
// Mounted into Mongo's /docker-entrypoint-initdb.d/ and run ONCE, on the very
|
||||
// first start against an empty data directory. Overleaf needs transactions,
|
||||
// which Mongo only offers in replica-set mode -- even for a single node.
|
||||
//
|
||||
// If you ever wipe ./data/mongo, this runs again. If Mongo has data but no
|
||||
// replica set, it does NOT run and you must call rs.initiate() by hand.
|
||||
|
||||
rs.initiate({ _id: 'overleaf', members: [{ _id: 0, host: 'mongo:27017' }] })
|
||||
Reference in New Issue
Block a user