Sealessland logo Sealessland
Tutorial

Docker 入门:概念、用法与 Jetson 注意事项

面向完全初学者的 Docker 入门文档,介绍 Docker 的核心概念、基本用法、Dockerfile 与 Compose 的作用,以及 Jetson 平台上的常见注意事项。

DockerJetsonContainer

Docker 入门:概念、用法与 Jetson 注意事项

初次接触 Docker 时,常见疑问通常集中在以下几件事上:

  • Docker 到底是什么?
  • 它和虚拟机有什么区别?
  • 为什么大家总说“用容器把环境装起来”?
  • 初学阶段最值得优先掌握哪些命令?
  • 放到 Jetson 上时,又为什么总会多出一堆和 GPU、架构、驱动有关的问题?

下文面向完全初学者展开,重点是建立最基本但足够稳定的理解框架:知道 Docker 是什么、为什么使用、最常见的命令是什么,以及放到 Jetson 上时应优先关注哪些问题。

Docker 到底是什么

Docker 是一套打包、分发和运行应用环境的工具链。它的核心能力,是把“程序运行所需的一整套环境”封装成镜像(Image),再在安装了 Docker 的机器上以容器(Container)的形式运行。

可以先做如下理解:

  • 镜像(Image):一份“已经准备好的运行环境模板”
  • 容器(Container):从镜像启动出来的一个正在运行的实例

例如,一个 Python 项目需要:

  • Python 3.11
  • 某几个 pip 依赖
  • 某些系统库
  • 某个固定版本的 Ubuntu

传统做法往往需要在每台机器上手动安装一遍;Docker 的做法则是把这套环境写进镜像中,随后直接在其他机器上运行该镜像。

Docker 官方文档将其描述为一套用于开发、交付和运行应用的平台,容器提供了一种轻量级、隔离的运行方式。
参考:https://docs.docker.com/get-started/docker-overview/

Docker 不是虚拟机

这里最容易产生误解。Docker 经常被粗略理解成“轻量虚拟机”,但这个说法并不准确。

虚拟机的思路

虚拟机一般是:

  • 在宿主机上虚拟出一整台机器
  • 再在里面装一个完整操作系统
  • 然后在那套系统里运行程序

所以虚拟机通常更重,启动更慢,但隔离也更完整。

Docker 的思路

Docker 容器一般是:

  • 直接复用宿主机的 Linux 内核
  • 只隔离进程、文件系统、网络、用户空间等运行视图
  • 不再启动一整套新的操作系统

所以 Docker 通常:

  • 启动更快
  • 占用更小
  • 更适合做开发环境、服务部署、实验环境复现

可以用一句话概括:

虚拟机更像“再开一台电脑”,Docker 更像“给这个程序单独划一个隔离的运行空间”。

为什么要用 Docker

Docker 的价值主要体现在环境一致性、隔离性和部署统一性上。只要项目存在依赖版本、系统库、运行方式等要求,Docker 的作用通常都会很直接。

1. 环境可复现

这是 Docker 最大的价值。

依赖版本、系统库和启动命令都可以被固定下来,从而让环境可复制、可重建。

这意味着:

  • 今天能运行的环境,在未来更容易被重新构建
  • 开发机上的环境,与服务器环境更容易保持一致
  • 团队成员拿到同一个镜像后,行为通常更接近

2. 隔离不同项目

例如存在两个项目:

  • 项目 A 需要 Python 3.10
  • 项目 B 需要 Python 3.12

如果全部直接安装在宿主机上,环境很容易互相污染;使用 Docker 后,可以把它们分别放在各自的容器中。

3. 部署更统一

开发阶段运行的是容器,测试阶段运行的是容器,部署到服务器时运行的仍然是容器。
这样“开发环境”和“线上环境”之间的差异就会显著减小。

4. 试错成本低

容器可以随时:

  • 新建
  • 测试
  • 删除

不用担心把宿主机搞乱。

Docker 世界里最常见的几个概念

在真正使用之前,先把几个高频词认清会更有效。

Image

镜像。
它是一个只读模板,里面包含程序和运行环境。

例如:

ubuntu:22.04
python:3.11
nginx:latest

Container

容器。
它是镜像启动出来的运行实例。

同一个镜像可以启动多个容器。

Registry

镜像仓库。
用于存放和分发镜像,比如:

  • Docker Hub
  • GitHub Container Registry
  • NVIDIA NGC

Dockerfile

构建镜像的脚本。
Dockerfile 中通常会写:

  • 基础镜像是什么
  • 要复制哪些文件
  • 要安装哪些依赖
  • 默认启动命令是什么

Volume / Bind Mount

用于把宿主机数据挂进容器。

这很重要,因为容器本身通常不应该作为长期数据存储的地方。

Compose

用于同时管理多个容器。
如果一个项目包含:

  • Web 服务
  • 数据库
  • Redis

更常见的做法不是手写多条冗长的 docker run,而是通过 compose.yaml 统一管理。

先学会最小可用命令

完全初学阶段不需要一次背很多命令。下面这些命令先熟悉即可。

验证 Docker 是否可用

完成安装后,最常见的验证方式是运行一个最小测试镜像:

docker run --rm hello-world

如果终端输出欢迎信息,说明 Docker 服务、镜像拉取和容器启动链路都已经打通。

拉镜像

docker pull ubuntu:22.04

作用:把镜像从远端仓库下载到本地。

启动一个交互式容器

docker run --rm -it ubuntu:22.04 bash

这个命令可以拆开理解:

  • run:创建并启动容器
  • --rm:退出后自动删除容器
  • -it:以交互方式运行,附着当前终端
  • ubuntu:22.04:使用哪个镜像
  • bash:容器启动后执行什么命令

这基本是最适合初学者的第一条 Docker 命令。

查看正在运行的容器

docker ps

查看所有容器(包括已停止)

docker ps -a

停止容器

docker stop <container_id>

删除容器

docker rm <container_id>

查看镜像

docker images

删除镜像

docker rmi <image_id>

查看日志

docker logs <container_id>

进入一个正在运行的容器

docker exec -it <container_id> bash

这个命令非常常用。很多时候不需要重新创建容器,只需要通过 exec 进入现有容器进行查看或调试。

一个适合初学者的最小工作流

对于绝大多数入门场景,可以把 Docker 的使用流程压缩成下面四步:

  1. 先找一个现成镜像并运行,确认环境链路没问题。
  2. 再把本地目录挂载进容器,理解代码和数据应放在哪里。
  3. 然后编写 Dockerfile,把依赖和启动方式固定下来。
  4. 最后在服务变多时,再引入 Compose 管理多个容器。

这个顺序的好处是,概念和操作是同步建立的,不容易在一开始就被过多工具细节淹没。

一个最简单的“跑起来”的例子

最简单的体验方式之一,是先运行一个现成的 Web 服务容器。

docker run --rm -p 8080:80 nginx:latest

然后打开浏览器访问:

http://localhost:8080

如果浏览器出现 Nginx 欢迎页,说明最基本的 Docker 运行流程已经打通。

这里的 -p 8080:80 表示:

  • 宿主机的 8080
  • 映射到容器内的 80

也就是说,外部访问 localhost:8080,实际上会被转发给容器里的 Nginx。

容器里改了文件,为什么一删就没了

这是第二个高频困惑。

因为容器默认不是你长期保存数据的地方。

错误直觉

初学阶段很容易把容器当成“一台长期存在的小机器”,然后在里面改文件、装依赖、存数据。

这通常不是 Docker 推荐的使用方式。

正确直觉

容器更应该被看成:

一个可以随时被销毁和重建的运行实例

真正应该长期保存的内容通常包括:

  • 代码:放在宿主机目录并挂载进去
  • 数据:放在 volume 或数据库里
  • 环境定义:写进 Dockerfile / Compose 文件

例如,把当前目录挂进容器:

docker run --rm -it -v "$PWD":/workspace -w /workspace python:3.11 bash

这样容器里的 /workspace 就对应宿主机当前目录。

Dockerfile 是什么

当工作流从“运行别人提供的镜像”走向“打包自己的项目”时,就会用到 Dockerfile。

一个最简单的 Python Flask 示例:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "app.py"]

然后构建镜像:

docker build -t my-flask-app:0.1 .

运行:

docker run --rm -p 5000:5000 my-flask-app:0.1

Dockerfile 的意义主要在于:

  • 环境定义可保存
  • 团队成员可共享
  • 构建过程可自动化

Docker Compose 解决什么问题

当一个项目不只一个进程时,Compose 会显著降低管理复杂度。

比如一个典型 Web 项目可能需要:

  • 一个应用服务
  • 一个 PostgreSQL
  • 一个 Redis

理论上可以手写多条 docker run,但维护成本较高。Compose 的作用,就是把这些服务统一写进一个文件里。

最小示例:

services:
  web:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/app
    working_dir: /app
    command: python app.py

  redis:
    image: redis:7

然后:

docker compose up

停止并删除:

docker compose down

对初学者来说,docker compose up/down 往往比背很多长命令更值得优先学。

初学者最常见的误区

1. 把容器当虚拟机养

前面已经提到,容器最好是可销毁、可重建的。
如果在容器里手工改了很多内容,但没有把它们写进 Dockerfile,那么这些改动通常不可复现。

2. 所有数据都放容器里

这样容器一删,数据就没了。
数据库、模型、日志、工作目录,应该考虑挂载或 volume。

3. latest 用太多

latest 用起来省事,但不利于复现。
如果是长期项目,尽量固定版本。

4. 一上来就追求复杂编排

没有必要在起步阶段引入过多概念。
先掌握 pull / run / exec / logs / build / compose up,已经可以覆盖很多实际场景。

在 Jetson 上用 Docker,为什么经常更麻烦

因为 Jetson 不是普通 x86 Linux 机器,它在下面几个维度上都更特殊:

  • CPU 架构通常是 ARM64 / aarch64
  • GPU 不是桌面独显的工作方式
  • NVIDIA 运行时、JetPack、L4T、CUDA、TensorRT 等版本关系更紧
  • 板子上的存储和内存通常也更紧张

因此,把 PC 上的 Docker 教程原样搬到 Jetson 上后直接遇到问题,是非常常见的情况。

Jetson 上最该先记住的注意事项

1. 先确认镜像是不是 ARM64

这是最容易忽略的问题。

很多教程默认环境是:

  • x86_64
  • Ubuntu PC
  • 桌面 NVIDIA GPU

而 Jetson 通常是 ARM64。
这意味着并不是所有 Docker Hub 镜像都能直接在 Jetson 上跑。

优先确认的事项包括:

  • 镜像是否支持 arm64
  • 或者是否存在 Jetson 专用 / L4T 专用镜像标签

否则最常见的结果就是:

  • 拉不下来
  • 能拉下来但启动失败
  • 或者程序本身依赖的二进制根本不兼容

2. GPU 容器不是“装个 Docker 就结束”

在 Jetson 上,如果容器需要访问 GPU,通常还需要:

  • NVIDIA Container Toolkit / NVIDIA Container Runtime
  • Docker runtime 的额外配置

在 NVIDIA 近年的官方文档里,常见做法是用 nvidia-ctk 为 Docker 配置 NVIDIA runtime。
参考:

一个较新的通用流程大致是:

sudo apt-get update
sudo apt install -y nvidia-container
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

注意:

这部分在不同 JetPack / Jetson 文档里会有差异。上面的命令是基于官方近期文档整理的通用流程;实际操作时,应以设备对应 JetPack / L4T 版本的官方文档为准。

3. Jetson 上容器标签经常要和 JetPack / L4T 对齐

这是 Jetson 平台和普通 PC 很不一样的一点。

在很多 NVIDIA 提供的 Jetson 容器中:

  • 宿主机 BSP / JetPack 版本
  • 容器镜像标签
  • CUDA / TensorRT / DeepStream 等运行时

往往不是随便混用的。

也就是说,不能简单地把“容器里什么都有,所以宿主机无所谓”当成前提。

更稳妥的原则是:

  • 先确认板子的 JetPack / L4T 版本
  • 再选择与之匹配的 NVIDIA 容器镜像,例如 NVIDIA NGC 中带有 l4t 标识的 Jetson 镜像
  • 不要把 dGPU 教程和 Jetson 教程混着看

如果一个教程默认使用的是面向桌面显卡或服务器显卡的 CUDA 镜像,那么它在 Jetson 上往往并不能直接复用。

4. Jetson 上磁盘空间很容易先炸

很多 Jetson 板子:

  • eMMC 容量有限
  • 系统盘空间紧张
  • 镜像层和构建缓存又很占地方

因此,在 Jetson 上使用 Docker 时,最好尽早养成以下习惯:

  • 定期清理不用的镜像和容器
  • 尽量避免在板子上盲目 docker build 大镜像
  • 模型、数据、日志放挂载目录,而不是全堆在镜像层里

常用清理命令:

docker ps -a
docker images
docker system df
docker system prune

如果使用的是开发板而不是服务器,空间管理几乎一定会成为实际问题。

5. 摄像头、串口、USB、GPIO 这类外设要单独考虑

Jetson 上很多项目不只是“跑个程序”,还会碰到:

  • 摄像头
  • /dev/video*
  • 串口
  • USB 设备
  • I2C / SPI / GPIO

这些资源并不会因为进入容器就自动变得可用。

常见做法包括:

  • 显式映射设备
  • 映射必要目录
  • 使用 --privileged(谨慎)
  • 某些场景下使用 --network host

例如,最粗暴但最不精细的方式是:

docker run --rm -it --privileged --network host <image>

但这并不是默认推荐做法,因为它会显著降低隔离性。
更稳妥的方式通常是按需映射实际需要的设备和权限。

6. Jetson 上构建大型项目时,内存往往比 CPU 先不够

这一点在板子上非常真实。

如果容器中执行的是:

  • 编译大 C++ 项目
  • 编译 PyTorch 扩展
  • 构建大型 ROS / AI 依赖

更可能先遇到的是:

  • OOM
  • 编译卡死
  • swap 不足

因此,在 Jetson 上,很多“容器问题”本质上并不只来自 Docker 本身,而是来自:

  • 板子资源有限
  • 容器把问题暴露得更明显

7. 不要照搬桌面 NVIDIA GPU 的排错方法

很多 x86 教程会先执行:

nvidia-smi

但 Jetson 并不是桌面独显环境,这条命令通常并不是首选检查手段。
在 Jetson 上,更常见的观察方式是:

  • 先确认 JetPack / L4T 版本
  • 再确认容器 runtime 是否配置完成
  • 然后结合 tegrastats 或其他 Jetson 平台工具观察资源占用

这也是为什么 Jetson 平台上的 Docker 文档,通常需要优先看面向 Jetson 的官方材料,而不是直接照搬 PC 服务器上的 CUDA 容器说明。

官方最速示例:在 Jetson 上用 Docker 拉起 Qwen3 30B-A3B

如果目标不是理解全部 Docker 概念,而是尽快在 Jetson 上验证一次大模型推理链路,那么 NVIDIA 当前给出的官方示例已经足够直接。

根据 NVIDIA Jetson AI Lab 当前模型页,Qwen3 30B-A3B (MoE) 在 Jetson 上支持通过 vLLM 容器直接启动。对应命令如下。

Jetson Orin

sudo docker run -it --rm --pull always --runtime=nvidia --network host \
  ghcr.io/nvidia-ai-iot/vllm:latest-jetson-orin \
  vllm serve RedHatAI/Qwen3-30B-A3B-quantized.w4a16

Jetson Thor

sudo docker run -it --rm --pull always --runtime=nvidia --network host \
  ghcr.io/nvidia-ai-iot/vllm:latest-jetson-thor \
  vllm serve RedHatAI/Qwen3-30B-A3B-quantized.w4a16

这两条命令的意义分别是:

  • --pull always:每次启动前都尝试拉取最新容器版本
  • --runtime=nvidia:让容器接入 Jetson 的 NVIDIA runtime
  • --network host:直接使用宿主机网络,省去额外端口映射
  • vllm serve ...:直接在容器内启动模型推理服务

如果已经按照 NVIDIA 的 Jetson Docker Setup 文档把 Docker 默认 runtime 设为 nvidia,那么 --runtime=nvidia 可以省略。

启动前的最低前提

在执行上面的模型命令之前,至少需要先完成 NVIDIA 官方文档中的 Docker 基础配置:

sudo apt-get update
sudo apt install -y nvidia-container curl
curl https://get.docker.com | sh && sudo systemctl --now enable docker
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl daemon-reload && sudo systemctl restart docker

如果还没有把默认 runtime 设成 nvidia,还需要继续执行:

sudo apt install -y jq
sudo jq '. + {"default-runtime": "nvidia"}' /etc/docker/daemon.json | \
  sudo tee /etc/docker/daemon.json.tmp && \
  sudo mv /etc/docker/daemon.json.tmp /etc/docker/daemon.json

完成后,/etc/docker/daemon.json 中应能看到 default-runtimenvidia

如何确认服务已经起来

容器启动后,终端会停留在前台输出日志。这时可以在另一终端发一个最小测试请求:

curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "RedHatAI/Qwen3-30B-A3B-quantized.w4a16",
    "messages": [
      {"role": "user", "content": "用一句话介绍 Docker。"}
    ]
  }'

如果返回了标准的 JSON 推理结果,说明容器、模型和服务接口都已经正常工作。

这个示例的边界

这条命令强调的是“尽快验证推理链路”,不是完整生产部署方案。实际使用时仍然需要继续考虑:

  • 首次拉取镜像和模型时的磁盘占用
  • Jetson 板卡的内存容量是否足够
  • 模型容器版本与 Jetson 平台版本是否匹配
  • 长时间运行时是否需要改为后台服务、日志落盘和重启策略

Jetson 上一个更稳的思路

在 Jetson 上,更稳妥的排查顺序如下:

  1. 先在宿主机确认基础功能能工作
    例如摄像头、CUDA、基础驱动链路是否正常。

  2. 再确认 Docker 本身正常
    先跑普通 Ubuntu / Alpine 容器。

  3. 再确认 GPU runtime 正常
    确认 NVIDIA runtime 已配置成功。

  4. 最后再跑具体业务镜像
    例如 DeepStream、PyTorch、ROS、推理服务等。

这样排错会轻松很多。否则,一上来就运行大型 AI 镜像时,很难判断问题究竟来自:

  • Docker 没装好
  • runtime 没配好
  • 镜像架构不对
  • 版本不匹配
  • 外设权限不够
  • 板子资源不足

一个适合 Jetson 的心智模型

Jetson 上的 Docker,可以被概括为:

在“资源有限、版本耦合更强、外设更多”的环境里,尽量把软件栈封装稳定。

所以,Jetson 上使用 Docker 的重点并不是“把所有问题都藏起来”,而是:

  • 让依赖更清楚
  • 让环境更容易重建
  • 让实验和部署更容易复制

但与此同时,Jetson 平台的宿主机环境依然很重要,尤其是:

  • BSP / JetPack
  • NVIDIA runtime
  • 外设访问
  • 存储和内存

初学阶段的最小学习顺序

初学阶段可按下面的顺序推进:

第一阶段:先学会“会跑”

只学这些:

  • docker pull
  • docker run
  • docker ps
  • docker logs
  • docker exec
  • docker build

第二阶段:学会“会保存环境”

开始写:

  • Dockerfile
  • compose.yaml

第三阶段:学会“会挂数据、会排错”

重点理解:

  • 端口映射
  • 目录挂载
  • 容器日志
  • 镜像层和容器层

第四阶段:再上 Jetson

在这个阶段,再进入以下内容会更稳妥:

  • ARM64 镜像
  • NVIDIA runtime
  • JetPack 版本匹配
  • 设备映射

成功率会高很多。

一个常见排错顺序

如果容器没有按预期工作,建议按下面的顺序缩小问题范围:

  1. 先看容器是否真的启动成功:docker ps -a
  2. 再看主进程输出了什么:docker logs <container_id>
  3. 如果容器还活着,再进入容器检查文件、依赖和环境变量:docker exec -it <container_id> bash
  4. 如果涉及端口,再确认映射关系和监听地址是否正确
  5. 如果涉及 Jetson GPU 或外设,再额外检查 runtime、设备映射和宿主机版本匹配

这个顺序的重点是先分清问题是在 Docker 层、应用层,还是宿主机和设备层。

小结

Docker 的核心价值,不是“炫酷”,而是:

  • 把环境写下来
  • 把环境打包起来
  • 把环境稳定地复用起来

对于初学者,真正值得先建立的认识只有三个:

  1. 镜像是模板,容器是实例
  2. 容器是可销毁的,不要把它当长期主机养
  3. 宿主机、镜像、挂载、网络这四件事,决定了大多数 Docker 问题

而在 Jetson 上,再额外多记住三件事:

  1. 先确认架构是不是 ARM64
  2. GPU 容器需要 NVIDIA runtime 配置
  3. 镜像标签最好和 JetPack / L4T 版本对应

做到这里,已经足以脱离“完全不会 Docker”的阶段。

参考文档