Table of Contents

About

This page contains some notes on running Overleaf using the new "toolkit" deployment developed by Overleaf that automatically pulls and builds all dependencies in order to avoid requiring people to scrounge together the correct dependent versions and set up configurations that are not-so-relevant.

The page mentions ARM64 because using Overleaf toolkit is also the safe way of creating containers that will be able to run on ARM64 as well.

Building Overleaf

cd OVERLEAF_HOME
git clone https://github.com/overleaf/toolkit.git
cd toolkit
bin/init

where:

After the commands run, the configuration file config/overleaf.rc should be edited to set:

The next step is to actually build the relevant containers:

cd OVERLEAF_HOME
git clone https://github.com/overleaf/overleaf.git
cd overleaf/server-ce
 
make ARCH=arm build-base
make ARCH=arm build-community

This process will take a long while and it will build the basic container that containers all the instrumentation necessary to run Overleaf and then it will build the community edition of Overleaf itself.

Finally the build overleaf image must have its name changed according to the version found in OVERLEAF_HOME/toolkit/config/version by running the command:

docker image tag sharelatex/sharelatex:main sharelatex/sharelatex:OVERLEAF_VERSION

where:

Running Overleaf

The documentation mentions running Overleaf by running:

bin/up -d

which will start Overleaf and all dependent containers.

However, it is clear that this will have to be changed in an OS-compliant manner, for instance, by using SystemD. Before that, the command bin/up -h can be ran in order to receive the list of parameters that can be passed to the bin/up script. Out of all parameters, the following parameters will be used to run Overleaf:

./bin/up --remove-orphans --no-recreate --attach-dependencies 

where:

Wrapping that up for SystemD, something like the following should work:

[Unit]
Description=Overleaf
After=docker.service traefik.service
StartLimitIntervalSec=0

[Service]
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -c '/usr/bin/docker network create entertainment || true'
ExecStart=/opt/overleaf/toolkit/bin/up --remove-orphans --no-recreate --attach-dependencies
WorkingDirectory=/opt/overleaf/toolkit
TimeoutSec=300
Environment=DOCKER_CONFIG=/etc/docker

[Install]
WantedBy=multi-user.target

Note that the SystemD service file creates the entertainment network and it is left there to match the Servarr complete stack that we devised because running Overleaf along the rest is compact and fun.

Traefik and Automatic Hostname Registration with Overleaf Toolkit

Note that the Overleaf toolkit does not really allow the level of customization that docker compose allows, especially if the toolkit is to be updated via git, but the Overleaf toolkit can be modified in order to make Overleaf bind to the network that traefik listens on.

This setup is entirely compatible with both the LAN auto-discovery feature as well as the all-in-one traefik template that bundles reverse-DNS, automatic dynamic DNS as well as authelia for global authentication and authorization with pluggable modules such that there are really no other changes required to Overleaf other than extending its network to the network that the other projects operate on (in our examples, the network is called entertainment).

Committing to a Registry

Unfortunately doing things in a non-compliant manner leads to poor interaction with other components. In this case, after the overleaf images are pulled and built they are stored temporarily within the docker cache such that cleaning up the cache using the standard tools and issuing commands like:

docker system prune -a -f --volumes

will lead to the containers being purged if they are not already started.

Clearly, the whole image set should be committed to a local storage and then pulled whenever the service restarts. In order to accomplish this and assuming that registry is running on the local network, the following steps would have to be followed in order to be able to run the images independently of the Overleaf toolkit.

First, the images that are running will have to be inspected, using:

docker ps 

and there should be a container running for mongo, redis and then sharelatex. The containers have to be relabeled with the local registry as prefix, for example, the following commands will retag all the images apropriately for a registry residing at localhost:5000:

docker tag mongo:6.0 localhost:5000/mongo:6.0
docker tag redis:6.2 localhost:5000/redis:6.2
docker tag sharelatex/sharelatex:5.5.4 localhost:5000/overleaf:5.5.4

The images can now be pushed to the registry for storage:

docker push localhost:5000/mongo:6.0
docker push localhost:5000/redis:6.2
docker push localhost:5000/overleaf:5.5.4

Now, it's a matter of determining what command-line parameters these containers are started with. We have used runlike which is a small program that can generate docker CLI commands from running containers. For instance, by running:

docker run --rm -v /var/run/docker.sock:/var/run/docker.sock localhost:5000/runlike:latest 8b853f065493

where 8b853f065493 is a container name for either mongo:6.0, redis:6.2 or sharelatex/sharelatex:5.5.4.

The command will then generate a set of command-line parameters that were used to start the container and using these parameters the containers can be started manually in a reliable manner.

The following sub-sections contain service files following the complete Servarr stack pattern for all the services required to run Overleaf. The service files assume that the toolkit is created under /opt/overleaf and the paths might be adjusted in case overleaf is downloaded to a different directory.

With that said, the idea here would be that the images are just built using the toolkit but then they are ran manually and independently from the Overleaf toolkit. In principle this should not be a problem because major LaTeX updates are very slow such that the whole procedure might perhaps have to be repeated every few years.

Mongo

[Unit]
Description=Mongo
After=docker.service traefik.service
StartLimitIntervalSec=0

[Service]
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -c '/usr/bin/docker network create entertainment || true'
ExecStartPre=/usr/bin/docker pull localhost:5000/mongo:6.0
ExecStart=/usr/bin/docker run --name=mongo \
  --rm \
  --hostname mongo \
  --net=entertainment \
  --interactive \
  --hostname=mongo \
  --volume /opt/overleaf/toolkit/data/mongo:/data/db \
  --volume /data/configdb \
  --expose=27017 \
  --label='com.docker.compose.project.config_files=/opt/overleaf/toolkit/lib/docker-compose.base.yml,/opt/overleaf/toolkit/lib/docker-compose.vars.yml,/opt/overleaf/toolkit/lib/docker-compose.redis.yml,/opt/overleaf/toolkit/lib/docker-compose.mongo.yml' \
  --label='com.docker.compose.project.working_dir=/opt/overleaf/toolkit/lib' \
  --label='com.docker.compose.version=2.39.2' \
  --label='com.docker.compose.depends_on=' \
  --label='com.docker.compose.oneoff=False' \
  --label='com.docker.compose.image=sha256:ee685f8b54c3b77548c5daf526d5deab8b8635797479450af05fac5e0e706f95' \
  --label='com.docker.compose.project=overleaf' \
  --label='com.docker.compose.config-hash=b81516a85e9d91dfb0407f9931ac887beefa95349d9adfef26bf22d61be6a6ad' \
  --label='com.docker.compose.service=mongo' \
  --label='com.docker.compose.container-number=1' \
  --runtime=runc \
  localhost:5000/mongo:6.0 \
  --replSet overleaf
ExecStop=/usr/bin/docker stop mongo
ExecStop=/usr/bin/docker rm -f mongo
TimeoutSec=300
Environment=DOCKER_CONFIG=/etc/docker

[Install]
WantedBy=multi-user.target

Redis

[Unit]
Description=Redis
After=docker.service traefik.service
StartLimitIntervalSec=0

[Service]
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -c '/usr/bin/docker network create entertainment || true'
ExecStartPre=/usr/bin/docker pull localhost:5000/redis:6.2
ExecStart=/usr/bin/docker run --name=redis \
  --rm \
  --hostname redis \
  --net=entertainment \
  --interactive \
  --volume /opt/overleaf/toolkit/data/redis:/data \
  --workdir=/data \
  --expose=6379 \
  --label='com.docker.compose.project.working_dir=/opt/overleaf/toolkit/lib' \
  --label='com.docker.compose.project.config_files=/opt/overleaf/toolkit/lib/docker-compose.base.yml,/opt/overleaf/toolkit/lib/docker-compose.vars.yml,/opt/overleaf/toolkit/lib/docker-compose.redis.yml,/opt/overleaf/toolkit/lib/docker-compose.mongo.yml' \
  --label='com.docker.compose.oneoff=False' \
  --label='com.docker.compose.project=overleaf' \
  --label='com.docker.compose.version=2.39.2' \
  --label='com.docker.compose.depends_on=' \
  --label='com.docker.compose.container-number=1' \
  --label='com.docker.compose.service=redis' \
  --label='com.docker.compose.config-hash=6e8b551fed1efba1e3b370cd5f5c70d2af49e8d3ac9487207eb5ecd8647b41ae' \
  --label='com.docker.compose.image=sha256:e4b478e3b090405716db317bf6562beccac5aff2ecfc24f74696460861b01997' \
  --runtime=runc \
  localhost:5000/redis:6.2 \
  redis-server --appendonly yes
ExecStop=/usr/bin/docker stop redis
ExecStop=/usr/bin/docker rm -f redis
TimeoutSec=300
Environment=DOCKER_CONFIG=/etc/docker

[Install]
WantedBy=multi-user.target

Overleaf

/usr/bin/docker run --name=overleaf \
  --rm \
  --hostname overleaf \
  --net=entertainment \
  --interactive \
  --volume /opt/overleaf/toolkit/data/overleaf:/var/lib/overleaf \
  --env=GIT_BRIDGE_ENABLED=false \
  --env=ENABLED_LINKED_FILE_TYPES=project_file,project_output_file \
  --env=ENABLE_CONVERSIONS=true --env=EMAIL_CONFIRMATION_DISABLED=true \
  --env=REDIS_HOST=redis \
  --env=GIT_BRIDGE_PORT=8000 \
  --env=OVERLEAF_MONGO_URL=mongodb://mongo/sharelatex \
  --env=GIT_BRIDGE_HOST=git-bridge \
  --env=EXTERNAL_AUTH=none \
  --env=V1_HISTORY_URL=http://sharelatex:3100/api \
  --env=OVERLEAF_REDIS_HOST=redis \
  --env='OVERLEAF_APP_NAME=Our Overleaf Instance' \
  --env=REDIS_PORT=6379 \
  --workdir=/overleaf \
  -p 7643:80 \
  --label='com.docker.compose.depends_on=redis:service_started:false,mongo:service_healthy:false' \
  --label='com.docker.compose.container-number=1' \
  --label='com.docker.compose.project=overleaf' \
  --label='com.docker.compose.service=sharelatex' \
  --label='com.docker.compose.project.working_dir=/opt/overleaf/toolkit/lib' \
  --label='com.docker.compose.project.config_files=/opt/overleaf/toolkit/lib/docker-compose.base.yml,/opt/overleaf/toolkit/lib/docker-compose.vars.yml,/opt/overleaf/toolkit/lib/docker-compose.redis.yml,/opt/overleaf/toolkit/lib/docker-compose.mongo.yml' \
  --label='com.docker.compose.replace=sharelatex' \
  --label='com.docker.compose.image=sha256:ae6179834dda19ec699d4016645c74c776d2d6737936a7943f1ef704c8c8e6ec' \
  --label='com.docker.compose.version=2.39.2' \
  --label='com.docker.compose.oneoff=False' \
  --label='com.docker.compose.config-hash=24c4171ed6f272c165d1da16610dd4f6cc2d9cdfb03d7df33261d1379bec7d4c' \
  --label='traefik.http.routers.overleaf.rule=Host(`overleaf.$HOSTNAME`)' \
  --runtime=runc \
  localhost:5000/overleaf:5.5.4
ExecStop=/usr/bin/docker stop overleaf
ExecStop=/usr/bin/docker rm -f overleaf
TimeoutSec=300
Environment=DOCKER_CONFIG=/etc/docker

[Install]
WantedBy=multi-user.target

Note that this service file contains one extraneous rule that should not be necessary and that was not derived from the container by runlike, namely:

--label='traefik.http.routers.overleaf.rule=Host(`overleaf.$HOSTNAME`)'

which is meant to indicate to traefik that the host should be overleaf, otherwise it seems traefik generates a hostname of sharelatex-overleaf.

Installing a Full TexLive Distribution

Overleaf is distributed as a separate technology than LaTeX such that after installing, the Overleaf instance is unable to compile complex documents. However, Overleaf contains the necessary tools to install a full TeX Live LaTeX distribution.

Ideally, the above procedures should have been followed including pushing the images to a persistent registry such that, at the very least, overleaf can now start on its own without needing the ./bin/up script from the toolkit. If that is the case, then the following steps can be followed in order to install a TeXLive distribution that will be persistent across Overleaf restarts.

First, start all dependencies and Overleaf as usual:

systemctl start mongo
systemctl start redis
systemctl start overleaf

or whatever the service / image names are depending on how the images are loaded from the local registry.

The next step is to check the container ID of the Overleaf image:

docker container ls | grep overleaf

which should output something along the following lines:

801981ffe734   localhost:5000/overleaf:5.5.4                                                "/sbin/my_init"          22 seconds ago      Up 20 seconds                0.0.0.0:7643->80/tcp  

where 801981ffe734 is the ID of the Overleaf container.

Now, when LaTeX is installed within the container, the distribution is placed at /usr/local/textlive within the container. Even now, without installing the distribution yet, the path inside the container /usr/local/texlive should contain a skeleton distribution that can only compile basic documents. The idea is to bind-mount /usr/local/texlive on the outside of the container using Docker volumes.

However, the existing skeleton must be copied out of the container. Using Docker commands the skeleton is copied to /opt/overleaf/toolkit/data/texlive (right within the Overleaf toolkit folder, to have everything contained):

docker cp 801981ffe734:/usr/local/texlive /opt/overleaf/toolkit/data/texlive

With the skeleton copied over, the Docker parameters to run the Overleaf container must be amended in order to bind-mount the path inside the container /usr/local/texlive to the path to the files outside the container at /opt/overleaf/toolkit/data/texlive. In other words, another volume-mount parameter must be added:

--volume /opt/overleaf/toolkit/data/texlive:/usr/local/texlive

and the Overleaf container restarted:

With the Overleaf container restarted, a shell should be obtained inside the container, with:

docker exec -it CONTAINER_ID bash

where:

and the following command must be run in order to install the full TexLive distribution:

tlmgr install scheme-full

Now, tlmgr will install the full distribution within the container at /usr/local/textlive but that folder is mapped on the outside of the container to some persistent storage at /opt/overleaf/toolkit/data/texlive such that after tlmgr finishes, the container can be restarted and the full LaTeX distribution should still be installed.

Here is what the full Overleaf service file will look like in the end:

[Unit]
Description=Overleaf
After=docker.service traefik.service
StartLimitIntervalSec=0

[Service]
Restart=always
RestartSec=5s
ExecStartPre=/bin/sh -c '/usr/bin/docker network create entertainment || true'
ExecStartPre=/usr/bin/docker pull localhost:5000/overleaf:5.5.4
ExecStart=/usr/bin/docker run --name=overleaf \
  --rm \
  --hostname overleaf \
  --net=entertainment \
  --interactive \
  --volume /opt/overleaf/toolkit/data/overleaf:/var/lib/overleaf \
  --volume /opt/overleaf/toolkit/data/texlive:/usr/local/texlive \
  --env=GIT_BRIDGE_ENABLED=false \
  --env=ENABLED_LINKED_FILE_TYPES=project_file,project_output_file \
  --env=ENABLE_CONVERSIONS=true --env=EMAIL_CONFIRMATION_DISABLED=true \
  --env=REDIS_HOST=redis \
  --env=GIT_BRIDGE_PORT=8000 \
  --env=OVERLEAF_MONGO_URL=mongodb://mongo/sharelatex \
  --env=GIT_BRIDGE_HOST=git-bridge \
  --env=EXTERNAL_AUTH=none \
  --env=V1_HISTORY_URL=http://sharelatex:3100/api \
  --env=OVERLEAF_REDIS_HOST=redis \
  --env='OVERLEAF_APP_NAME=Our Overleaf Instance' \
  --env=REDIS_PORT=6379 \
  --workdir=/overleaf \
  -p 7643:80 \
  --label='com.docker.compose.depends_on=redis:service_started:false,mongo:service_healthy:false' \
  --label='com.docker.compose.container-number=1' \
  --label='com.docker.compose.project=overleaf' \
  --label='com.docker.compose.service=sharelatex' \
  --label='com.docker.compose.project.working_dir=/opt/overleaf/toolkit/lib' \
  --label='com.docker.compose.project.config_files=/opt/overleaf/toolkit/lib/docker-compose.base.yml,/opt/overleaf/toolkit/lib/docker-compose.vars.yml,/opt/overleaf/toolkit/lib/docker-compose.redis.yml,/opt/overleaf/toolkit/lib/docker-compose.mongo.yml' \
  --label='com.docker.compose.replace=sharelatex' \
  --label='com.docker.compose.image=sha256:ae6179834dda19ec699d4016645c74c776d2d6737936a7943f1ef704c8c8e6ec' \
  --label='com.docker.compose.version=2.39.2' \
  --label='com.docker.compose.oneoff=False' \
  --label='com.docker.compose.config-hash=24c4171ed6f272c165d1da16610dd4f6cc2d9cdfb03d7df33261d1379bec7d4c' \
  --runtime=runc \
  localhost:5000/overleaf:5.5.4
ExecStop=/usr/bin/docker stop overleaf
ExecStop=/usr/bin/docker rm -f overleaf
TimeoutSec=300
Environment=DOCKER_CONFIG=/etc/docker

[Install]
WantedBy=multi-user.target