用 Docker 容器化 LaTeX 编译环境:从镜像选择到 VSCode 联动
2026/9/16 3:03:26 网站建设 项目流程

写 LaTeX 论文,最崩溃的往往是环境而不是排版本身。在电脑上装过 TeX Live 的人,大概都经历过这种情景:下载安装包好几个 GB,装完发现模板需要某个宏包的补丁版本;年初装好的环境,年底打开旧论文,参考文献却报出This is BibTeX, version 0.99d之后就没下文了,一查是 TeX Live 版本差异导致辅助文件不兼容。这些问题归根结底一句话:本机 LaTeX 环境很难保证“一次安装,长期稳定,处处一致”。后来我把 Docker 和 TeX Live 组合在一起,用容器来跑 LaTeX 编译,把论文排版环境固化成镜像,才算是真正从这堆环境问题里解放出来。这篇文章不是什么官方文档翻译,是我自己搭这套 LaTeX 论文排版编译平台的完整记录,包含镜像选择、部署编排、VSCode 联动、中文排版、性能优化的踩坑,适合正被 LaTeX 环境折腾的写论文党,也适合想给实验室或小团队统一编译环境的同学。

1. 折腾久了才明白:LaTeX 环境最大的问题不是 LaTeX 本身

1.1 本地安装和使用的三个常见困境

先聊聊我在本地方案上踩过的坑。第一是版本碎片化。TeX Live 是每年一个大版本,2025、2026 的宏包仓库都在持续更新。你如果用的是 macOS 的 MacTeX,或者 Linux 发行版的apt install texlive-full,版本大概率落后于最新发布。论文模板往往对宏包版本很敏感,同一个模板在旧版本 TeX Live 下可能一个宏包报错,换到新版就正常,反之亦然。最典型的就是 biblatex 配合某些样式文件,旧版报Package biblatex Warning,新版才能编译。

第二是宏包依赖。LaTeX 的宏包生态非常庞杂,ctexalgorithm2ebeamercaption这些常用宏包之间还有隐性依赖。本机装环境往往是一边编译一边tlmgr install,装到最后,你的系统里多了一堆不知道谁依赖谁的包,换电脑之后再装一遍,又是一个下午。

第三是可复现性差。我在写毕业论文那阵,从 Windows 切到 macOS,环境重新搭了两天半。同学发过来的模板在实验室 Linux 服务器上编译正常,到我的电脑上编译就报字体缺失。这些都不是 LaTeX 语法问题,而是环境状态不一致。

1.2 容器化到底解决了什么

Docker 部署 TeX Live 的核心思路,就是把整个 TeX Live 安装目录、宏包、字体、编译脚本全部打进一个镜像。你不需要在系统里安装任何 LaTeX 发行版,只需要一个 Docker 运行环境,然后通过docker rundocker compose调用容器里的编译工具链。

这带来三个很实际的好处。一是环境一致性:镜像是什么样,编译环境就是什么样,镜像层面锁定了 TeX Live 版本和宏包集合;本地明明调好的环境,不会因为操作系统升级或者别的软件改了你系统的 PATH 就突然挂掉。二是快速重建:不管换了哪台电脑,只要拉同一个镜像,一条命令就能恢复完整环境,不再有“装三天环境”的焦虑。三是隔离与可分享:容器不污染宿主系统,同一台电脑可以同时保留多个 TeX Live 版本,把镜像推送到团队仓库后,成员拉下来就拥有一模一样的排版环境。

当然,容器化并不是没有学习成本,后面几章我会把这些成本尽量摊开讲:镜像该怎么选、compose 怎么写、怎么和编辑器联动、字体怎么处理、性能怎么优化。

2. 镜像选型:几个主流 TeX Live 镜像到底差在哪

2.1 TUG 官方 texlive/texlive 镜像

如果你的第一反应是去 Docker Hub 搜 LaTeX 或 TeX Live 镜像,大概率会先碰到texlive/texlive。这是 TeX Users Group(TUG)维护的官方镜像,按年份打 tag,同时通过 scheme 区分安装规模,常见的有scheme-smallscheme-mediumscheme-full。选它最大的优点是“根正苗红”,由官方组织维护,和 TeX Live 发布节奏同步,适合希望严格跟随官方版本的场景。

但我的实际体验是,官方镜像默认不会把所有宏包都装进去。你拿它跑简单文章没问题,一旦论文模板用到特定宏包(比如各学报的模板里那一堆专属.sty和字体),就需要在运行时执行tlmgr install,这会引入网络下载和版本匹配的额外变量。如果你想完全避免这些麻烦,建议直接用 scheme-full 这样的完整 tag,代价是镜像体积非常大,拉取时间长。

2.2 GitHub 生态里常见的 texlive-full 完整镜像

我目前的主力镜像来自 GitHub Container Registry,即ghcr.io/xu-cheng/texlive-full。这个镜像按年份打 tag,最新 tag 基本跟随年度 TeX Live 发布,里面装的是 full scheme,也就是说绝大多数常用宏包、字体、bib 工具都已经就绪。它最初是为 CI/CD 场景设计的,所以在 GitHub Actions、GitLab CI 这些流水线里使用率很高。

为什么会选它而不是官方 full 镜像?坦白说,两者在宏包覆盖度上差别不大,但ghcr.io/xu-cheng/texlive-full在实践里更省心:入口命令齐全,latexmkxelatexpdflatexbiberbibtex都能直接执行,而且按年份的 tag 策略非常清晰。论文写了大半,编译器版本被钉在今年,明年也不会变动影响结果,这一点对学术投稿很重要。

2.3 轻量镜像和其他选择

除了上面两个,还有一个我偶尔用的pandoc/latex镜像。它主要面向 Markdown 转 PDF 的场景,预置了 pandoc 和一个中等规模的 TeX Live,适合快速把稿子转成 PDF,但如果要编译规范的学位论文模板就力不从心。另一个是blang/latex,镜像虽然不大,但宏包集合主要是 CI 场景的常用子集,遇到冷门宏包同样得现场安装。

下面用表格给出它们的大致特点,方便你快速做选择:

镜像维护方宏包覆盖典型体积适合场景
texlive/texliveTUG 官方按 tag 区分中等或很大追求官方权威性,愿意自己装宏包
ghcr.io/xu-cheng/texlive-fullxu-chengfull,完整很大论文模板复杂、需要开箱即用
pandoc/latexPandoc 社区常用子集中等Markdown/CommonMark 转 PDF
blang/latex社区CI 子集中等轻量编译任务

我给的结论很简单:如果是正儿八经的论文排版,直接选择 full 类镜像,省下的时间远比磁盘空间值钱。如果只是偶尔把稿件转成 PDF,再考虑轻量镜像。

3. 我的部署方案:镜像、挂载和 Compose 编排都给你

3.1 定制一个带中文字体的 Dockerfile

选定了镜像,下一步是定制。绝大多数论文模板需要中文字体,而基础镜像里未必有完整的中文字体,所以我会在 Dockerfile 里补上字体这一层。这里有一个常识:LaTeX 里的字号、字体文件本身不归 TeX 管,而归操作系统的 fontconfig 管;ctex 宏包调用某个字体失败时,往往不是宏包问题,而是系统字体目录里根本没有对应的字体文件。

下面是我实际在用的 Dockerfile:

# 实际使用建议把 latest 换成具体年份 tag,例如 ghcr.io/xu-cheng/texlive-full:2025 FROM ghcr.io/xu-cheng/texlive-full:latest RUN apt-get update \ && apt-get install -y --no-install-recommends \ fonts-noto-cjk \ fonts-noto-cjk-extra \ fontconfig \ && rm -rf /var/lib/apt/lists/* \ && fc-cache -f WORKDIR /work CMD ["bash"]

fonts-noto-cjk提供开源的思源黑体等 CJK 字体,论文模板没有强制指定商业字体时,用它最省事。fc-cache -f这一步刷新系统的字体缓存,否则新装的字体不会立刻被识别。如果你的学校模板明确要求“宋体”“黑体”,那就把对应字体文件放到镜像里,比如拷贝到/usr/local/share/fonts/再执行fc-cache -f,或者通过 Volume 挂载进去也行。

3.2 docker-compose.yml 的实际写法

有了镜像,日常用的时候我不太喜欢敲一长串docker run,尤其是参数一多真的容易重复出错。推荐用 compose 文件把启动参数固化下来。以下是我写在项目根目录的docker-compose.yml

services: latex: image: ghcr.io/xu-cheng/texlive-full:latest container_name: latex-build volumes: - ./papers:/work/papers - latex-cache:/root/.texlive working_dir: /work user: "${UID:-1000}:${GID:-1000}" stdin_open: true tty: true command: tail -f /dev/null volumes: latex-cache:

解释几个关键点。第一是volumes:左边是宿主机论文目录,右边是容器内的/work/papers,两边实时同步,你在宿主机写.tex,容器里立刻能看到。第二是user:容器默认以 root 运行,生成出来的.aux.log.pdf文件也归 root,回到宿主机想删都删不掉,所以最好把用户 ID 映射为当前用户。在 Linux 上${UID:-1000}能正确取到当前用户 ID,macOS 上不同用户可能是 501,需要按id -u的结果调整。第三是command: tail -f /dev/null:让容器保持后台运行,等想编译时再docker compose exec latex latexmk ...

如果你只是临时编译一次,不想启动常驻容器,直接用这一条:

docker run --rm -v "$PWD":/work -w /work \ ghcr.io/xu-cheng/texlive-full:latest \ latexmk -xelatex -interaction=nonstopmode -synctex=1 main.tex

--rm表示容器退出就删除,适合一锤子买卖;-w /work是指容器内工作目录,这样 PDF 就生成在当前目录下。

3.3 第一次启动时绕不开的两个大坑

第一次在 Windows 上启动 Docker Desktop 时,我遇到过virtualization support not detected的报错,启动按钮点下去一直是转圈,最后提示 Docker Desktop failed to start。这个问题基本不是 Docker 的问题,而是 Windows 的虚拟化支持没有打开。排查顺序一般是:先到“启用或关闭 Windows 功能”里打开“Windows 虚拟机监控程序平台”和“适用于 Linux 的 Windows 子系统”,然后把 BIOS 里的虚拟化技术(Intel VT-x / AMD-V)打开,重启后再看 WSL2 状态。如果是公司统一配发的电脑,BIOS 被锁,那可能要找 IT 管理员。

第二个坑是拉镜像太慢。full 类的 TeX Live 镜像动辄几个 GB,网络不佳时拉取就是灾难。一个稳妥的办法是在 Docker Desktop 的 Docker Engine 配置中增加 registry-mirrors,让 Docker 拉取基础镜像的速度大幅提升。这一步只是把 Docker 的镜像拉取地址换成可用的镜像加速器,对镜像内容没有任何影响。如果你所在的网络访问 GitHub Container Registry 更顺畅,也可以把基础镜像改成 ghcr.io 上的对应镜像,速度问题要靠实际网络环境来权衡。

4. 打通 VSCode:让 LaTeX Workshop 用容器编译

4.1 两种连接思路

环境搭好之后,真正的使用频率取决于编辑器。我平时写 LaTeX 用的是 VSCode,所以这一步的关键是让 VSCode 里最常用的 LaTeX Workshop 插件能调用容器内的编译命令,而不是本地安装一套 TeX Live。

目前有两条路线。第一条是完全容器化的 Dev Containers:本地 VSCode 通过 Dev Containers 插件,直接把整个开发环境开进容器,VSCode 里的终端、LaTeX Workshop、文件目录全部在容器内运行。这个方案最沉浸,适合长期写论文,缺点是第一次配置时插件需要在容器内重新安装一遍,有点重。

第二条是轻量路线:本地 VSCode 只负责编辑,用 LaTeX Workshop 配置 Docker 编译命令,每次按编译按钮时,插件启动一个一次性容器来执行 LaTeX 编译,PDF 再回到宿主机预览。我不需要装 TeX Live,也不需要进容器开发,速度也够快。我的建议是先试第二条,觉得不够用再上 Dev Containers。

4.2 LaTeX Workshop 配置文件的完整写法

下面是我在用户设置settings.json里保存的配置:

{ "latex-workshop.latex.tools": [ { "name": "docker-latexmk", "command": "docker", "args": [ "run", "--rm", "-v", "%DIR%:/work", "-w", "/work", "ghcr.io/xu-cheng/texlive-full:latest", "latexmk", "-xelatex", "-interaction=nonstopmode", "-synctex=1", "%DOC_EXT%" ], "env": {} } ], "latex-workshop.latex.recipes": [ { "name": "docker latexmk", "tools": ["docker-latexmk"] } ], "latex-workshop.view.pdf.viewer": "tab" }

这里拉拉杂杂讲几个参数的含义。%DIR%是 LaTeX Workshop 提供的占位符,代表当前.tex文件所在的目录,把它映射到容器内的/work,这样编译产出的辅助文件会留在宿主机目录里。%DOC_EXT%是带扩展名的文件名,传给 latexmk 的就是main.tex-xelatex设置 latexmk 使用 XeLaTeX 编译,这是中文排版最常见的引擎。-interaction=nonstopmode是让编译遇到错误时不卡在交互界面,直接输出错误信息到日志,对排查问题非常重要。

我实际用下来,这套配置的开销只是每次编译前启动一下容器,通常几秒钟,比想象中轻快。因为挂载的宿主机目录是实时同步的,之前跑过的中间文件(.aux等)还在,latexmk 会基于增量结果判断哪些章节需要重编,大量页面没变的时候,后续编译速度并不慢。

4.3 正反向搜索:Synctex 的路径对齐

LaTeX Workshop 自带的 PDF 预览支持 Synctex 正反向搜索:在.tex里点击可以跳到 PDF 对应位置,在 PDF 里点击可以跳回源码。这个功能在容器化方案里很容易踩坑,因为容器内的文件路径(比如/work/main.tex)和宿主机上的绝对路径(比如D:\Papers\main.tex)完全不一样,Synctex 默认会把路径写入辅助文件,两边对不上就跳转失败。

我的做法是保持路径映射简单且可预测:统一用-v 宿主机论文根目录:/work,并保证latexmk在容器内的工作目录就是/work。经过这个路径对齐,Synctex 记录的是/work/main.tex,我在 VSCode 里查看 PDF 时的反向搜索,通过 LaTeX Workshop 的同步配置,一般都能正常工作。如果遇到反向定位没反应,优先检查是不是容器内路径和宿主机路径没有保持同一套映射规则。

5. 中文论文排版:字体、ctex 和参考文献的容器内处理

5.1 字体缺失不是宏包的问题

写中文论文,ctex宏包基本绕不开。很多人在容器里编译中文模板时遇到类似The font "FandolSong-Regular" cannot be found的错误,第一反应是去tlmgr installctex 相关宏包,但实际上宏包一直在,真正缺的是字体。ctex 宏包在 Linux 环境下会自动检测系统字体,找不到可用字体时才改用 Fandol 字体;如果 Fandol 字体文件也没被完整安装,就会直接报字库错误。

排查字体问题,我的固定流程是在容器里执行:

fc-list :lang=zh

这个命令会列出系统里所有支持中文的字体。如果输出为空或者只有很少几项,说明字体没装好。前面 Dockerfile 里安装fonts-noto-cjk后,这里的输出会明显变长。如果模板指定了“宋体”“黑体”这样的字体名,而系统里只有 Noto CJK,模板可能仍会报找不到SimSun,这时候要嘛把中文字体文件挂载进容器,要嘛在模板里把字体名替换成系统已有的中文字体。对于自用论文,我通常用 Noto Serif CJK SC 替代宋体,保真度尚可。

5.2 一个可运行的 ctex 论文模板片段

下面是个非常简单的 ctex 论文骨架,可以直接在容器里跑:

\documentclass[UTF8,scheme=plain]{ctexart} \usepackage{amsmath,amssymb} \usepackage{booktabs} \usepackage{graphicx} \usepackage{geometry} \geometry{margin=2.5cm} \title{基于容器化环境的论文排版测试} \author{Your Name} \date{\today} \begin{document} \maketitle \section{引言} 这是一段中文测试文本,可以正常出现中文排版效果,例如 $\alpha + \beta + \theta$。 \begin{table}[htbp] \centering \begin{tabular}{ccc} \toprule 编号 & 名称 & 说明 \\ \midrule 1 & 测试环境 & Docker + TeX Live \\ \bottomrule \end{tabular} \caption{测试表格} \end{table} \end{document}

编译命令我固定用latexmk -xelatex。为什么不用默认的pdflatex?因为 ctex 的中文排版底层设计就是围绕 XeLaTeX 或 LuaLaTeX 展开的,pdflatex配合传统 CJK 方案不仅字体配置繁琐,遇到中文标点压缩等问题也难以处理。所以在容器里,我通常直接把 latexmk 的默认编译器设为xelatex,可以通过在项目根目录放一个.latexmkrc来固化:

$pdf_mode = 5; $pdflatex = 'xelatex';

$pdf_mode = 5的意思是让 latexmk 知道我们要用 XeLaTeX 生成 PDF,这样它内部依赖跟踪才准确,增量编译的判断才不会出偏差。

5.3 参考文献:bibtex 和 biber 的编译顺序

参考文献是论文编译里最容易“不明原因失败”的环节。常见现象是日志里出现:

This is BibTeX, version 0.99d The top-level auxiliary file: main.aux

然后就没有下文了,PDF 里引用处显示问号。很多人以为 BibTeX 崩了,其实多半是编译顺序问题:第一次编译只生成.aux,BibTeX 根据.aux生成.bbl,之后还要再跑一两遍 LaTeX,引用标签才会最终解析。latexmk 默认会自动处理这个过程,所以理论上不应该出问题;但如果模板用了 biblatex + biber 的组合,而.latexmkrc里还强制指定了$bibtex = 'bibtex',那就会因为程序不匹配而失败。

我的建议是,在容器里用标准的\cite{}thebibliographybibtex方案时,保持默认配置即可;一旦切换到biblatex,请把.latexmkrc改成让 latexmk 自动检测后端工具,同时把文档里\usepackage[backend=biber]{biblatex}的 backend 显式写上。这样 latexmk 会在每次编译链条里自动调用 biber,不用手工介入。归根结底,参考文献编译顺序的问题,本质是“编译链工具是否匹配”的问题,容器化的好处就是你可以在镜像里同时装好 bibtex 和 biber,再通过配置决定走哪条链。

6. 用久了才发现的性能优化和隐藏坑

6.1 增量编译的正确姿势

容器化最容易被嫌弃的一点,是每次编译都像“重新冷启动一个系统”。实际上,只要你没有把论文目录里生成的辅助文件清掉,latexmk 会自动做增量编译,只有变化的章节才会重跑。所以推荐把.aux.out.log.toc.bbl等中间文件留在宿主机目录里,它们会通过挂载目录被容器读到。

我的一个建议是不要迷信latexmk -pvc这种连续监听模式。-pvc是为了让你保存后自动编译、PDF 自动刷新,在纯 Linux 环境下体验很好,但在 Docker Desktop 的 Windows/macOS 上,宿主机和虚拟机之间的文件事件通知经常不灵敏,保存文件后容器里面可能不能及时感知变化,反而给人一种“卡住了”的错觉。更稳妥的做法是,在 VSCode 里配一个保存后自动执行编译的任务,或者习惯性手动按一次编译。效果一样,但稳定很多。

6.2 辅助文件管理

随着编译次数变多,工作目录里的main.auxmain.logmain.outmain.tocmain.bblmain.blg会越来越多。这些文件虽然对增量编译有用,但放进 Git 里就是灾难。我的做法是在项目根目录的.gitignore里加如下内容:

*.aux *.log *.out *.toc *.bbl *.blg *.fls *.fdb_latexmk *.synctex.gz

只把main.pdf和你自己的.tex.bib文件纳入版本管理。这样做还有一个额外好处:换电脑 checkout 代码后第一次编译可能有点慢(因为全量跑一遍),但中间文件很快会重新生成,之后的增量编译又恢复了流畅。

如果你想清理中间文件但保留 PDF,可以在容器里运行:

latexmk -c

-c会删除所有辅助文件但保留 PDF。注意不要手滑写成-C,那会把 PDF 也一并删掉。

6.3 常见的报错速查

容器化 LaTeX 遇到的错误,大部分并不是“Docker 的问题”,而是环境细节问题。我把自己踩过的高频问题整理成了一张速查表:

报错或现象根因处理方式
virtualization support not detectedWindows 虚拟化未启用开启 WSL2/Hyper-V,检查 BIOS Intel VT-x/AMD-V
The font "SimSun" cannot be found系统无中文字体安装fonts-noto-cjk或挂载中文字体到/usr/local/share/fonts
I can't find file 'xxx.bbl'BibTeX 未执行或引用为空检查.bib路径,用 latexmk 重新完整编译
File 'algorithm.sty' not found宏包缺失tlmgr install algorithm2e,或改用 full 镜像
生成文件属主是 root容器用户和宿主机 UID 不一致compose 中设置user: "${UID}:${GID}"
latexmk -pvc不自动刷新文件事件通知不可靠改用保存后手动编译或任务触发

这里我想特别强调最后一行,因为很多人会忽略为什么容器里pvc模式下文件改了却不触发编译。原因在于 Docker Desktop 在 Windows/macOS 上运行在轻量虚拟机里,宿主机目录通过 bind mount 映射进去,虽然文件内容能实时看到,但文件的变更事件不一定能穿透过虚拟机层。这个问题属于平台特性,不是配置错误,理解这一点就不会白折腾。

7. 再进一步:把 Docker 编译放进论文 CI 流程

7.1 GitHub Actions 里直接用容器编译

环境一旦容器化,很自然的一个升级是把论文编译也交给 CI。尤其对于多作者合作的论文,每次 PR 都自动编译一份最新 PDF,能直接在提交流程里发现编译错误,这个收益比什么都大。

我在 GitHub 仓库里使用的 Actions 工作流大致长这样:

name: build-latex on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Compile LaTeX in Docker run: | docker run --rm \ -v "$PWD":/work \ -w /work \ ghcr.io/xu-cheng/texlive-full:latest \ latexmk -xelatex -interaction=nonstopmode -halt-on-error main.tex - name: Upload PDF artifact uses: actions/upload-artifact@v4 with: name: paper-pdf path: main.pdf

这里的核心就是把宿主机当前目录映射到容器里的/work,在容器里执行和本地完全一致的编译命令。-halt-on-error的作用是遇到第一个错误立即停止并返回非零退出码,这样 CI 会准确标记为失败,不会带着错误一路编下去。

7.2 GitLab CI 的思路

如果你在 GitLab 上管理论文,套路也差不多。可以给 job 直接指定一个 LaTeX 镜像作为执行环境:

build: stage: build image: ghcr.io/xu-cheng/texlive-full:latest script: - latexmk -xelatex -interaction=nonstopmode -halt-on-error main.tex artifacts: paths: - main.pdf

GitLab Runner 在 docker executor 下,每个 job 本身就跑在容器里,所以不需要再手动docker run,直接把latexmk当作常规命令执行即可。这种方式的体验很顺,唯一要注意的是镜像拉取时间会算进 job 耗时,首次会慢一些。

7.3 把版本钉住,别让环境悄悄漂移

整个方案里,我认为最容易被忽略的细节就是“版本漂移”。如果用latesttag,镜像可能在某一天悄悄更新,你明明什么都没改,但 CI 突然编译失败,或者 PDF 输出和上周不一样。这种事情我遇到过不止一次。

因此,建议在 docker-compose 和 CI 脚本里都钉住具体 tag,比如ghcr.io/xu-cheng/texlive-full:2025,而不是latest;等到 2026 年度版本发布后,再手动测试并切换过去。每年要更新 TeX Live 时,重新跑一次全套论文编译,确认无回归后再落到配置里。这种“显式声明版本”的做法虽然笨,但可以帮你省掉大量“为什么变了”的排查时间。

最后额外分享一个我这半年养成的习惯:一旦某个宏包或字体被证明可用,我会把它补充进 Dockerfile,重新构建镜像,而不是在容器内临时tlmgr install。这样无论何时何地拉镜像,环境都是完整且固定的。容器的作用本质上就是把“当初能用”变成“现在也能用,将来换台机器还能用”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询