Skip to main content
One replica uses the CPU of one node for every compile: on our test nodes with 2 vCPUs, six simultaneous compiles took three times as long as one, while the other nodes stayed idle. Several replicas spread users, and their compiles, over the nodes. This follows Overleaf’s Horizontal scaling design, translated to Kubernetes:
Plan S3 from the start. An instance that already keeps files on its volume needs the S3 migration first. This setup replaces the Deployment from step 5: delete it (kubectl delete -f 40-overleaf.yaml) before you apply the StatefulSet.
1

S3 storage

Set up the three buckets and two users as described in S3, with your S3 service or with Garage in the cluster, and put the four keys into the Secret overleaf-s3. The configuration below uses the Garage example from that page.
2

Configuration

This ConfigMap replaces 30-config.yaml. It adds S3 and git-bridge and points OVERLEAF_SITE_URL at the load balancer:
60-config.yaml
3

Replicas

70-overleaf.yaml
  • StatefulSet, not Deployment. Each replica gets a stable name (overleaf-0, overleaf-1, …) that the load balancer cookie and git-bridge refer to, and OrderedReady starts overleaf-0 alone first, so only one replica runs the database migrations.
  • One replica per node. Each replica starts its compiles through its node’s Docker and keeps them in its node’s /srv/overleaf-ha.
  • V1_HISTORY_URL per replica. Each replica hands out history links with its own name, so that git-bridge can reach them.
4

git-bridge

75-git-bridge.yaml
See Git integration for why GIT_BRIDGE_HOST is a fixed IP address.
5

Load balancer

The HAProxy configuration is Overleaf’s sample with five changes: /project/<id>/mcp routed to overleaf-0 like /git/ (see MCP Server), plain HTTP on port 8080 (the image runs as a non-root user, which cannot bind port 80 under containerd), servers found through cluster DNS (pod IPs change on every restart), a DaemonSet so that every node answers, and balance leastconn instead of the default roundrobin (see the Q&A below).
80-haproxy.yaml
externalTrafficPolicy: Local keeps the users’ IP addresses in X-Forwarded-For.
6

TLS

Terminate HTTPS in HAProxy as described in TLS proxy, then set OVERLEAF_SITE_URL to the https:// address and OVERLEAF_SECURE_COOKIE: "true" in 60-config.yaml.

What to expect

We tested three replicas on three nodes with 2 vCPUs and 4 GB each, with MongoDB, Redis, Garage, git-bridge and HAProxy on the same nodes:
  • 12 users compiling a 600-page document at the same time: all 12 compiled and fetched their PDF, 4 users per replica; a round took 8 to 18 seconds. Each node kept at least 1.3 GB of memory available.
  • Every user stays on one replica (cookie server-pro-ha), including the PDF download that must come from the replica that compiled it. The same holds over HTTPS, including the editor’s WebSocket.
  • When a replica stops, its users get errors for about 15 to 20 seconds, until HAProxy marks it down; then they move to another replica and stay logged in, because sessions live in Redis.

Q&A

Answer: With Overleaf’s sample (roundrobin, one HAProxy per node), 12 new users ended up 8, 3 and 1 on the three replicas in our test, and the busiest replica made the slowest users wait 29 seconds. With leastconn, which prefers the replica with the fewest open connections (each editor keeps a WebSocket open), the same test gave 4, 4 and 4 and 18 seconds. Stickiness is unchanged: it comes from the cookie.
Answer: Yes, if it routes by a cookie to a stable replica, supports WebSockets, allows request times of 10 minutes and uploads of 50 MB, keeps its keep-alive timeout below 65 seconds, sends X-Forwarded-For and X-Forwarded-Proto, and sends /git/ and /project/<id>/mcp to overleaf-0. Route to the replicas’ own names (overleaf-N.overleaf-hl), not to a Service that balances every request.
Answer: Add a node with Docker and the TeX Live images, set replicas: 4 in 70-overleaf.yaml, and add a server overleaf-3 ... line to the HAProxy configuration (and bump config-hash so HAProxy restarts).
Last modified on November 2, 2026