Le TeX Live 2024 déployé sur le disque à côté de moi occupe 8,7 Go. L'image texlive/texlive:latest sur Docker Hub fait environ 2,5 Go compressée, et le tag latest-minimal environ 335 Mo (valeurs amd64 telles qu'affichées en août 2026). Choisir de faire tourner LaTeX dans Docker, c'est d'abord une conversation sur ces chiffres. Cette page traite de l'image texlive/texlive elle-même — qui la construit, ce qu'elle contient, quel tag prendre, comment figer une version — et du piège propre aux conteneurs : le PDF qui ressort appartenant à root. La rédaction du workflow GitHub Actions, elle, relève de la page CI.
Pourquoi faire tourner TeX dans un conteneur
Il y a une raison : la reproductibilité. Qu'un même .tex donne le même PDF ne dépend pas du seul manuscrit. Cela dépend du millésime de TeX Live installé, de la révision de chaque package et des polices enregistrées. Une image Docker, c'est cet environnement figé avec son système d'exploitation et expédié dans une caisse. À l'intérieur se trouve un instantané de TeX Live avec ses moteurs, ses packages et ses polices : que l'hôte soit Windows, macOS ou un runner de CI, ouvrir la même caisse donne le même environnement. C'est pourquoi « ça compile chez moi, pas chez mon coauteur ni en CI » cesse pour l'essentiel.
Conteneurs et installation locale ne s'excluent pas. Le partage réaliste : écrire au jour le jour dans son éditeur avec un TeX Live local, et confier seulement la version finale et l'exemplaire envoyé aux coauteurs à un conteneur. Autre avantage : expérimenter sans salir sa propre installation — pour comprendre pourquoi un \usepackage{...} échoue, un TeX Live immaculé est à quelques minutes. Le prix existe pourtant : il faut tirer plusieurs gigaoctets au moins une fois, et dans un conteneur il n'y a ni visionneuse graphique ni recherche inverse SyncTeX. Formulé honnêtement : un conteneur est un environnement de compilation, pas d'édition.
Qui construit l'image texlive/texlive, et ce qu'elle contient
Elle n'est pas construite par le TeX Users Group mais par une communauté nommée Island of TeX — ce n'est pas une image officielle de TeX Live. La même image s'obtient depuis Docker Hub sous texlive/texlive et depuis GitLab sous registry.gitlab.com/islandoftex/images/texlive. La base est une image Debian testing slim ; le Dockerfile consigne pourquoi Alpine n'a pas été retenu : à l'époque de sa rédaction, des binaires comme biber n'étaient pas distribués pour la plateforme Linux/musl. Le contenu dépasse TeX Live lui-même : Java pour arara, Perl pour biber et xindy, Python avec Pygments pour minted, Ghostscript pour la conversion EPS, et jusqu'à gnuplot pour 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 jette le conteneur à la sortie, -v "$PWD":/workdir relie votre emplacement courant à /workdir dans le conteneur, et -w en fait le répertoire de travail. Entrée et sortie sont dans votre propre dossier : le PDF apparaît simplement là. Pour le japonais, LuaLaTeX est le chemin le plus court — il suffit de mettre latexmk -lualatex main.tex. Pour la route upLaTeX et dvipdfmx, posez le .latexmkrc montré plus loin à côté du manuscrit et appelez latexmk main.tex. Une chose à retenir : on peut lancer tlmgr update --self --all dans l'image, mais dès qu'on jette le conteneur, la mise à jour disparaît.
Quel tag tirer : les schémas et leurs tailles réelles
Les tags sont le produit de deux axes : le schéma (quelle quantité de TeX Live) et la présence ou non de la documentation et des sources. Le schéma compte cinq paliers — minimal, basic, small, medium, full — voire six si l'on ajoute context pour les utilisateurs de ConTeXt, et le latest nu est un alias de latest-full. On y ajoute -doc (manuels), -src (les sources .dtx) ou -doc-src (les deux). L'effet est spectaculaire : Docker Hub annonce latest (= full) à environ 2,5 Go et latest-doc-src à environ 6,7 Go — la seule documentation plus que double l'image. Sachant que texmf-dist/doc fait à lui seul 3,7 Go, rien d'étonnant.
| Tag | Contenu | Taille annoncée sur Docker Hub (amd64, août 2026) |
|---|---|---|
latest | Alias de latest-full : tous les packages, sans docs ni sources | environ 2,53 Go — le choix par défaut raisonnable |
latest-medium | Le schéma medium ; suffisant pour la plupart des documents courants | environ 898 Mo |
latest-small | Le schéma small ; quand on veut un job CI léger | environ 590 Mo |
latest-basic | Le schéma basic : LaTeX nu et un peu plus | environ 367 Mo |
latest-minimal | Le schéma minimal : essentiellement plain TeX, le plus petit | environ 335 Mo |
latest-doc-src | full plus manuels plus sources ; texdoc fonctionne ici | environ 6,69 Go — pour consulter en local |
TL2018-historic | Une version passée ; il en existe depuis 2013 | Pour recompiler un vieux manuscrit — voir la mise en garde ci-dessous |
Le choix pratique est simple. En CI, prenez le latest nu, ou latest-medium s'il contient ce qu'il vous faut. On ne va vers un tag -doc que si l'on veut texdoc en local. Une réserve accompagne les petits schémas : d'après le README d'Island of TeX, dans toute image autre que full, un exécutable installé par tlmgr install n'est pas ajouté au PATH automatiquement. Il faut enchaîner, par exemple tlmgr install <pkg> && tlmgr path add. Des builds ARMv8 (arm64) sont également publiés, mais le README les qualifie explicitement d'expérimentaux ; si quelque chose se comporte bizarrement sur Apple Silicon, --platform linux/amd64 mérite un essai.
Un tag historic n'est pas immuable : figez aussi le digest
Même un tag de version passée pointe vers une image qui change. Le README d'Island of TeX dit clairement des images historic qu'elles sont reconstruites chaque mois et mises à jour dès que l'image du système d'exploitation sous-jacent reçoit des mises à jour. TeX Live a beau être gelé, les bibliothèques Debian en dessous ne le sont pas. De plus, latest bascule chaque semaine vers un nouvel instantané du CTAN, et chacun de ces builds hebdomadaires porte un tag de la forme TL{version}-{année}-{mois}-{jour} (avant TL2021, le format incluait aussi l'heure et la minute). Donc si l'on veut vraiment figer la compilation finale d'un article, on écrit un digest plutôt qu'un tag — c'est le seul moyen fiable.
# 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.gzUn digest est un hachage du contenu même de l'image : quoi qu'il arrive en amont, on ne tire jamais que les mêmes octets. Pour une thèse, ou pour un travail qu'un relecteur pourrait demander de reproduire des années plus tard, consigner cette ligne dans le dépôt est un cadeau au soi futur. Pour les compilations quotidiennes qui n'exigent pas cette rigueur, latest convient. Le critère n'est pas « l'un ou l'autre » mais à quoi sert cette compilation.
Le PDF ressort appartenant à root
Sous Linux, c'est la première chose que l'on rencontre. L'utilisateur par défaut de l'image est root — le README d'Island of TeX dit explicitement que root est la valeur par défaut afin de pouvoir ajouter facilement des paquets avec apt — si bien que main.pdf et main.aux écrits dans un répertoire monté en bind appartiennent à root ; ensuite l'éditeur ne peut plus les enregistrer et git clean ne peut plus les supprimer. Docker Desktop sous macOS et Windows masque le problème car sa couche de partage de fichiers réécrit la propriété, mais sous Linux et en CI on tombe dessus sans cesse. Le remède consiste à passer ses propres UID et GID avec --user.
# 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.texPasser --user fait de vous un utilisateur sans répertoire personnel, et LuaTeX ou fontconfig peuvent protester en voulant écrire un cache. Pointer HOME vers un répertoire accessible en écriture, comme ci-dessus, suffit. Depuis février 2025, l'image fournit aussi un utilisateur non root nommé texlive, que le README recommande pour les images dérivées — tout en ajoutant que les permissions des montages bind peuvent demander un ajustement. Au fond, utiliser le même UID sur l'hôte et dans le conteneur est la seule vraie solution.
Construire une image propre au projet
Plutôt que de lancer tlmgr install à chaque compilation, il est plus rapide — et bien plus facile à déboguer — de construire une image contenant le nécessaire et de la figer. On part de FROM texlive/texlive:latest-medium et l'on ajoute les packages ou polices manquants. Un joli tour de main se cache ici. L'image d'Island of TeX fabrique avec equivs un paquet Debian fictif nommé texlive-local, en version 9999.99999999, et le marque comme installé. De ce fait, installer un paquet Debian qui dépend de TeX — apt-get install pandoc, par exemple — n'entraîne pas un second TeX Live empaqueté par Debian.
# 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 /workdirOn la construit avec docker build -t myproject-tex ., puis on n'utilise plus que ce tag. Avec les polices CJK cuites dedans, la combinaison LuaLaTeX plus luatexja-fontspec se suffit à elle-même dans la caisse. Pour remettre l'image à des coauteurs ou à une équipe, on la pousse dans un registre interne ou on la fige avec docker save. À ce stade, le vrai gain n'est pas en mégaoctets : c'est que la consigne donnée à un collaborateur tient en une ligne, au lieu de « installe TeX Live toi-même ».
Utiliser l'image depuis la CI : GitHub Actions et GitLab CI
Sur GitLab CI, on nomme l'image dans le image: du job ; sur GitHub Actions, on spécifie un conteneur directement ou l'on utilise xu-cheng/latex-action. Une idée reçue mérite d'être écartée ici : xu-cheng/latex-action n'utilise pas texlive/texlive. L'action tire ghcr.io/xu-cheng/texlive-alpine ou ghcr.io/xu-cheng/texlive-debian, publiés par le projet xu-cheng/latex-docker du même auteur. C'est pourquoi son entrée os vaut alpine par défaut ; texlive_version accepte 2020 à 2026, ou latest. Bref, deux choses décrites comme « un conteneur avec TeX Live » peuvent être fort différentes à l'intérieur.
# .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.texÉcrire l'image dans container: a l'avantage que le docker run local et la CI sont littéralement le même environnement. Passer par xu-cheng/latex-action est plus court et offre des entrées fines comme latexmk_use_lualatex et latexmk_shell_escape (ses arguments latexmk par défaut sont -pdf -file-line-error -halt-on-error -interaction=nonstopmode). Le choix est un arbitrage entre identité avec l'environnement local et concision ; si la reproductibilité prime, prenez le premier. L'assemblage du workflow, le cache et la gestion des artefacts relèvent de la page CI.
Rendre un manuscrit prêt pour le conteneur
Les manuscrits qui échouent en conteneur ont un point commun : la procédure de compilation n'existe que dans un réglage d'éditeur. Fichier racine, moteur, commandes auxiliaires, emplacement de sortie — dès que ces quatre éléments existent sous forme de texte dans le dépôt, la même ligne latexmk main.tex fonctionne en local, dans Docker et en CI. Si vous employez upLaTeX et dvipdfmx pour le japonais, posez un .latexmkrc à côté de main.tex et écrivez la route explicitement. $pdf_mode = 3 signifie « produire un DVI, puis en faire un PDF avec dvipdfmx ».
# .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;Une fois ce .latexmkrc versionné, le PDF est fabriqué selon les mêmes règles, qu'on l'appelle depuis docker run, GitLab CI ou GitHub Actions. À côté, prenez l'habitude de revoir le tag de l'image chaque fois que vous ajoutez réellement un \usepackage. Ajoutez tikz-cd alors que le schéma reste figé sur medium, et un beau jour seule la CI tombera, avec ! LaTeX Error: File 'tikz-cd.sty' not found. Versionnez le manuscrit et l'environnement ensemble : c'est au fond l'engagement que l'on prend en composant dans un conteneur.