简介面向 Jetson Nano 的 Docker 部署入门文档核心解决 nvidia-docker 深度学习环境搭建与 GPU 调用配置适合需要在 ARM 嵌入式设备上运行容器化训练或推理任务的开发者。文档共 1 个 docx 文件约 208KB内容沿安装、验证、容器创建三个阶段展开从 Docker 官方源配置、nvidia-container-runtime 安装到 daemon.json 设置 nvidia 默认运行时、deviceQuery 验证 GPU 是否可见再到编写 Dockerfile 构建 nano_env_img 镜像。针对 Jetson 特有的硬件直通问题文档专门列出了 /dev/nvhost-* 设备节点映射和 tegra 驱动目录挂载同时补充容器内安装 vim、sqlite3、gcc、libssl-dev 等依赖时可能遇到的 _ssl 缺失和 opencv 报错规避方法。这些细节能帮助读者避开常见坑直接搭建出可调用 GPU 的深度学习容器并在宿主机与容器之间完成运行时验证整体结构清晰命令与说明排布紧凑基本照着执行即可完成从宿主机配置到容器内 CUDA 环境验证的全过程。已有 157 人学习下载适合作为 Jetson Nano 上配置 Docker 与 nvidia-docker 的实操笔记如需迁移部署可进一步参考作者免费博客。1. Docker安装为什么明明装好了容器却总是起不来“Docker安装.docx”这份标题我第一眼看到时想到的是无数个深夜帮同事排查容器的场景明明照着教程一步一步装好了Dockerdocker --version也输出了版本号但docker run hello-world就是报错或者镜像拉不下来或者Windows上Docker Desktop点了启动按钮却一直转圈。这类问题不是个例而是每个刚接触Docker的开发者几乎都会撞上的墙。Docker安装这件事表面上是“下载一个安装包点下一步”实际上牵涉到内核虚拟化支持、WSL2、镜像源配置、用户组权限、daemon.json参数任何一个环节不对后面跑容器就会以各种玄学姿势翻车。这篇文章的目标读者很明确在Windows或Linux上装过Docker但没跑通或者跑通了但不知道怎么配置成生产可用的状态以及想通过Docker Compose把MySQL、Redis、GitLab这类服务一键拉起来的从业者。本文不抄官方文档只讲一线实操时会遇到的坑和能直接复用的命令。2. 装Docker前的三个前提虚拟化、内核和发行版选型2.1 怎么确认CPU虚拟化已经开启Virtualization Support 报错的处理很多人在Windows上装Docker Desktop装完点启动立刻弹出一条让人看不懂的报错Virtualization support not detected - Docker Desktop failed to start because virtualization support is disabled。这条报错的意思是Docker Desktop需要Hyper-V或者WSL2来运行Linux容器但你的CPU虚拟化功能没有在BIOS/UEFI里打开。在Windows上先打开任务管理器切到“性能”选项卡看右下角的“虚拟化”这一行是不是“已启用”。如果是“已禁用”需要重启电脑进BIOS设置。不同主板厂家的入口不一样常见的是开机时按Del、F2或F10在Advanced或CPU Configuration里找到Intel VT-x或AMD SVM设为Enabled。这里有个血泪经验很多人以为开了VT-x就万事大吉但实际上Windows功能里的“虚拟机平台”和“适用于Linux的Windows子系统”这两个可选组件也得勾上否则Docker Desktop依然起不来。用命令确认这两个组件是否启用可以在PowerShell管理员模式里执行dism.exe /online /get-featureinfo /featurename:Microsoft-Windows-Subsystem-Linux dism.exe /online /get-featureinfo /featurename:VirtualMachinePlatform第一行检查WSL组件状态第二行检查虚拟机平台状态。输出里的State信息如果是Disabled执行下面的命令启用再重启dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart命令里的/all参数是启用所有依赖项/norestart表示先不重启两条都执行完再手动重启一次。这个顺序不能反过来先装组件再开BIOS虚拟化重启后Docker Desktop才能正常识别环境。2.2 WSL2还是Hyper-V在Windows上跑Docker的两种后端怎么选Windows上装Docker Desktop安装器会让你选后端是WSL2还是Hyper-V。当前主流做法是选WSL2原因是启动更快、内存占用更小而且WSL2本身就是一个轻量级虚拟机跟Docker Desktop配合得比较顺。但这里有个常见误区WSL2不是装完就能用的需要在安装Docker Desktop之前先把WSL2的内核更新装好。在PowerShell里执行wsl --status如果提示“未安装适用于Linux的Windows子系统”说明WSL2内核没装。常见做法是去微软官网下载WSL2内核更新包或者用一条命令直接设置默认版本wsl --set-default-version 2这条命令的目的是把新安装的Linux发行版默认使用WSL2架构。设置完之后wsl --list --verbose查看发行版列表VERSION列显示2才是对的。如果显示1说明跑在WSL1上Docker Desktop虽然也能用但文件读写性能和兼容性会差一截尤其是挂载目录跑MySQL这类需要频繁IO的应用时速度差距明显。2.3 Linux发行版上的安装路径Ubuntu和CentOS的差异如果是Linux服务器上装Docker核心注意点就是别用发行版自带的老版本也别直接apt install docker装到系统包里。Ubuntu上官方维护的安装脚本是这么跑的curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh这段脚本会自动添加Docker官方源、安装docker-ce、docker-ce-cli和containerd.io三个核心包并启动服务。这里不推荐手动配源的另一个原因是不同系统版本的源地址格式不一样Ubuntu用deb [archamd64]CentOS/RHEL则用yum-config-manager手动配错过一次就得折腾半天。CentOS 7这类老发行版尤其要注意自带的内核版本是3.10即便Docker装上能跑遇到iptables相关报错的概率很高。工作里我一般会先升级内核到5.x以上或者干脆用Docker官方推荐的linux-server内核包。3. 跑通第一个容器的完整过程镜像加速、hello-world与权限边界3.1 配置镜像加速器之前先确认docker daemon能启动装完Docker之后先别急着拉镜像很多人卡在“docker pull没反应”这一步原因其实不是镜像源而是docker服务本身没起来。在Linux上用systemctl status docker看服务状态如果看到Active: failed先跑journalctl -u docker --no-pager | tail -50看日志。常见情况是daemon.json配置写错或者iptables规则冲突。用systemd管理的发行版执行sudo systemctl enable --now docker可以开机自启并立刻启动。需要注意--now参数在部分老版本systemd上不支持如果报错就拆成systemctl enable docker和systemctl start docker两条执行。服务起来之后执行docker info输出里的Server Version一行有值才说明客户端和daemon通信正常。如果这步报Cannot connect to the Docker daemon at unix:///var/run/docker.sock八成是权限问题先看下面的3.3节。3.2 设置registry-mirrors解决镜像下载慢的实用参数Docker从Docker Hub拉镜像在国内网络环境下的速度大家都懂几十MB的镜像等几分钟是常事甚至直接超时。解决办法是配置registry mirror。编辑/etc/docker/daemon.json没有就新建一个{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ], log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 }, storage-driver: overlay2 }这里解释几个关键字段。registry-mirrors是拉取镜像时的加速地址可以配多个Docker会按顺序尝试。log-driver和log-opts限制容器日志体积max-size10m表示单个日志文件超过10MB就轮转max-file3表示最多保留3个文件。不配置日志限制的话一个容器跑几个月日志文件占用几十GB的情况并不少见。storage-driver指定存储驱动overlay2是现代Docker推荐的选择在老内核上会报错建议先确认内核版本。修改完daemon.json之后sudo systemctl restart docker。然后执行docker info看Registry Mirrors字段是否是刚才配置的地址。如果镜像源列表没变大概率是daemon.json的JSON格式有问题用python3 -m json.tool /etc/docker/daemon.json检查一下语法。3.3 docker run hello-world背后的权限模型docker用户组和sudo跑docker run hello-world的时候如果遇到permission denied while trying to connect to the Docker daemon socket别急着重装这跟Docker本身没关系。Docker daemon默认监听在/var/run/docker.sock这个Unix socket上而该socket属于root用户和docker用户组。当前用户不在docker组里客户端自然连不上。把当前用户加入docker组并刷新会话权限sudo usermod -aG docker $USER newgrp dockerusermod -aG docker $USER是追加-a用户到docker组-G。newgrp docker是让当前终端会话立即生效不用退出重登。这里有个安全边界要注意docker组里的用户等价于root权限因为可以直接docker run -v /:/host挂载宿主机根目录进容器操作文件。生产服务器上给用户加docker组之前先想清楚这条权限边界是Docker安装阶段很多人忽略的隐患。3.4 最小可运行验证docker run、docker ps和镜像生命周期确认权限没问题拉镜像跑第一个容器docker run --name first-test hello-world这条命令的执行流程是先检查本地有没有hello-world镜像没有就去配置的镜像源拉取就是3.2节registry-mirrors生效的地方然后创建容器并执行默认的入口程序。--name参数指定容器名不指定的话Docker会随机生成一个“clever_golick”之类的名字难记且不便管理。docker ps -a查看容器状态hello-world这类一次性容器执行完就退出了docker ps不带-a看不到这是正常的。docker rm first-test删掉这个测试容器这样宿主机上不会遗留无用容器。如果后面要继续跑MySQL、Redis这类服务一个镜像可以反复创建多个容器每个容器是独立的运行实例镜像本身不会因为容器删除而消失。4. 装完Docker立刻部署一套实用环境MySQL 8.0加Redis主从的Compose写法4.1 为什么用Docker Compose而不是一串docker run跑单容器用docker run没问题但一旦涉及多个服务比如MySQL加Redis或者GitLab加PostgreSQL每条命令都要带端口映射、数据卷、环境变量、重启策略一个字符错就全部重来。Docker Compose的价值是把这些配置写成声明式YAML一条docker compose up -d拉起全部服务停止也是docker compose down一键清理。工作里的交付环境我基本只写Compose文件不推荐手工执行一长串docker run。4.2 一份能直接用的MySQL 8.0 Compose配置在项目目录创建docker-compose.ymlversion: 3.8 services: mysql: image: mysql:8.0 container_name: mysql8 restart: always environment: MYSQL_ROOT_PASSWORD: Root123456 MYSQL_DATABASE: app_db MYSQL_USER: app_user MYSQL_PASSWORD: App123456 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d - ./mysql/init:/docker-entrypoint-initdb.d command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci networks: - app_net redis: image: redis:7.2 container_name: redis7 restart: always ports: - 6379:6379 volumes: - ./redis/data:/data - ./redis/conf/redis.conf:/etc/redis/redis.conf command: [redis-server, /etc/redis/redis.conf] networks: - app_net networks: app_net: driver: bridgeMYSQL_ROOT_PASSWORD是root的密码MYSQL_USER和MYSQL_PASSWORD是启动时自动创建的普通用户MYSQL_DATABASE则是随之创建的默认库。volumes里的三个挂载点分别解决数据持久化、自定义配置和初始化脚本三个问题/var/lib/mysql是MySQL的数据目录不挂载的话容器删了数据就全没了/docker-entrypoint-initdb.d目录下的.sql脚本会在MySQL首次初始化时按文件名顺序执行适合灌入建表语句。注意ports写的是3306:3306左边是宿主机端口右边是容器内端口。宿主机3306如果已经被本机MySQL占用把左边改成3307即可。为什么不建议容器内也改端口容器内的3306是MySQL默认监听端口改了之后客户端连接串也得跟着改徒增复杂度。4.3 Redis主从复制在Docker环境下的具体配置和三步操作Redis主从是典型适合用Compose管理的场景。在4.2节的Compose文件里扩展一个redis-slave服务redis-slave: image: redis:7.2 container_name: redis-slave depends_on: - redis ports: - 6380:6379 volumes: - ./redis-slave/conf/redis.conf:/etc/redis/redis.conf command: [redis-server, /etc/redis/redis.conf, --slaveof, redis, 6379] networks: - app_net从节点的配置核心是--slaveof参数第一个值redis是主节点的服务名不是IP。Docker Compose会为同一网络内的服务自动做DNS解析所以服务名可以直接当主机名用这是跨容器通信的关键点。depends_on确保主节点先启动但从节点不会等主节点完全就绪如果主节点初始化慢从节点可能报连接失败Redis会按配置自动重试。启动这一整套环境的命令docker compose up -d docker compose psup -d是后台启动ps查看运行状态。如果修改了YAML配置执行docker compose down docker compose up -d才会用新配置重新创建容器直接up -d不会重建已存在的容器。4.4 拉不起GitLab、MediaMTX这类重服务时先查这两个资源限制有人用Docker部署GitLab社区版或MediaMTX这类容器时发现启动后立刻退出或者运行一段时间被自动杀掉。工作里遇到最多的两个原因一是宿主机内存不够被OOM Killer杀掉二是Compose没限制容器资源导致无限抢占。前者用docker inspect container | grep -A5 OOMKilled查看输出里OOMKilled: true就说明是内存耗尽。后者解法是在Compose里显式声明资源限额deploy: resources: limits: memory: 4g cpus: 2.0memory: 4g把容器内存上限设为4GB超出会被内核强制回收cpus: 2.0限制最多占用2个CPU核心。这两个参数用在GitLab这类吃内存的容器上能避免它在初始化时把宿主机内存吃光导致整机卡死。但注意deploy.resources.limits在Compose非Swarm模式下只对容器运行时限制生效docker compose直接支持不需要额外配置。5. Docker安装与启动全链路避坑从daemon启动失败到容器网络不通的排查顺序5.1 现象Docker Desktop一直卡在“Docker Desktop is starting”Windows上装完Docker Desktop点击图标后桌面右下角鲸鱼图标转圈半天之后提示启动失败。排查顺序是先打开PowerShell执行wsl --status如果输出提示WSL内核太旧或没有已安装的发行版进入Microsoft Store安装一个Ubuntu发行版再设置默认版本为WSL2。如果WSL正常再看Windows功能里的“虚拟机平台”是否启用这几个组件缺一不可。原因通常就两类要么WSL2没有真正启用要么Docker Desktop的配置缓存坏了。对于后者工作里见过不少人直接重装Docker Desktop但问题依旧因为配置文件残留在%AppData%\Docker目录下。解决手段是退出Docker Desktop删除%AppData%\Docker和%LocalAppData%\Docker两个目录再重启软件让它重新初始化配置。注意这个操作会丢掉已有的Docker Desktop设置所以先确认没有依赖该配置的容器卷再删。5.2 现象systemctl start docker报“Failed to start Docker Application Container Engine”Linux上执行启动命令时报这个错误journalctl -u docker里最常见的三行错误是failed to start daemon: error initializing graphdriver: lstat overlay2Error starting daemon: open /var/lib/docker/tmp: permission deniedunable to configure the Docker daemon with network: invalid CIDR address第一种是内核模块或存储驱动问题检查lsmod | grep overlay和modprobe overlay加载模块第二种是/var/lib/docker目录被改过属主或SELinux策略限制先恢复属主sudo chown -R root:root /var/lib/docker第三种是之前手动改过daemon.json里的bip配置写入了非法网段把这一行删除即可。这里最容易被忽略的是daemon.json的格式错误。Docker 19之后的版本对配置校验严格一个多余的逗号就导致daemon拒绝启动。先备份原配置再修改改完用dockerd --validate验证配置文件合法性。这条命令不会启动服务只做解析是排查daemon启动失败的后悔药。5.3 现象容器启动了但宿主机访问不到端口按4.2节的Compose启动MySQL之后在宿主机执行mysql -h127.0.0.1 -P3306 -uroot -p却连接不上或者报连接被拒。先一条条排查docker ps docker port mysql8docker port mysql8会输出容器的端口映射关系。如果输出是3306/tcp - 0.0.0.0:3306说明映射正常问题在MySQL容器内部如果输出为空说明端口根本没映射对检查Compose文件的ports段是否写错。容器内部无法访问时用docker exec -it mysql8 bash进容器执行mysql -uroot -p验证MySQL是否正常监听。另外有一种网络层的坑容器能跑、映射也对但其他机器就是访问不了宿主机端口。大概率是防火墙拦了。sudo ufw status sudo ufw allow 3306/tcpUbuntu上ufw默认策略是拒绝入站不放开端口就只能在宿主机本机访问。如果前面执行过newgrp docker记得确认用户组权限不然连docker port都会报权限错误。5.4 现象docker compose up后镜像能拉但没有日志输出Compose里服务启动后docker compose logs mysql没有任何输出或者卡在Starting mysql8 ... done之后不继续。这种情况先执行docker inspect mysql8 --format {{.State.Status}}确认容器实际状态是running还是exited。如果Exited查看容器退出码docker logs mysql8 --tail 20。退出码137表示容器被OOM Kill退出码1通常是应用自身初始化失败。MySQL容器初始化失败最常见的原因是挂载目录权限不对容器内mysql用户对/var/lib/mysql没有写权限执行chmod -R 777 ./mysql/data能临时解决但更稳妥的做法是chown -R 1000:1000 ./mysql/data因为MySQL 8.0官方镜像里运行用户UID是1000。6. 给Docker安装补上最后一环镜像清理瘦身与自建本地镜像仓库容器环境跑久了之后磁盘占用越来越大docker system df能看到镜像、容器、卷、构建缓存的占用分布。工作里的经验是定期执行两件事一是清理悬空镜像和构建缓存二是在内网自建一个registry做镜像中转避免每次部署都去公网拉取。docker system prune -af --volumesdocker system prune是Docker官方自带的清理命令-a清除所有未被容器使用的镜像而不仅仅是悬空镜像-f跳过确认提示--volumes连带删除未被引用的匿名卷。但这三个参数合在一起要小心--volumes会删掉没有被容器使用的命名卷吗不会命名卷有名字删除前需要显式docker volume rm匿名卷才会被prune连带清掉。因此生产环境里执行这条命令之前先确认不需要保留的容器数据卷已经挂载到宿主机目录否则数据丢了没有后悔药。自建registry的方式也很直接用官方镜像跑一个docker run -d -p 5000:5000 --name registry --restartalways \ -v /opt/registry-data:/var/lib/registry registry:2-p 5000:5000暴露5000端口-v /opt/registry-data:/var/lib/registry做数据持久化挂载。之后给本机镜像打上地址标签并推送docker tag mysql:8.0 localhost:5000/mysql:8.0 docker push localhost:5000/mysql:8.0这里的关键点是推送自建仓库时镜像名必须带仓库地址前缀localhost:5000否则默认推送到Docker Hub。新机器上拉取时Docker会把localhost:5000/mysql:8.0作为完整镜像名解析不需要额外配置insecure-registries。但如果仓库跑在另一台机器上比如192.168.1.10:5000客户端需要在/etc/docker/daemon.json里加insecure-registries: [192.168.1.10:5000]否则Docker默认走HTTPS会握手失败。这个配置细节是内网Docker部署里最常见的踩坑点之一。部署新机器的时候先用docker pull localhost:5000/mysql:8.0把镜像拉到本地之后docker run的启动速度会快很多不会受外网波动影响。这是我在离线环境交付服务时屡试不爽的一套流程。装Docker这件事装好只是一个起点把镜像管理、资源限制和权限边界都理顺后面的业务容器才真正省心。希望帮到你。本文还有配套的精品资源点击获取