资讯动态

Ubuntu深度学习Docker环境搭建:从镜像选型到GPU透传最佳实践

发布时间:2026/9/29 15:58:21 来源:尧图企业网站定制
1. 从环境地狱说起为什么深度学习项目比任何软件都更需要Docker我的Ubuntu桌面系统上原本装的是Anaconda一开始一切都好直到有一天我把PyTorch、TensorFlow、OpenCV三个包换了三个版本终于把系统Python环境搞成了一锅粥。PyTorch要的CUDA版本和TensorFlow冲突OpenCV的依赖又把系统自带的库顶掉了最后连pip install都开始报错。从那一刻起我把所有深度学习环境全部迁进Docker容器宿主机上只剩一个干净的Docker Engine。今天这篇就按我自己的操作路径带你在Ubuntu里从零建立一个深度学习Docker环境。这篇文章的受众很明确刚开始接触Linux的深度学习入门者从Windows换到Ubuntu还不习惯命令行操作的迁移用户以及已经受够了宿主机Python环境反复被搞崩、想找个一劳永逸方案的老手。不管你属于哪一类跟着这篇文章走完一遍你会有自己的第一个深度学习容器以后换机器、换项目、给朋友复制环境都只需要一条命令。先说结论深度学习项目比其他任何软件都更适合用Docker原因不复杂深度学习环境里有两条依赖链是最难处理的。一条是Python包依赖链pip install torch会拖进来几十个底层库它们互相之间版本锁得很死另一条是CUDA依赖链nvidia-driver、CUDA Toolkit、cuDNN、PyTorch四者的版本必须严格匹配差一个版本可能直接黑屏或者编译报错。这两条链交叠在一起就是传说中的“环境地狱”。Docker做的事情很暴力也很有效把整个依赖环境连同操作系统层一起打包进镜像宿主机上什么都不装需要什么环境就拉什么镜像。我实测下来一套带CUDA的PyTorch镜像从拉取到跑通nvidia-smi大概只需要十分钟。如果用传统方式从零装通常要做好折腾一整天的心理准备还不一定成功。有人会问虚拟机不也能隔离环境吗VMware里跑Ubuntu当然可以但虚拟机的GPU直通实现麻烦性能损耗也明显——虚拟机里跑深度学习训练速度掉个20%到30%是常有的事。Docker容器不是虚拟机它共享宿主机内核GPU透传是直接通过NVIDIA Container Toolkit把显卡设备映射进容器性能和裸机几乎一致。这也是我最终选择Docker而不是虚拟机的根本原因。1.1 深度学习环境的典型痛点版本锁死的威力我举个真实的例子。你有两个项目项目A用的是PyTorch 2.1配CUDA 12.1项目B用的是PyTorch 1.13配CUDA 11.7。在宿主机上这两个项目要共存你需要conda维护两套环境每套环境几GB切换来切换去还经常出现conda把底层库更新了导致另一个环境莫名其妙崩掉的情况。用Docker就简单得近乎粗暴项目A对应一个容器项目B对应另一个容器两个容器互不知道对方的存在各自运行在各自的镜像层里。你把项目A的镜像打上tag存起来半年后想重新跑docker run一拉环境跟半年前一模一样。这种可复现性是深度学习实验最需要的品质。1.2 为什么虚拟机不行容器可以虚拟机的思路是在宿主机操作系统之上再虚拟出一套完整的硬件然后装一个完整的操作系统。这意味着每台虚拟机都要吃掉几个GB的内存和几十GB的磁盘启动也要几十秒。而Docker容器直接共享宿主机的Linux内核只是通过namespace和cgroup做了隔离和资源限制启动一个容器比启动一个虚拟机快一个数量级。更关键的是GPU。虚拟机的GPU直通不仅需要硬件支持配置起来非常折腾而Docker配合NVIDIA官方提供的Container Toolkit只需要在docker run的时候加一个--gpus all参数GPU就自动映射进容器了。本质上就是让容器里的CUDA runtime通过宿主机的NVIDIA驱动直接访问显卡硬件中间几乎没有性能损耗。这也是NVIDIA官方推荐的深度学习部署方式。2. 装Docker前先把Ubuntu和硬件理顺在动手装Docker之前有几步准备工作值得先花十分钟确认清楚。很多人在Docker安装上卡住其实不是Docker本身装不上而是前置条件没具备。尤其是从Windows迁移过来的用户最容易在虚拟化支持这里栽跟头。2.1 确认硬件虚拟化已开启如果你打算在本机跑Ubuntu无论是双系统还是虚拟机都需要确认CPU的虚拟化功能已经打开。Intel的CPU叫VT-xAMD的叫AMD-V在BIOS里通常找Intel Virtualization Technology或SVM Mode选项把它设为Enabled。怎么看当前系统是否已经开启可以在终端跑这个命令grep -E --colorauto vmx|svm /proc/cpuinfo如果有vmx或svm字样输出说明虚拟化已开启。老机器尤其要注意这一步很多2015年以前的笔记本电脑出厂默认关闭虚拟化跑Docker Desktop会直接报virtualization support not detected但实际上关掉Docker Desktop、改用Docker Engine方式安装的话对虚拟化的依赖会小很多——因为Docker Engine直接在Linux内核上跑不需要嵌套虚拟化。2.2 确认Ubuntu版本和网络先看一下你的系统版本lsb_release -a这篇文章以Ubuntu 24.04 LTS为例操作在20.04到24.04之间都通用。如果你用的是Ubuntu桌面版网络设置一般在安装系统时就已经搞定有线连接不需要额外配置只要ping得通就行。装Docker之前我习惯先把APT源换成国内镜像源。这一步不是必须的但它直接影响后面的安装速度。清华、阿里、中科大都有Ubuntu镜像源选一个改/etc/apt/sources.list就行。我在多台机器上实测换源之后apt update的速度能从几分钟降到十几秒。换完源顺手执行一下更新sudo apt update sudo apt upgrade -y2.3 清理旧版本Docker如果你之前装过Docker先清理干净再重装。旧版Dockerdocker.io、docker-engine和新版Docker Engine如果混在一起容易出现装完跑不起来的问题sudo apt remove docker docker-engine docker.io containerd runc这一步是可选的如果你确定机器上从来没装过Docker跳过即可。2.4 安装Docker Engine这里有一个重要的选择装Docker Desktop还是Docker Engine。我的建议是在纯Linux环境下直接用Docker Engine不要装Docker Desktop。Docker Desktop是从macOS和Windows那套思维迁移过来的产品在Linux上反而多了一层VM组件出问题的时候排查链路更复杂。你要的是一个能跑容器的引擎不是另一个GUI。Docker官方提供了一键安装脚本简单粗暴curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh安装完成后把当前用户加入docker组避免每次都用sudosudo usermod -aG docker $USER newgrp docker这里解释一下为什么要这么做。Docker守护进程是以root身份运行的而普通用户调用docker命令需要和守护进程通信默认通过一个Unix socket。这个socket的权限属于root普通用户没权限访问所以才会出现docker: permission denied这类报错。把用户加入docker组本质上是让这个用户获得访问socket的权限。要提醒的是加入docker组等于给了该用户等同于root的操作权限因为能操作Docker就能挂载宿主机目录所以只建议在你自己信任的机器上这样做。2.5 验证Docker装好了验证方式不用我说正是指令docker run --rm hello-world这条命令会从仓库拉取一个极小的测试镜像然后运行一个打印hello的小程序。如果你能看到Hello from Docker!字样说明Docker守护进程正常工作网络能连通镜像仓库容器运行链路也没问题。如果卡在这一步最常见的两个原因一个是网络问题镜像拉不下来后面我会专门讲怎么解决另一个是守护进程没起来可以查看一下Docker服务状态sudo systemctl status docker如果是服务没起来先启动sudo systemctl start docker sudo systemctl enable dockerenable是让Docker开机自启这一步可以顺手做了省得每次重启机器都要手动起服务。3. 镜像选型别从零装CUDA用官方深度学习镜像这一步是整个流程里最关键的决策点。很多人习惯用ubuntu:22.04或者python:3.10这种基础镜像然后进容器里一步步装CUDA、装PyTorch我理解这种思路——觉得基础镜像干净可控。但实际操作下来这条路非常容易踩坑因为CUDA Toolkit和显卡驱动的版本匹配问题在容器里手动装和在宿主机手动装一样麻烦。3.1 三种路线对比Ubuntu裸镜像、Python镜像、NGC镜像我整理了一张表对比了最常见的三种方案路线操作成本踩坑概率推荐程度ubuntu:22.04 手动装CUDA/PyTorch高需要自装驱动、CUDA、cuDNN高版本匹配极其容易出错不推荐新手尝试python:3.10 自己装CUDA库中高Python有了但CUDA还是要自己来中至少包管理简单了有一定经验可用NGCPyTorch镜像低拉下来就能跑低NVIDIA官方测试过的组合最推荐3.2 NGCPyTorch镜像里到底预装了什么NVIDIA官方维护了一个镜像仓库叫NGCNVIDIA GPU Cloud里面有专门为深度学习和科学计算准备的镜像。我用的比较多的是nvcr.io/nvidia/pytorch系列镜像以24.02-py3这个tag为例里面预装了完整的CUDA Toolkit和匹配的cuDNNPyTorch及其配套的torchvision、tensorboardJupyter Notebook和Jupyter Lab一些常用的Python科学计算库也就是说docker pull拉下来之后你不需要再装CUDA、不需要再装PyTorch直接就能跑训练脚本。这个“开箱即用”的体验是所有自装路线都给不了的。当然代价是镜像体积大一般在15GB到25GB之间这个后面说怎么处理。3.3 硬件和网络要求别忽略的硬指标镜像虽然好用但你的机器得扛得住。基于我的实际经验最低配置建议是内存8GB起步16GB会更从容。NGC镜像里的PyTorch跑起来本身占用就不小再开Jupyter和浏览器8GB会比较紧。磁盘至少准备50GB空闲空间。一个NGC镜像20GB再加上你挂载的工作目录、模型文件、日志50GB很快就会用到。显卡NVIDIA显卡且驱动已经装好。没有NVIDIA显卡的话纯CPU版本的PyTorch也能跑但需要换一个不带CUDA依赖的镜像容器的启动参数也不能加--gpus all。如果你的机器没有NVIDIA显卡我建议退而求其次用pytorch/pytorch:2.1.0-cpu这种官方CPU镜像至少环境一致性还是有保障的。但如果你手上有NVIDIA GPU那NGC镜像就是最省事的选择。3.4 Docker拉取NGC镜像之前的网络准备NGC镜像的仓库地址是nvcr.io网络没问题的话直接拉。如果拉取速度慢或者超时可以先在/etc/docker/daemon.json里配置镜像加速器然后重启Dockersudo systemctl restart docker配置镜像加速器的具体内容我会在后面的排查章节里展开这里先知道有这么回事不影响往下走。4. 第一次创建深度学习容器run命令拆解与验收镜像选好之后接下来就是把这个镜像变成一个真正能干活、能存数据的容器。我用的镜像是nvcr.io/nvidia/pytorch:24.02-py3你完全可以沿用同样的命令只要把镜像名换成你自己选的那一个。4.1 先拉镜像再启动容器拉镜像这个动作很简单docker pull nvcr.io/nvidia/pytorch:24.02-py3如果你用的是CPU方案把镜像名换掉就行。拉取完成后确认一下镜像确实在本地docker images4.2 docker run命令的每一个参数下面这行命令是我个人平时用的标准启动命令我逐段拆开解释docker run -d \ --name dl-dev \ -p 8888:8888 \ -p 6006:6006 \ -v /home/$USER/workspace:/workspace \ --gpus all \ nvcr.io/nvidia/pytorch:24.02-py3-d以detached模式运行也就是容器在后台跑不会占住当前终端。如果你想要容器日志实时输出可以把-d换成-it但日常开发我用后台模式比较多。--name dl-dev给容器起个名字叫dl-dev以后操作就不需要记容器ID。-p 8888:8888宿主机8888端口映射到容器8888端口这个留给Jupyter。-p 6006:6006宿主机6006端口映射到容器6006端口这个留给TensorBoard。-v /home/$USER/workspace:/workspace把宿主机的workspace目录挂载到容器的/workspace目录。这是数据持久化的核心你的代码、模型、数据放在宿主机的workspace下容器里都能看到容器删了数据也不会丢。--gpus all把宿主机上所有GPU都映射进容器。这是NVIDIA Container Toolkit生效的关键参数没有它容器里跑nvidia-smi会直接报错。4.3 验收GPU到底有没有进容器容器启动之后先看它是不是还活着docker ps然后进容器里执行nvidia-smidocker exec -it dl-dev nvidia-smi如果你看到了熟悉的显卡信息表格说明GPU成功的映射进了容器。这一步非常重要很多人的容器能启动、Python能跑但torch.cuda.is_available()返回False就是GPU没正确映射进去。顺便验证一下PyTorch能不能用上GPUdocker exec -it dl-dev python -c import torch; print(torch.cuda.is_available(), torch.cuda.device_count())如果输出的是True 1那你的深度学习Docker环境基本就算建成了。4.4 数据持久化避免丢掉所有成果的教训我第一次用Docker跑深度学习的时候没有挂载目录直接把训练脚本拷进容器里跑。训练了十几个小时的中途我随手一个CtrlD退出了容器训练结果全没了。因为容器一旦退出所有写进容器层的数据都会保留在容器里但如果你没注意或者容器的名字搞错了很容易在处理过程中把容器删掉那训练数据就真的没了。所以我想强调任何容器内的非系统数据都不应该放在容器可写层必须放在挂载卷里。上面的命令已经把/home/$USER/workspace挂进了/workspace以后开发约定就是在宿主机~/workspace下用编辑器写代码在容器里以/workspace路径运行两边看到的是同一个文件夹这个体验很像在本地开发。从命令行到容器内部这一套流程跑通之后你已经有了一个能跑深度学习任务的基础环境。但日常开发不止是跑一个脚本还需要写代码、看训练曲线、偶尔调试这些都需要在容器里搭建更完整的开发配套接下来我们把这一步补齐。5. 容器内日常开发Jupyter、SSH、环境包的搭配容器跑起来之后真正进入开发阶段你会发现直接对着docker exec -it bash操作还可以但写代码、看TensorBoard还是要配合更顺手的工具。我一般会在NGC镜像上配置三件套Jupyter Lab、端口映射好的TensorBoard、以及一套我自己常用的Python包补充列表。5.1 启动Jupyter并访问NGC镜像自带Jupyter所以不需要额外安装。第一次启动的时候可能需要指定一些参数因为镜像默认是root用户运行docker exec -it dl-dev jupyter lab --allow-root --ip 0.0.0.0 --port 8888 --no-browser--ip 0.0.0.0是让Jupyter监听容器内的所有网络接口这样外部才能访问到--no-browser是因为容器里没有浏览器可开。启动之后它会打印一串token在宿主机浏览器打开http://localhost:8888输入token就能进到Jupyter界面。后面不用每次都手动敲这一长串命令可以直接在Docker run命令里让容器启动时就运行Jupyter做一个自启动的配置。不过那个配置方式写进容器镜像里比较干净后来我其实直接改用VS Code的Docker插件来编辑和执行代码了Jupyter反而用得少。这点看你个人习惯Jupyter适合数据探索和可视化VS Code适合工程化开发两者不冲突。5.2 TensorBoard的访问路径TensorBoard是深度学习中绕不开的可视化工具。启动其实很简单在容器里指定log目录docker exec -it dl-dev tensorboard --logdir /workspace/logs --port 6006因为前面启动容器的时候已经做了-p 6006:6006的端口映射所以直接在宿主机浏览器打开http://localhost:6006就能看到。这里的关键是日志要写到挂载目录/workspace/logs下面TensorBoard才能正确找到文件而且当你把容器删掉重建时日志还在宿主机的workspace里训练历史不会丢。5.3 SSH连接什么时候真正需要在Linux服务器上做深度学习很多人习惯直接用SSH连进去开发。一个常见的疑问是容器里有没有必要跑一个SSH服务我的看法是大部分情况下没必要。你已经可以通过docker exec -it dl-dev bash进容器这是Docker原生提供的交互方式比SSH更快更安全。SSH走进容器是在某些特定场景下才真正有用的比如你要配合一些IDE的远程开发插件或者容器跑在远端服务器上而你想做端口转发。如果是自家机器本地开发完全不需要额外在容器里开SSH服务。直接在宿主机上用VS Code安装Docker扩展选中dl-dev容器点“Open Attached to Container”就能无缝在容器里写代码、跑命令体验跟本地几乎没有区别。这个是Docker配合深度学习开发最舒服的一种工作流。5.4 容器里额外装Python包的正确姿势NGC镜像虽然是开箱即用但深度学习项目总会有一些它没预装的包比如transformers、timm、opencv-python、wandb等。这时候直接进容器用pip安装docker exec -it dl-dev pip install transformers timm wandb这个是没问题的。但请注意这样安装的包是写进容器的可写层的容器如果被删掉这些包会丢失。解决办法有两个看你的使用场景把安装命令写进一个requirements.txt放在挂载目录下每次重建容器后执行一遍pip install -r /workspace/requirements.txt即可。如果你确定这次安装的包以后每次都会用就把容器提交成一个新镜像下次直接用新镜像启动docker commit dl-dev pytorch:myenvdocker commit会把当前容器的状态打包成一个新镜像。这个方法操作最快适合你花了一个小时装好各种包之后给它拍个快照以后用的每个新容器都基于这个快照。6. 排查实录网络、权限、GPU三个高频坑用Docker跑深度学习前前后后总会碰到一些问题。我把这个过程中多次踩到、也经常看到群友问的三个高频问题放在这里分享排查思路不看结论而是看链路。6.1 docker pull很慢或者卡住先分锅再处理网络问题绝对是遇到的第一个坑。docker pull慢不代表网速不行也不代表Docker有问题。可能的原因有三个镜像仓库的连接速度、本机网络环境、DNS解析。大多数时候配置镜像加速器就能解决。配置方法是在/etc/docker/daemon.json里写入加速器地址然后重启Docker{ registry-mirrors: [https://docker.m.daocloud.io] }sudo systemctl restart docker重启之后重新docker pull速度通常会有明显改善。如果换了镜像加速器之后依然很慢再检查DNScat /etc/resolv.conf如果DNS解析本身有问题解决的思路是换成公共DNS比如在/etc/systemd/resolved.conf里指定DNS223.5.5.5这类地址然后重启systemd-resolved服务。这个操作在Ubuntu 24.04上稍微要注意一下新版系统用了netplan管理网络修改方式略有不同具体路径是/etc/netplan/下的yaml文件。但这一步属于系统网络的底层问题我建议不要一卡就先动这个先排查加速器和镜像源。验证网络是否通畅有一个最直升飞机的方法在宿主机上直接docker pull一个体积很小的测试镜像比如hello-world如果能秒下说明网络链路没问题如果连hello-world都卡那就要按上面的思路慢慢排查。6.2 permission denieddocker组你加了吗docker: Got permission denied while trying to connect to the Docker daemon socket这个报错几乎每个装Docker的人都会遇到。原因前面说过普通用户没有访问docker socket的权限。解决办法有两种sudo usermod -aG docker $USER newgrp docker执行完这两条命令之后新开的终端里应该就可以直接用docker命令不用加sudo。要注意的是newgrp docker只对当前终端生效如果想彻底生效最简单是重新登录系统或者重启一次。还有一个小坑如果你在无sudo权限的远程服务器上想办法绕过这个限制那是行不通的。加入docker组本身也需要root权限而且安全上也不推荐在生产服务器上这么做。如果是生产环境规范做法是配置dockerd的远程访问并做好TLS证书或者就在命令前面加sudo操作。6.3 容器里nvidia-smi有报错多半往前查容器里跑nvidia-smi报错一般有几种表现。一种是nvidia-smi: command not found说明你的镜像里压根没装NVIDIA驱动工具。NGC镜像不会出现这个问题但如果你是从基础镜像自己搭的环境就要确认nvidia-utils是否装了。另一种是报错couldnt communicate with NVIDIA driver这说明驱动层出了问题但要注意不是容器里缺驱动而是宿主机上的NVIDIA驱动状态不对劲。排查顺序是这样不要反过来先在宿主机上跑nvidia-smi如果宿主机都报错说明显卡驱动本身有问题先解决宿主机驱动。如果宿主机正常设置容器启动参数时确认加了--gpus all如果没有重新用带这个参数的命令启动容器。如果还是不行检查NVIDIA Container Toolkit有没有装。Docker虽然能正常跑但如果没有这个工具--gpus参数是不被识别的。检查一下docker info | grep -i nvidia如果看到Runtimes: nvidia说明runtime已经注册成功可以跑GPU容器如果没有参考安装NVIDIA Container Toolkit的命令重新装一遍。一个经验是把宿主机驱动和容器环境的关系理清能节省大量排查时间。宿主机只负责提供驱动容器里跑的CUDA库跟宿主机的驱动版本是独立的两层这也是Docker深度学习环境最大的优势之一。比如你的宿主机驱动是470系列老卡常见的版本NGC镜像里的CUDA版本虽然比较新但驱动层兼容照样能跑只要驱动版本支持这个CUDA的运行。6.4 容器退出了就进不去先查ps再查名字很多人第一次用Docker的时候会怀疑是不是自己把容器搞坏了因为输入docker exec -it dl-dev bash之后提示容器不存在。最常见的原因有两个容器已经退出了docker exec是针对运行中的容器的退了就进不去。先看状态docker ps -a-a是all的意思所有容器都会列出来包括已退出的。如果看到STATUS列是Exited (0)需要先启动它docker start dl-dev docker exec -it dl-dev bashdoccker run每次执行都会新建一个容器如果你以为“运行同一个镜像就是进入同一个容器”实际上你已经在前一次退出之后这次docker run又建了一个新的。所以后面的容器名字和你预期的不一致操作的时候要注意区分docker run新建容器和docker start启动已有的、停止的容器以及docker exec进入在运行的容器。这两者的区别一次搞明白后面就不会慌了。最后关于日常使用的几个小建议这套深度学习Docker环境我用了两年多说三个我踩过坑之后养成的习惯你可以直接用。第一个每次训练前把docker run那长串命令保存成一个脚本文件比如start_dl_dev.sh放在工作目录里。以后不只是你你的同事、朋友想复现你的环境直接执行这个脚本就能得到一个完全一样的容器。我在多台机器上都是这么做的。第二个容器里的卡和内存是有限的。多GPU的机器在启动容器时如果不想一个容器占满所有卡可以指定GPUdocker run --gpus device0 ...这样这个容器只会看到编号为0的那张显卡其他卡留给别的任务。多容器并行开发的时候这个技巧非常实用。第三个磁盘空间的教训。每拉一个NGC镜像就是20GB每docker commit一次又多占几个GB。我建议日常做好清理docker system prune这条命令会清理停止的容器、没有使用的网络、悬空镜像是释放磁盘空间最有效的命令。初期我不舍得清理结果500GB的磁盘两周就见红了。环境搭好之后你的整个深度学习工作流会发生一个明显的变化你不再关心“这个包要怎么装”而是关心代码本身。镜像即环境容器即沙盒把折腾环境的时间节约出来用在模型上这才是值得的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑