Docker容器化TeX Live:打造可移植的LaTeX论文编译环境 说实话作为一个常年和论文、排版打交道的人我以前听到“装 LaTeX 环境”就头大。不是因为 LaTeX 难学而是因为 TeX Live 这玩意一装就是几个 GB升级一次还得折腾半小时换台电脑又得重来一遍。直到我把整个编译环境塞进了 Docker用容器跑 TeX Live 编译 LaTeX 论文这套流程才算真正消停了。这篇文章就详细讲讲我是怎么做的包括镜像怎么选、Dockerfile 怎么写、日常怎么编译以及踩过的那些坑。适合本地不想装全家桶、又需要稳定编译环境的朋友尤其是经常换电脑写论文的学生党。1. 为什么选 Docker 跑 TeX Live——论文编译环境到底有哪些坑1.1 本地装 TeX Live 的四大烦恼先说结论在本地直接安装 TeX Live 不是不行但维护成本真的高。我总结下来主要有四类问题一直在反复消耗我的时间。第一是版本碎片化。TeX Live 每年更新一个大版本2022、2023、2024不同版本对宏包的支持不完全一样。期刊投稿的时候很多模板会明确要求“请使用 TeX Live 2022 编译”你机器上装的是 2024编译出来的格式可能就跟编辑部的要求有细微差别。为了一个返修稿去装一个旧版本说实话很劝退。第二是卸载和清理困难。Windows 上的 TeX Live 卸载还算好macOS 和 Linux 上如果你当时是手动装到/usr/local/texlive或者用户目录下时间一长根本记不清哪些文件是 TeX 的、哪些是后来其他工具放的。等你意识到需要清理的时候往往已经和系统混在一起了。第三是系统环境的干扰。同一个.tex文件换一台机器编译结果可能完全不一样。原因可能是 A 机器装了某个字体B 机器没装A 机器tlmgr更新过宏包B 机器还是老版本。尤其是我这种经常用笔记本加台式机来回写的人这种差异会让人怀疑人生。第四是团队协作和 CI 的需求。如果你跟同学合写论文或者想把论文构建过程接入自动化流程统一编译环境几乎是刚需。大家本地环境各不一样只有把环境固化成镜像才能保证“我这儿编译过你那儿也编译过”。1.2 Docker 方案的取舍分析用 Docker 跑 TeX Live本质上是把“编译工具链”和“你的操作系统环境”彻底隔离。镜像里是什么版本就是什么版本宿主机的环境再乱也影响不到编译结果。我自己最直观的感受是Docker 方案是在“体积”和“省心”之间做了取舍。TeX Live 镜像一般很大官方镜像解压后好几个 GB第一次拉取确实慢。但好处是拉一次以后就不用管了论文写完了、环境要升级了重新拉一个新 tag 的镜像就行宿主机上一点残留都没有。性能上也不需要担心。编译论文本身是 CPU 密集型的小任务Docker 的容器化开销几乎可以忽略不计。我拿一篇十几页的中文毕业论文试过纯xelatex编译也就几十秒和裸机跑几乎没有体感差异。真正的大工程比如好几本几百页的书容器化也不会有明显瓶颈毕竟编译不涉及 GPU、不涉及高频 I/O。1.3 什么场景适合用 Docker 编译 LaTeX不是所有情况都必须上 Docker但下面这几类场景我是强烈建议用的。多设备切换笔记本、台式机、公司电脑随时拉镜像就行保证所有设备编译结果一致。论文模板复现跑期刊或学校的模板时一个模板配一个容器模板之间的宏包冲突完全隔离。自动化和批量编译比如用 GitHub Actions 或自己搭的构建服务Docker 镜像天然适合作为执行环境。给不会装环境的朋友写教程我帮学弟学妹搭环境时直接给他们一个 Docker 命令比让他们安装 Visual Studio Code、再装 TeX Live 全家桶省心太多。反过来如果你的使用频率极低一个月就编译两三次而且只在一台固定的电脑上用那本地装一个精简版也行没必要非得引入 Docker 这个概念。2. 核心配置解析镜像选型与 Dockerfile 编写2.1 官方镜像和社区镜像怎么选目前跑 TeX Live 的 Docker 镜像主要有两类来源。一类是官方仓库比如texlive/texliveDocker Hub 上和ghcr.io/texlive/texliveGitHub Container Registry 上的新镜像。注意texlive/texlive这个仓库的 tag 规则有点特殊早期的 tag 是latest、2023.1这种带年份的后来官方换到了 GHCR推荐用ghcr.io/texlive/texlive:latest或指定年份的 tag。这类镜像是基于官方 TeX Live 的install-tl脚本构建的包含完整的宏包集合体积大但是最省心。另一类是社区精简镜像比如mawippel/texlive或者各种针对中文优化的镜像。这类镜像我实际用得不多因为搞不清它到底放了哪些宏包、去掉了哪些排查问题会更费劲。我给新手的建议是直接用官方镜像不要折腾精简版。TeX Live 编译报错里最多的就是 “Package xxx not found”精简镜像是为了省体积砍了宏包你写论文的时候根本猜不到模板下一秒要用哪个宏包。官方完整版镜像虽然大但基本涵盖 CTAN 上绝大多数常用宏包能把你从“缺啥装啥”的循环里解放出来。另外提一句用ghcr.io拉取镜像在国内网络环境下有时候很慢。常用的办法是给 Docker 配置 registry mirror或者把ghcr.io/texlive/texlive换成docker.io/texlive/texlive的旧镜像。但是版本 tag 会老一些自己权衡就好。2.2 基于官方镜像定制 Dockerfile直接拉官方镜子也能用但我个人还是习惯自己写一个 Dockerfile。原因有两个一是为了额外装一些模板需要但镜像里没有的字体或宏包二是为了方便在团队里分发其他人docker build一下就有一模一样的环境。下面是我一直在用的 Dockerfile基于ghcr.io/texlive/texlive:latest修改而来FROM ghcr.io/texlive/texlive:latest # 设置工作目录 WORKDIR /work # 安装常用系统工具方便排查问题 RUN apk add --no-cache \ fontconfig \ ttf-freefont \ curl \ git # 设置 CTAN 镜像源用于 tlmgr RUN tlmgr option repository https://mirrors.tuna.tsinghua.edu.cn/CTAN/systems/texlive/tlnet # 安装额外的常用宏包按需添加 RUN tlmgr install \ latexmk \ ctex \ xecjk \ fandol \ biblatex \ biber \ algorithm2e \ listings # 更新字体缓存 RUN fc-cache -f # 默认执行命令 CMD [bash]这里有几个值得说明的点。FROM我用了 GHCR 的最新镜像如果你需要精确的版本可以指定 tag比如ghcr.io/texlive/texlive:2025.1这样的格式。我建议写论文时固定一个 tag别用latest不然某天重新拉镜像可能就和你之前编译的环境不一样了。tlmgr option repository这一行是把宏包源切到国内 CTAN 镜像实测下载宏包速度快很多。当然这个命令不是必须的如果你不管网络环境也可以不切换。要注意的是新版 TeX Live 的tlmgr默认仓库地址是https://mirror.ctan.org/systems/texlive/tlnet有些网络环境下不稳定切到国内镜像纯粹是出于可用性考虑。apk add装的是 Alpine 的包因为ghcr.io/texlive/texlive:latest基础镜像是 Alpine Linux。如果你的基础镜像换成了 Debian 系的那就要用apt-get不要照抄。2.3 中文字体与 ctex 配置的细节中文论文绕不开ctex宏包和字体问题。在 Docker 里跑中文 LaTeX最省事的方式就是用ctex宏包 xelatex编译它默认会使用Fandol字体FandolSong、FandolHei 等这些字体在 TeX Live 自带的宏包集合里就有不需要额外安装系统字体。所以上面 Dockerfile 里我装fandol是有原因的。很多模板会显式指定\setCJKmainfont{FandolSong}或依赖 ctex 的默认字体设置如果镜像里没有 Fandol编译就会报 “Font FandolSong not found”。这个报错在本地机器上很常见因为 Fandol 并不是操作系统字体它在 TeX Live 的字体目录里。如果你的论文要求使用特定中文字体比如“宋体”或“黑体”那就涉及安装或挂载系统字体的问题。有两个思路把字体文件直接复制进镜像然后在 Dockerfile 里RUN fc-cache -f一劳永逸但镜像会变大。在运行容器时把宿主机的字体目录挂载进去比如-v /usr/share/fonts:/usr/share/fonts:roWindows 下可以挂载C:\Windows\Fonts。这种方式更灵活但不适合团队分发。我个人在团队协作时偏向于把字体“固化进镜像”因为成员之间的字体差异很容易踩坑。你要是自己单干挂载宿主字体就够用了。3. 实操过程构建镜像与日常编译命令3.1 构建并验证镜像Dockerfile 准备好后构建镜像非常简单docker build -t my-texlive:2025 .这里-t指定镜像名和 tag我用my-texlive:2025方便自己识别版本。构建过程中如果网络不好tlmgr install可能失败建议配置好国内镜像源再重试。构建完成后先验证一下环境是否正常docker run --rm my-texlive:2025 tex --version正常能看到 TeX Live 的版本信息。再验证一下 xelatexdocker run --rm my-texlive:2025 xelatex --version到这里你的 TeX Live 编译环境就已经是一个“可移植的工具箱”了去哪台机器都能用。3.2 日常编译挂载目录与 latexmk 自动编译写论文的时候工作目录里会有主.tex文件、图片文件夹、.bib文献库编译过程中还会生成一堆中间文件。所以每次运行容器时最关键的一步是把当前论文目录挂载进容器并且让容器在挂载目录下执行编译命令。我常用的编译命令长这样docker run --rm \ -v $PWD:/work \ -w /work \ my-texlive:2025 \ latexmk -xelatex -interactionnonstopmode -halt-on-error main.tex这里每个参数解释一下--rm容器结束后自动删除不留垃圾容器。-v $PWD:/work把当前目录挂载到容器里的/work目录这样容器里生成的 PDF 和中间文件会直接落在宿主机当前目录。-w /work进入容器后的工作目录。-interactionnonstopmode编译遇到错误时不要停下来等待交互直接把日志打出来。-halt-on-error遇到第一个错误就停止避免无意义的继续编译。latexmk自动判断编译次数跑完xelatex、bibtex、再跑xelatex直到交叉引用稳定比手动连续跑两三遍xelatex省心太多。如果你不想用latexmk手动跑命令也行docker run --rm -v $PWD:/work -w /work my-texlive:2025 xelatex -interactionnonstopmode main.tex docker run --rm -v $PWD:/work -w /work my-texlive:2025 bibtex main docker run --rm -v $PWD:/work -w /work my-texlive:2025 xelatex -interactionnonstopmode main.tex但这样效率低而且需要你记得每步的顺序。latexmk是 LaTeX 生态里的标准自动构建工具强烈建议直接用它。3.3 编译产物管理中间文件不搞乱目录LaTeX 编译会生成大量的中间文件.aux、.log、.toc、.bbl、.bcf、.out等等。如果全堆在当前目录时间长了目录会非常乱。而通过 Docker 挂载目录时容器在当前目录下生成的这些中间文件也会直接落到宿主机所以“乱目录”的问题并不会因为用了 Docker 就自动消失。解决方式有两种。一种是在latexmkrc里配置输出目录把中间文件放到一个子目录里# .latexmkrc $xelatex xelatex -interactionnonstopmode -halt-on-error -outdirbuild %O %S; $pdf_mode 5; # 使用 xelatex这样编译时中间文件都会生成在build目录下目录干净很多。另一种方式是编译完了直接在宿主机清理临时文件。可以写一个小脚本把main.aux、main.log这类垃圾文件删掉。我在实际项目里是把两种方式都用上了.latexmkrc里指定输出目录build另外在.gitignore里把build/忽略掉这样团队协作时不会误提交中间文件。3.4 tlmgr 补装宏包的正确姿势文章写了几个月模板突然用到一个镜像里没有的宏包这是常态。遇到这种情况不用改 Dockerfile 重新 build直接在现有容器里临时用tlmgr安装就行。不过要注意容器默认是无状态的直接跑docker run my-texlive:2025 tlmgr install xxx装完就没了下次容器还是老样子。如果只是想临时验证一下某个宏包可以这么跑docker run --rm my-texlive:2025 tlmgr install xxx但这是改到容器层容器一删除就没了。如果确认这个宏包以后一直要用正确做法是把它加进 Dockerfile重新 build 一遍镜像。所以在开始做项目时我习惯每隔一段时间就把常用宏包固化进 Dockerfile这样重新构建的镜像会越来越“顺手”。4. 用 VSCode LaTeX Workshop 在容器里一键编译4.1 为什么要让 LaTeX Workshop 调 Docker 编译如果你只是偶尔编译一下纯命令行就够了。但我写论文的频率高每次都在终端敲一长串docker run命令挺烦的。所以我把 VSCode 里的 LaTeX Workshop 扩展也配置成了走 Docker 编译这样在编辑器里点一下按钮就能出 PDF日志也能直接跳转到出错的行。核心思路其实不复杂LaTeX Workshop 本质上是调用一个编译命令我们只要把“本地latexmk”换成“docker run ... latexmk”就行。4.2 tools 和 recipes 的配置详解在 VSCode 的设置里加下面这段配置{ latex-workshop.latex.tools: [ { name: latexmk-docker, command: docker, args: [ run, --rm, -v, %DIR%:/work, -w, /work, my-texlive:2025, latexmk, -xelatex, -interactionnonstopmode, -halt-on-error, %DOC% ], env: {} } ], latex-workshop.latex.recipes: [ { name: latexmk (Docker), tools: [latexmk-docker] } ] }关键点在%DIR%和%DOC%这两个占位符。LaTeX Workshop 会自动把它们替换成当前文件的目录路径和文件名。注意 Windows 环境下%DIR%会变成类似C:\Users\xxx\paper这样的路径Docker Desktop 里挂载 Windows 路径时一般也能自动转换但路径带中文或空格的时候会出问题建议论文目录用英文名。配好之后打开一个.tex文件切到 LaTeX Workshop 侧边栏点一下 “latexmk (Docker)” 或者用快捷键CtrlAltB就会在终端里看到 Docker 容器启动、编译、输出日志的全过程。4.3 PDF 预览与正反向同步编译完 PDF 后LaTeX Workshop 默认会调用内置的 PDF 查看器直接可以预览。需要注意的是PDF 文件是容器生成的挂载目录后它会直接出现在宿主机当前目录里VSCode 打开它没有任何问题。反向同步从 PDF 点击跳转到源码也正常因为 LaTeX Workshop 读的是.synctex.gz文件这个文件由编译过程生成跟 Docker 环境的隔离没有关系。我实际用下来整个链路是源码文件在 Windows 宿主机 → VSCode 点击编译 → Docker 里跑latexmk→ 生成 PDF 在宿主机目录 → VSCode 自动刷新预览。整个过程很像是在本地编译但真正干活的其实是容器。4.4 日常使用体验和注意事项用这套方案写作几个月我的体会是编译速度取决于论文规模中等长度的论文几十页几秒到十几秒就能完成和本地环境几乎没有差别。真正要注意的是首次拉镜像或更新镜像时的等待这个时间无法避免毕竟 TeX Live 完整版太大。Windows 下还需要确保 Docker Desktop 处于运行状态。如果你突然点编译没反应第一反应应该是去看 Docker Desktop 是否正常启动很大概率是它没跑起来而不是你配置写错了。macOS 和 Linux 下相对省心一些没有这个状态检查环节。5. 常见问题与排查技巧实录5.1 编译报“Package xxx not found”这是最常见的错误原因就是镜像里没有对应的宏包。我的排查思路是三步走第一步先用docker run --rm my-texlive:2025 tlmgr search --global --all 宏包名查看这个宏包在 CTAN 上的存在情况。如果搜不到可能是宏包名写错了。第二步确认名字后装进当前镜像临时试用docker run --rm my-texlive:2025 tlmgr install 宏包名第三步试过能用之后把tlmgr install加进 Dockerfile重新构建镜像。这样一劳永逸。这里有个坑有些论文模板用了很久不更新的宏包可能在新的 TeX Live 里已经被合并到别的宏包里名字变化了。遇到这种情况搜索时除了搜包名还要去模板目录下的.cls文件里看它到底RequirePackage了什么然后逐个检查。5.2 “Fatal error occurred, no output PDF file produced”这种报错通常说明编译在某个环节彻底失败了。不要只看最后一行要往.log文件里翻。我一般用这种姿势来定位docker run --rm -v $PWD:/work -w /work my-texlive:2025 xelatex -interactionnonstopmode main.tex 21 | grep -A 10 ^!!开头的行是 LaTeX 报错的关键行通常后面跟着错误类型和行号。常见的比如! Undefined control sequence说明有命令拼写错误! LaTeX Error: File not found说明\includegraphics引用的图片路径不对。如果你用的是 VSCode 的 LaTeX Workshop直接在输出面板点错误信息就能跳到源码对应行定位起来很方便。5.3 中文字体找不到的问题中文论文里“Font FandolSong not found”是高频报错。出现这个问题的原因一般是镜像里没装 Fandol 字体或者装了但字体缓存没更新。验证方法docker run --rm my-texlive:2025 fc-list | grep Fandol如果有输出说明字体在没有输出说明镜像缺字体。解决办法就是把fandol宏包装上docker run --rm my-texlive:2025 tlmgr install fandol但注意就算宏包装了某些场景下 ctex 依然找不到字体。这通常是字体缓存的问题需要重新构建镜像时执行fc-cache -f。我在 Dockerfile 里特意加了这一行就是为了避免这种玄学问题。5.4 文件权限问题生成的文件是 root 拥有用 Docker 编译有一个不可避免的小麻烦容器默认以 root 用户运行生成的 PDF 和中间文件在宿主机上显示的所有者是 root。这意味着你想删掉或修改这些文件时普通用户权限经常不够得手动sudo。解决方式是在docker run时指定当前用户的 UID 和 GIDdocker run --rm \ -v $PWD:/work \ -w /work \ --user $(id -u):$(id -g) \ my-texlive:2025 \ latexmk -xelatex -interactionnonstopmode main.texLinux 和 macOS 下这个命令很通用。Windows 下的 Docker Desktop 权限模型不太一样一般不需要这样处理但我见过有些用户遇到文件只读的问题通常重置文件属性即可。5.5 Docker Desktop 启动失败与虚拟化问题Windows 用户比较容易遇到 Docker Desktop 无法启动提示虚拟化未开启。这个属于 Docker 环境本身的问题排查方式比较固定打开任务管理器确认虚拟化是否开启如果没有需要在 BIOS/UEFI 里开启硬件虚拟化然后再重新启动 Docker Desktop。还有一类情况是更新 Docker Desktop 之后突然起不来多半是 WSL 内核和 Docker Desktop 版本不匹配。处理办法是去“控制面板 - 启用或关闭 Windows 功能”里确认“适用于 Linux 的 Windows 子系统”和“虚拟机平台”两项处于开启状态或者执行wsl --update更新内核。macOS 上遇到 Docker Desktop 启动问题相对少重启一下 Docker Desktop 往往就能解决。5.6 磁盘占用过大与镜像清理方法TeX Live 镜像加上各种 tag时间久了很占磁盘空间。我建议定期清理一下不再使用的镜像docker system df docker image prune -f docker system prune -fdocker system df可以查看镜像、容器、卷占用的具体空间docker image prune -f清理悬空镜像docker system prune -f清理没用的构建缓存、停止的容器和不在使用的网络。注意不要乱加-adocker system prune -af会把所有没在使用的镜像全删掉如果你不是想彻底回归干净状态别轻易用。另外构建新镜像时如果改了 Dockerfile旧镜像可以保留一两个版本方便回退。我自己一般保留最近两个 tag太旧的就删掉。6. 回归一下“为什么值得这么干”最后从一个个人使用者的角度说点实在的。用 Docker 来跑 TeX Live最让我受益的不是“技术多炫”而是它把写论文过程中最不稳定的环境因素给抹平了。我不用再担心模板缺某个宏包、不用在不同电脑之间同步环境、不用在换机之后花一下午重装软件。容器挂载目录、生成 PDF、VSCode 直接预览这套流程一旦跑顺日常使用的体感其实和本地编译几乎一样。我踩过最典型的坑反而是最开始想省体积、用精简镜像结果每次编译都在“装宏包”的路上折腾浪费时间比省下的那点磁盘空间多得多。所以如果你也准备用这套方案我的建议是直接上官方完整版镜像把环境一次性配置到位。后面写论文时你会感谢自己当初这个决定。