Das TeX Live 2024, das auf der Platte neben mir entpackt liegt, umfasst 8,7 GB. Das Image texlive/texlive:latest auf Docker Hub ist komprimiert rund 2,5 GB groß, das Tag latest-minimal etwa 335 MB (amd64-Werte, wie im August 2026 gelistet). Die Entscheidung, LaTeX in Docker laufen zu lassen, ist zuallererst ein Gespräch über diese Zahlen. Diese Seite handelt vom Image texlive/texlive selbst – wer es baut, was darin steckt, welches Tag zu wählen ist und wie sich eine Version festnageln lässt – dazu von der Falle, die es nur in Containern gibt: dem fertigen PDF, das root gehört. Wie die GitHub-Actions-Workflow-Datei aussieht, behandelt die CI-Seite.
Warum TeX überhaupt im Container laufen lassen
Es gibt einen Grund: Reproduzierbarkeit. Ob dasselbe .tex dasselbe PDF ergibt, entscheidet nicht das Manuskript allein. Es hängt davon ab, welcher Jahrgang von TeX Live installiert ist, welche Revision jedes einzelne Paket hat und welche Schriften registriert sind. Ein Docker-Image ist genau diese Umgebung, zusammen mit ihrem Betriebssystem eingefroren und als Kiste ausgeliefert. Darin liegt ein Schnappschuss von TeX Live samt Engines, Paketen und Schriften; ob der Host Windows, macOS oder ein CI-Runner ist, dieselbe Kiste ergibt dieselbe Umgebung. Deshalb hört „läuft bei mir, scheitert bei der Mitautorin und in CI“ weitgehend auf.
Container und lokale Installation schließen einander nicht aus. Die realistische Aufteilung: täglich im Editor gegen ein lokales TeX Live schreiben und nur den finalen Build sowie die an Mitautoren versandte Fassung an einen Container übergeben. Ein weiterer Vorteil ist das Experimentieren, ohne die eigene Installation zu verschmutzen – wer eingrenzen will, warum ein \usepackage{...} scheitert, hat in wenigen Minuten ein blitzsauberes TeX Live. Es hat aber seinen Preis: Mindestens einmal müssen mehrere Gigabyte gezogen werden, und im Container gibt es weder GUI-Betrachter noch SyncTeX-Rückwärtssuche. Ehrlich gesagt ist ein Container eine Bau-, keine Schreibumgebung.
Wer das Image texlive/texlive baut und was darin steckt
Gebaut wird es nicht von der TeX Users Group, sondern von einer Gemeinschaft namens Island of TeX – ein offizielles TeX-Live-Image ist es nicht. Dasselbe Image gibt es sowohl auf Docker Hub als texlive/texlive als auch auf GitLab als registry.gitlab.com/islandoftex/images/texlive. Grundlage ist ein schlankes Debian-testing-Image; das Dockerfile hält fest, warum nicht Alpine gewählt wurde: Zur Entstehungszeit wurden Binärdateien wie biber nicht für die Plattform Linux/musl ausgeliefert. Enthalten ist mehr als TeX Live selbst: Java für arara, Perl für biber und xindy, Python mit Pygments für minted, Ghostscript für die EPS-Umwandlung und sogar gnuplot für pgfplots.
# One-off build of ./main.tex. The image's own WORKDIR is /workdir.
docker run --rm -v "$PWD":/workdir -w /workdir texlive/texlive \
latexmk -pdf main.tex
# Poke around inside instead of building:
docker run --rm -it -v "$PWD":/workdir -w /workdir texlive/texlive bash--rm verwirft den Container beim Beenden, -v "$PWD":/workdir bindet den aktuellen Ort an /workdir im Container, und -w macht ihn zum Arbeitsverzeichnis. Ein- und Ausgabe liegen im eigenen Ordner, das PDF erscheint also einfach dort. Für Japanisch ist LuaLaTeX der kürzeste Weg – man tauscht schlicht latexmk -lualatex main.tex ein. Für die Route über upLaTeX und dvipdfmx legt man die weiter unten gezeigte .latexmkrc neben das Manuskript und ruft latexmk main.tex auf. Eines noch: Im Image lässt sich tlmgr update --self --all ausführen, doch mit dem Verwerfen des Containers ist die Aktualisierung wieder fort.
Welches Tag ziehen: Schemata und ihre realen Größen
Tags sind das Produkt zweier Achsen: des Schemas (wie viel TeX Live) und der Frage, ob Dokumentation und Quellen enthalten sind. Das Schema kennt fünf Stufen – minimal, basic, small, medium, full – beziehungsweise sechs, zählt man context für ConTeXt-Nutzende mit; das nackte latest ist ein Alias für latest-full. Darauf kommen -doc (Handbücher), -src (die .dtx-Quellen) oder -doc-src (beides). Die Wirkung ist drastisch: Docker Hub führt latest (= full) mit rund 2,5 GB und latest-doc-src mit rund 6,7 GB – allein die Dokumentation mehr als verdoppelt das Image. Da texmf-dist/doc für sich schon 3,7 GB umfasst, überrascht das nicht.
| Tag | Inhalt | Auf Docker Hub angegebene Größe (amd64, Aug. 2026) |
|---|---|---|
latest | Alias für latest-full: alle Pakete, ohne Doku und Quellen | rund 2,53 GB – die vernünftige Vorgabe |
latest-medium | Das Schema medium; für die meisten Alltagsdokumente ausreichend | rund 898 MB |
latest-small | Das Schema small; wenn der CI-Job leicht bleiben soll | rund 590 MB |
latest-basic | Das Schema basic: schlichtes LaTeX und ein wenig mehr | rund 367 MB |
latest-minimal | Das Schema minimal: im Wesentlichen plain TeX, das Kleinste | rund 335 MB |
latest-doc-src | full plus Handbücher plus Quellen; hier funktioniert texdoc | rund 6,69 GB – zum Nachschlagen vor Ort |
TL2018-historic | Eine vergangene Veröffentlichung; Images gibt es ab 2013 | Zum Neubau alter Manuskripte – siehe den Hinweis unten |
Die praktische Wahl ist einfach. In CI nimmt man das nackte latest oder latest-medium, falls es das Nötige enthält. Zu einem -doc-Tag greift man nur, wenn man texdoc lokal braucht. Bei den kleineren Schemata gibt es einen Vorbehalt: Laut der README von Island of TeX wird in jedem Image außer full eine mit tlmgr install nachinstallierte ausführbare Datei nicht automatisch in den PATH aufgenommen. Man muss nachlegen, etwa tlmgr install <Paket> && tlmgr path add. Auch ARMv8-Builds (arm64) werden veröffentlicht, doch die README nennt sie ausdrücklich experimentell; verhält sich auf Apple Silicon etwas merkwürdig, lohnt --platform linux/amd64 einen Versuch.
Ein historic-Tag ist nicht unveränderlich – auch den Digest festnageln
Auch ein Tag einer vergangenen Veröffentlichung zeigt auf ein Image, das sich ändert. Die README von Island of TeX sagt über die historic-Images unmissverständlich, dass sie monatlich neu gebaut und aktualisiert werden, sobald es Aktualisierungen für das zugrunde liegende Betriebssystem-Image gibt. TeX Live mag eingefroren sein, die darunterliegenden Debian-Bibliotheken sind es nicht. Hinzu kommt, dass latest wöchentlich auf einen neuen CTAN-Schnappschuss wechselt und jeder dieser wöchentlichen Builds ein Tag der Form TL{Release}-{Jahr}-{Monat}-{Tag} trägt (vor TL2021 enthielt das Format auch Stunde und Minute). Wer den finalen Build einer Arbeit wirklich einfrieren will, schreibt daher einen Digest statt eines Tags – anders geht es nicht zuverlässig.
# A tag can be re-pointed later; a digest cannot.
docker pull texlive/texlive:latest
docker image inspect --format '{{index .RepoDigests 0}}' texlive/texlive:latest
# -> texlive/texlive@sha256:0123abcd...
# Record that string in the repository and build against it forever:
docker run --rm -v "$PWD":/workdir -w /workdir \
texlive/texlive@sha256:0123abcd... latexmk -pdf main.tex
# Belt and braces for archival work: keep your own copy of the image.
docker save texlive/texlive@sha256:0123abcd... | gzip > texlive-frozen.tar.gzEin Digest ist ein Hash des Image-Inhalts selbst; was auch immer stromaufwärts geschieht, man zieht stets dieselben Bytes. Für eine Dissertation oder für Arbeiten, deren Reproduktion Jahre später verlangt werden könnte, ist diese eine Zeile im Repository ein Geschenk an das eigene zukünftige Ich. Für alltägliche Builds, die diese Strenge nicht brauchen, genügt latest. Das Kriterium lautet hier nicht „egal“, sondern wofür der Build gedacht ist.
Das fertige PDF gehört root
Unter Linux stößt man zuerst darauf. Der Standardbenutzer des Images ist root – die README von Island of TeX sagt ausdrücklich, root sei die Vorgabe, damit sich mit apt bequem Pakete nachinstallieren lassen –, also gehören main.pdf und main.aux, die in ein per Bind-Mount eingebundenes Verzeichnis geschrieben werden, anschließend root; der Editor kann sie nicht überschreiben, git clean sie nicht entfernen. Docker Desktop unter macOS und Windows verbirgt das, weil seine Dateifreigabeschicht die Eigentümerschaft umschreibt; unter Linux und in CI begegnet es einem ständig. Abhilfe schafft --user mit der eigenen UID und GID.
# Run as the calling user, so the PDF belongs to you.
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PWD":/workdir -w /workdir \
texlive/texlive latexmk -pdf main.tex
# If a package writes to $HOME (luaotfload caches, fontconfig), give it one:
docker run --rm --user "$(id -u):$(id -g)" -e HOME=/workdir \
-v "$PWD":/workdir -w /workdir \
texlive/texlive latexmk -lualatex main.texMit --user läuft man dann allerdings als Benutzer ohne Home-Verzeichnis, und LuaTeX oder fontconfig beschweren sich womöglich beim Schreiben eines Caches. Dann genügt es, HOME wie oben auf ein beschreibbares Verzeichnis zu richten. Seit Februar 2025 enthält das Image außerdem einen Nicht-root-Benutzer namens texlive; die README empfiehlt ihn für abgeleitete Images – und fügt im selben Atemzug hinzu, dass die Rechte bei Bind-Mounts angepasst werden müssen. Im Kern ist dieselbe UID auf Host und im Container die einzige echte Lösung.
Ein projekteigenes Image darauf aufbauen
Statt bei jedem Build tlmgr install laufen zu lassen, ist es schneller – und viel leichter zu debuggen –, einmal ein Image mit allem Nötigen zu bauen und festzunageln. Man beginnt mit FROM texlive/texlive:latest-medium und ergänzt fehlende Pakete oder Schriften. Hier steckt ein hübscher Kniff. Das Image von Island of TeX erzeugt mit equivs ein fiktives Debian-Paket namens texlive-local in der Version 9999.99999999 und markiert es als installiert. Dadurch zieht die Installation eines Debian-Pakets, das von TeX abhängt – etwa apt-get install pandoc –, kein zweites, von Debian paketiertes TeX Live nach.
# Dockerfile
FROM texlive/texlive:latest-medium
# Extra TeX packages. On a non-full scheme, "path add" is required
# for any package that installs a new executable.
RUN tlmgr install latexmk siunitx biblatex biber \
&& tlmgr path add
# Extra system packages: the image already claims texlive is installed,
# so this will not pull in Debian's own TeX Live.
RUN apt-get update \
&& apt-get install -y --no-install-recommends fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workdirGebaut wird mit docker build -t myproject-tex .; danach benutzt man nur noch dieses Tag. Sind die CJK-Schriften eingebacken, ist die Kombination aus LuaLaTeX und luatexja-fontspec in der Kiste vollständig. Um das Image an Mitautoren oder ein Team zu geben, schiebt man es in eine interne Registry oder friert es mit docker save ein. Der eigentliche Gewinn sind an dieser Stelle nicht die Megabyte, sondern dass die Anweisung an Mitwirkende eine Zeile lang ist statt „installiere dir TeX Live“.
Das Image aus CI heraus nutzen: GitHub Actions und GitLab CI
In GitLab CI trägt man das Image in das image: des Jobs ein; in GitHub Actions gibt man entweder direkt einen Container an oder nutzt xu-cheng/latex-action. Eine Annahme sei hier ausgeräumt: xu-cheng/latex-action verwendet nicht texlive/texlive. Die Action zieht ghcr.io/xu-cheng/texlive-alpine oder ghcr.io/xu-cheng/texlive-debian, veröffentlicht vom Projekt xu-cheng/latex-docker desselben Autors. Deshalb steht ihre Eingabe os standardmäßig auf alpine; texlive_version akzeptiert 2020 bis 2026 oder latest. Kurz: Zwei Dinge, die beide „ein Container mit TeX Live“ heißen, können innen recht verschieden sein.
# .gitlab-ci.yml - the image goes straight into the job.
build:
image: texlive/texlive:latest
script:
- latexmk -pdf main.tex
artifacts:
paths:
- main.pdf
# GitHub Actions, run inside the same image instead of an action:
jobs:
build:
runs-on: ubuntu-latest
container: texlive/texlive@sha256:0123abcd...
steps:
- uses: actions/checkout@v7
- run: latexmk -pdf main.texDas Image in container: zu schreiben hat den Vorzug, dass lokales docker run und CI buchstäblich dieselbe Umgebung sind. xu-cheng/latex-action zu verwenden ist dafür kürzer und bietet feine Eingaben wie latexmk_use_lualatex und latexmk_shell_escape (die voreingestellten latexmk-Argumente lauten -pdf -file-line-error -halt-on-error -interaction=nonstopmode). Die Wahl ist ein Abwägen zwischen Identität mit der lokalen Umgebung und Kürze; hat Reproduzierbarkeit Vorrang, nimmt man Ersteres. Aufbau des Workflows, Caching und Umgang mit Artefakten sind Sache der CI-Seite.
Das Manuskript containerfähig machen
Manuskripte, die im Container scheitern, haben eines gemeinsam: Der Bauablauf steht nur in einer Editoreinstellung. Wurzeldatei, Engine, Hilfsbefehle, Ausgabeort – stehen diese vier als Text im Repository, genügt lokal, in Docker und in CI dieselbe eine Zeile latexmk main.tex. Wer für Japanisch upLaTeX und dvipdfmx verwendet, legt eine .latexmkrc neben main.tex und schreibt den Weg ausdrücklich hin. $pdf_mode = 3 heißt „erst ein DVI erzeugen, dann mit dvipdfmx ein PDF daraus machen“.
# .latexmkrc: upLaTeX + dvipdfmx
$latex = "uplatex -interaction=nonstopmode -halt-on-error %O %S";
$bibtex = "upbibtex %O %B";
$dvipdf = "dvipdfmx %O -o %D %S";
$pdf_mode = 3;Ist diese .latexmkrc eingecheckt, entsteht das PDF nach denselben Regeln, ob der Aufruf aus docker run, GitLab CI oder GitHub Actions kommt. Daneben lohnt die Gewohnheit, das Image-Tag jedes Mal zu überdenken, wenn tatsächlich ein \usepackage hinzukommt. Fügt man tikz-cd hinzu, während das Schema noch auf medium festgenagelt ist, fällt eines Tages nur CI um, mit ! LaTeX Error: File 'tikz-cd.sty' not found. Manuskript und Umgebung gehören gemeinsam unter Versionskontrolle – letztlich ist genau das die Zusage, die man mit dem Satz im Container gibt.