Aldine / Self-hosting guide
Self-hosting Aldine with Docker Compose
Aldine is an open-source, self-hosted collaborative LaTeX editor. It runs as two containers: the app and a sandboxed TeX Live compiler. This guide covers the install and what comes after it: upgrades, TeX packages, logins, TLS, backups and limits.
Applies to Aldine 0.11.0. Updated on 12 October 2026. The reference is the README and the single-VPS runbook.
What it needs to run
- Docker Engine with the compose plugin.
- Disk for the compiler image: the first pull is about 1.1 GB compressed and 4.3 GB on disk, because TeX Live lives in that image.
- For a shared production instance, the runbook recommends a VPS with 2 or more dedicated vCPUs, at least 8 GB of RAM and at least 40 GB of disk. LaTeX compiles are CPU and RAM bursts of about 2 GB each, so size for the compiles you expect to run at once.
- No database and no external services. Projects are git repositories on disk and metadata is stored in flat files by default.
Install
No clone, no build: save this as docker-compose.yml and run docker compose up -d.
name: aldine
services:
app:
image: ghcr.io/trahloff/aldine-app:${ALDINE_VERSION:-0.11.0}
ports:
- "8080:3000"
volumes:
- aldine-data:/data
- aldine-secrets:/secrets
networks: [frontend, backend]
init: true
restart: unless-stopped
compiler:
image: ghcr.io/trahloff/aldine-compiler:${ALDINE_VERSION:-0.11.0}${ALDINE_TEXLIVE:-}
volumes:
- aldine-data:/data
# The compiler runs untrusted LaTeX. Keep this block.
networks: [backend]
mem_limit: 2g
pids_limit: 256
cap_drop: [ALL]
security_opt: [no-new-privileges]
init: true
restart: unless-stopped
networks:
frontend: {}
backend:
internal: true # no route to the internet
volumes:
aldine-data:
aldine-secrets:
Open http://localhost:8080. Projects live in the aldine-data volume. Keep the name: aldine line wherever you save the file: it fixes the volume names, which is what lets you switch compose files later and what deploy/backup.sh looks for.
The file is equivalent to the repository's docker-compose.yml (same services, images and sandbox settings), so a clone works the same way:
git clone https://github.com/trahloff/Aldine && cd Aldine && docker compose up -d
If port 8080 is taken, change the left side of ports:.
Check that it is up
After the first pull, starts take seconds. The instance is ready when
curl localhost:8080/api/health
returns {"ok":true,"name":"aldine"}.
Version pinning, upgrade and rollback
The compose file says ${ALDINE_VERSION:-0.11.0}, so you are pinned to a release and nothing changes under a running install on its own. To upgrade, read the changelog, back up, then:
ALDINE_VERSION=<new version> docker compose pull && docker compose up -d
To roll back, set ALDINE_VERSION to the previous release and run the same command. Your projects live in the aldine-data and aldine-secrets volumes, and an image swap does not touch them.
Versions follow SemVer with one caveat: before 1.0 a minor bump (0.3 to 0.4) may change behaviour or the on-disk layout, and a patch bump will not. Upgrades across a minor version are not yet tested against existing data, so back up first. Only the latest release gets security fixes.
TeX packages and the full image
The default compiler image carries TeX Live's medium scheme plus the pictures, latexextra and bibtexextra collections (pgfplots, tikz-cd, cleveref, todonotes, minted, biblatex and more), the publisher classes, and the Arabic, Persian, Cyrillic and Greek scripts. The one family it leaves out is CJK. When a package is missing, the error in the editor names it. There are two ways to get it.
Add one package
Put a docker-compose.override.yml next to docker-compose.yml that builds a derived compiler image, then run docker compose up -d --build:
services:
compiler:
image: aldine-compiler-local
build:
dockerfile_inline: |
FROM ghcr.io/trahloff/aldine-compiler:0.11.0
RUN tlmgr install pgfplots tikz-cd
Bump the FROM tag when you bump ALDINE_VERSION.
Get all of TeX Live, including CJK
Every release from 0.4.0 also ships all of TeX Live as a -full image: about 2.8 GB compressed and 9 GB on disk. CJK is only in this image. Add ALDINE_TEXLIVE=-full next to ALDINE_VERSION and pull again.
Logins and single sign-on
Out of the box Aldine is single-tenant with no login. Everything beyond the minimum is configured with environment variables, and those are wired up in docker-compose.full.yml, which carries the same compiler sandbox and the same volumes, so you can switch without losing data. That file has no image: lines: it needs a clone of the repository, builds from source (20 to 60 minutes the first time) and runs the commit you have checked out, not the pinned release image. Start it with docker compose -f docker-compose.full.yml up -d --build. To stay on the release images instead, add an environment: block with the variables you need to the app service of the minimal file. Settings go in a .env file next to the compose files:
# multi-user login, ownership and sharing (unset = single-tenant)
AUTH_ENABLED=1
# Google SSO
GOOGLE_OAUTH_CLIENT_ID=
GOOGLE_OAUTH_CLIENT_SECRET=
# GitHub SSO
GITHUB_LOGIN_CLIENT_ID=
GITHUB_LOGIN_CLIENT_SECRET=
# ORCID SSO
ORCID_CLIENT_ID=
ORCID_CLIENT_SECRET=
# OpenID Connect SSO (Keycloak, Authentik, Authelia, Pocket ID, …): redirect URI
# <ALDINE_PUBLIC_URL>/api/auth/oauth/oidc/callback; every option in docs/OIDC.md
OIDC_ISSUER=
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
OIDC_LABEL=
OIDC_ALLOWED_GROUPS=
Give each comment its own line: anything after a value becomes part of the value.
AUTH_ENABLED=1turns on login, per-project ownership and sharing (invite-only or by link).- Each provider is independent. Email and password works without any of them;
ALDINE_SSO_ONLY=1disables passwords entirely. ALDINE_ADMIN_EMAILS=you@example.orgopens/admin: accounts, active users, projects and compile time. Metadata only. Neither compose file passes this variable through, so addALDINE_ADMIN_EMAILS: ${ALDINE_ADMIN_EMAILS:-}underenvironment:of theappservice.- Recipes for Authentik, Keycloak, Authelia and Dex are in docs/OIDC.md.
Reverse proxy and TLS
Set the public URL and bind the app to loopback in .env:
ALDINE_APP_BIND=127.0.0.1
ALDINE_PUBLIC_URL=https://aldine.example.com
Then start the stack with the production overlay, which trusts proxy headers, sets secure cookies and rotates logs, and point your proxy at 127.0.0.1:8080:
docker compose -f docker-compose.full.yml -f deploy/docker-compose.prod.yml \
up -d --build
Both files are in the repository, so this runs from a clone. Building from source installs TeX Live into the compiler image; expect 20 to 60 minutes for the first build.
- nginx: use the sample vhost in
deploy/nginx.conf. The lines that matter areclient_max_body_size 81m(nginx's 1 MB default breaks ZIP import) and theUpgradeandConnectionheaders on/collab(without them the editor loads but live cursors never appear). - Traefik: route by labels to port 3000 of the app; the runbook has the overlay.
- No proxy yet: append
--profile tlsand setALDINE_DOMAINin.env. The bundled Caddy provisions and renews the certificate.
Backup and restore
Two volumes hold everything: aldine-data (projects and their git history) and aldine-secrets (users, sessions, keys, comments, usage). The repository ships scripts for both directions. They live in deploy/ of a clone, which the install above does not create, so clone the repository first:
deploy/backup.sh aldine-backup.tar.gz
# Restore, after stopping the stack with `docker compose down`:
deploy/restore.sh aldine-backup.tar.gz
For daily backups there is a systemd timer:
sudo cp deploy/aldine-backup.service deploy/aldine-backup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now aldine-backup.timer
Snapshots land in /var/backups/aldine and 14 are kept. The service file expects the checkout at /opt/aldine. If your compose project name is not aldine, set ALDINE_PROJECT for both scripts.
Compile isolation and limits
The compiler runs on an internal-only Docker network with no internet egress, drops all Linux capabilities and is bounded on memory and PIDs. LaTeX compiles with restricted shell-escape (whitelist only).
| Variable | What it limits | Default |
|---|---|---|
COMPILE_TIMEOUT_MS | Time per compile, in the compiler container | 120000 ms |
MAX_CONCURRENT_COMPILES | Compiles in flight in the compiler container | 2 |
RL_COMPILE_CONCURRENCY | Concurrent compiles the app forwards | 2 |
ALDINE_COMPILE_QUOTA_MIN | Compile minutes per user per month; over-quota compiles return HTTP 402 | uncapped |
docker-compose.full.yml passes COMPILE_TIMEOUT_MS and MAX_CONCURRENT_COMPILES through from .env. ALDINE_COMPILE_QUOTA_MIN is passed through by the deploy/docker-compose.prod.yml overlay. RL_COMPILE_CONCURRENCY is in neither file: add it under environment: of the app service yourself. Every variable Aldine reads is listed in the configuration table.
Storage: flat files, Postgres optional
Project files are real git repositories with worktrees on disk; that is what gives you branches and history. Users, sessions, project metadata, review comments and usage go through a datastore with two backends: JSON files (the default) or Postgres. The switch is configuration:
# in .env
DATABASE_URL=postgres://aldine:aldine@db:5432/aldine
# bring up with the bundled Postgres (or point at a managed one)
docker compose -f docker-compose.full.yml -f deploy/docker-compose.prod.yml \
--profile postgres up -d
What is single-node by design
The supported production topology is one app node, scaled vertically. Live collaboration state is held in the app process, and routing each project's collaboration socket to a consistent node is not built, so two app nodes behind a load balancer are not supported even with Postgres and Redis configured. When one box is not enough, the first step is to move the compiler container to its own machine against the shared data volume and set COMPILER_URL. docs/SCALING.md lists what exists and what does not.
Importing an Overleaf project
Download the project from Overleaf as a ZIP and drop it on the Aldine home screen. The root file is detected and the first typeset runs automatically. You can also import a repository from GitHub, GitLab, Gitea or Forgejo as a project. Aldine exports a project as a ZIP too, so the way out is the same as the way in.
Limits to know before you rely on it
- Young. Aldine is at v0.x (0.11.0, September 2026). Before 1.0 a minor version may change behaviour or the on-disk layout, so back up before upgrading.
- No keystroke-level track changes. Aldine has review comments with suggested edits.
- Single node. One app node is the supported topology; you scale it vertically. docs/SCALING.md says what is and is not built.
- CJK needs the full image. The default compiler image leaves out the CJK scripts; they are in the
-fullimage (about 2.8 GB compressed, 9 GB on disk). - You run it. There is no paid plan and no hosted service today.
If you are deciding between this and Overleaf's own self-hosted editions, self-hosting Overleaf (Community Edition, Server Pro) compared with Aldine has the comparison, including where Overleaf is the better choice.
No account, no install. The demo is a shared sandbox and resets every night.
Common questions
What does Aldine need to run?
Docker with the compose plugin and room for the compiler image (about 4.3 GB on disk). No database is required.
How do I upgrade?
Back up, then run docker compose pull and docker compose up -d with ALDINE_VERSION set to the new release. Rolling back is the same command with the previous version.
How do I back up?
From a clone of the repository, run deploy/backup.sh aldine-backup.tar.gz, or install the systemd timer for daily snapshots of both volumes.
Does it support single sign-on?
Yes: Google, GitHub, ORCID and any OpenID Connect provider, with AUTH_ENABLED=1.