Docker 提供了一种轻量级虚拟化解决方案。相比传统虚拟机,Docker 容器更轻便、启动更快,也更容易部署。使用 Docker 后,开发者可以避免“同一份代码在 A 机器正常、在 B 机器异常”的问题,因为容器封装了一致的运行环境,让应用在不同机器上表现一致。

1 Docker 环境安装步骤
在 Ubuntu 虚拟机中,完整的 Docker环境安装步骤 通常包括更新系统、安装依赖、配置软件源与引擎验证。下面一步步来看。
1.1 安装依赖
先更新系统包索引:
sudo apt update && sudo apt upgrade -y
安装依赖:
sudo apt install ca-certificates curl gnupg lsb-release -y
添加 Docker 官方 GPG 密钥:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
这里可能会报错:
xxpcb@xxpcb-ubuntu20:~/Desktop$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to download.docker.com:443
gpg: no valid OpenPGP data found.
xxpcb@xxpcb-ubuntu20:~/Desktop$
国内虚拟机访问 download.docker.com 被重置,公钥下载失败。换成阿里云 Docker 公钥 + 源,先创建 keyrings 目录:
sudo mkdir -p /usr/share/keyrings
下载阿里云镜像的 Docker GPG 密钥:
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
添加 Docker 软件源(阿里云):
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
如果不需要使用国内镜像,也可以使用 Docker 官方软件源:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
1.2 安装 Docker Engine
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
把当前用户加入 docker 组:
sudo usermod -aG docker $USER
1.3 验证安装
docker --version
sudo docker run hello-world
我这里的版本如下:
xxpcb@xxpcb-ubuntu20:~/Desktop$ docker --version
Docker version 28.1.1, build 4eba377
1.4 hello-world 例子报错解决
hello-world 如果报错:
xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker run hello-world
[sudo] password for xxpcb:
Unable to find image 'hello-world:latest' locally
可以先更换国内镜像:
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.m.daocloud.io"
]
}
EOF
验证 JSON 语法,防止写错,没有报错代表 JSON 格式正确:
python3 -m json.tool /etc/docker/daemon.json
重启 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
检查 Docker 状态:
sudo systemctl status docker
看到 active (running) 就代表成功启动:
xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2026-10-12 22:44:21 CST; 8s ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 5278 (dockerd)
Tasks: 10
Memory: 21.0M
CGroup: /system.slice/docker.service
└─5278 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
xxpcb@xxpcb-ubuntu20:~/Desktop$
然后可以先拉取 hello-world 再执行:
sudo docker pull hello-world
sudo docker run hello-world
这次可以了:
xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
4f55086f7dd0: Pull complete
Digest: sha256:5e23090353324d887c48ad5e5c56d294eab81588df9605b07d1afe895f9cc8f8
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
2 查看镜像与容器
这一节结合 docker images 和 docker ps -a 来查看镜像与容器。先明确 Docker 引擎、镜像、容器三者的区别。
2.1 Docker 引擎、镜像、容器的区别
- Docker(引擎):刚才装的整套程序,包括 dockerd 服务、docker 命令,是运行容器的底层软件,就像虚拟机软件 VMware。
- Docker 镜像(image):一个只读模板,相当于虚拟机的系统安装盘或模板。镜像不能直接“进去”,它只是文件包。
- Docker 容器(container):用镜像启动出来的运行实例,相当于开机跑起来的虚拟机。只能进入容器,不能直接进入镜像。
三者的关系是:在 Docker 引擎的支持下,镜像通过 docker run 生成容器,之后我们进入的是容器。
镜像获取有两种方式:
docker pull:从镜像仓库拉取,例如 Docker Hub、国内镜像站。
docker load -i xxx.tar:导入本地导出的镜像文件,也就是 docker save 导出的 tar 包。
2.2 查看镜像
查看当前有哪些镜像:
sudo docker images
应该能看到刚才的 hello-world 镜像:
xxpcb@xxpcb-ubuntu20:~/Desktop$ sudo docker images
[sudo] password for xxpcb:
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest e2ac70e7319a 6 months ago 10.1kB
hhb4tools/hhb 2.4.5 58df969ae05a 3 years ago 8.16GB
xxpcb@xxpcb-ubuntu20:~/Desktop$
参数说明:
REPOSITORY:仓库名。
- 格式为
名字 或 用户名/名字,这是 Docker Hub 风格。
- 同一个仓库名可以对应多个不同 TAG 的版本。
TAG:标签,也就是镜像的版本号,用来区分同一个仓库下的不同版本。
latest 是默认标签,不写 tag 时默认拉取 latest,但它不一定是最新版,只是默认别名。
2.4.5 是 hhb 镜像的版本号。
- 组合起来
hhb4tools/hhb:2.4.5 才是镜像完整且唯一的名字。
IMAGE ID:镜像的唯一指纹。
- 它是 sha256 哈希的前 12 位,相当于镜像真正的身份证。
- 如果两个镜像 ID 一样,说明这两个镜像是完全相同的文件,只是名字或标签不同,类似软链接。
- 一个 IMAGE ID 可以对应多个
REPOSITORY:TAG,也就是给同一个镜像打多次别名。
CREATED:镜像构建时间。
- 镜像做好之后,这个时间就固定不变。
- 多次启动容器,镜像的
CREATED 不会变。
SIZE:镜像占用本地磁盘大小。
注:hhb4tools/hhb 是之前安装的玄铁 RISC-V 交叉编译器的镜像。
2.3 查看容器
xxpcb@xxpcb-ubuntu20:~/Desktop$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
211b48b192b2 hello-world "/hello" 2 days ago Exited (0) 2 days ago tender_volhard
9b266aa1e9ac hhb4tools/hhb:2.4.5 "bash" 3 years ago Exited (0) 2 years ago your.hhb2.4
xxpcb@xxpcb-ubuntu20:~/Desktop$
参数说明:
CONTAINER ID:容器 ID,用来唯一标识容器,注意它和镜像 ID 不是一回事。
IMAGE:这个容器基于哪个镜像启动。
- 例如
hhb4tools/hhb:2.4.5,它对应 docker images 里的 REPOSITORY:TAG。
COMMAND:容器启动时执行的入口命令。
- 比如执行
docker run ... bash,这里就会显示 "bash"。
- 也可能是镜像默认入口,例如某个脚本路径。
CREATED:容器创建时间。
- 例如
2 hours ago 表示这个容器是 2 小时前创建的,注意不是镜像创建时间。
STATUS:容器当前状态。
Up 10 minutes:正在运行,已经跑了 10 分钟。
Exited (0) 5 min ago:已经退出,5 分钟前停止;括号中 0 表示正常退出,非 0 通常是异常崩溃。
Created:创建完成,但从来没启动过。
Dead:容器损坏,只能删除。
PORTS:端口映射,即宿主机与容器内端口的对应关系。
- 空 PORTS 表示启动命令没有加
-p,没有端口映射。
- 有映射时可能显示类似
0.0.0.0:8080->80/tcp,表示宿主机 8080 端口转发到容器内部 80 端口。
NAMES:容器名字。
- 不手动指定
--name 时,Docker 会自动随机生成;名字和 ID 等效,都可以用来操作容器。
3 创建自定义镜像
自定义镜像实际上就是在某个基础镜像上做自定义配置,然后再次 commit,得到新镜像。比如想创建一个带有特定交叉工具链的镜像,可以用 Ubuntu 或其它 Linux 镜像作为基础,本篇使用 Debian 来演示。
3.1 创建基础版 Dockerfile
以 debian:bullseye-slim 为基础,先只安装基础编译依赖,交叉工具链可以后续手动配置。
新建文件夹,并创建 Dockerfile 文件:
FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive
# 安装基础工具:编译依赖、wget、tar、vim等,清理apt缓存减小镜像
RUN apt update && apt install -y \
build-essential \
cmake \
git \
wget \
tar \
unzip \
vim \
&& rm -rf /var/lib/apt/lists/*
# 容器内默认工作目录
WORKDIR /workspace
3.2 构建镜像
在 Dockerfile 所在目录执行:
docker build -t debian-arm64-base:v1 .
构建完成后,debian-arm64-base:v1 就是我们的基础镜像,里面只有 Debian 系统加基础编译软件。
第一次构建时,可能是海外网站访问问题,计时一直在增加,但下载大小一直没变:

按 Ctrl+C 结束掉,然后在 Dockerfile 的 RUN apt 前面一行增加:
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
再次构建还是报错:

于是改为如下写法:
FROM debian:bookworm-slim
RUN echo "deb http://mirrors.ustc.edu.cn/debian bookworm main contrib non-free" > /etc/apt/sources.list && \
echo "deb http://mirrors.ustc.edu.cn/debian bookworm-updates main contrib non-free" >> /etc/apt/sources.list && \
echo "deb http://mirrors.ustc.edu.cn/debian-security bookworm-security main contrib non-free" >> /etc/apt/sources.list
# 安装编译依赖
RUN apt update && apt install -y \
build-essential \
cmake \
git \
wget \
tar \
unzip \
vim \
&& rm -rf /var/lib/apt/lists/*
# 后续你可以在这里添加 ARM64 交叉编译工具链
# RUN wget xxx 安装 aarch64-linux-gnu-gcc
然后构建前先清理旧缓存:
docker build --no-cache -t debian-arm64-base:v1 .
这次可以了:

可以再查看一下镜像:
xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
debian-arm64-base v1 24de33851c6b 9 minutes ago 604MB
hello-world latest e2ac70e7319a 6 months ago 10.1kB
hhb4tools/hhb 2.4.5 58df969ae05a 3 years ago 8.16GB
xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$
注意到这次构建镜像时,Dockerfile 忘记加上 WORKDIR /workspace 了,也就是没有指定默认工作目录。这个问题可以在 docker run 时再次指定。
3.3 启动容器
docker run -it --name build-arm64-env -w /workspace -v /home/xxpcb/myTest/DockerTest/code:/workspace debian-arm64-base:v1 bash
--name build-arm64-env:给容器命名,后面拷贝文件、commit 打包都要靠这个名字。
-it:执行完直接进入容器终端。

确认已创建的容器:
xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6ef569725d63 debian-arm64-base:v1 "bash" About a minute ago Exited (0) 9 seconds ago build-arm64-env
211b48b192b2 hello-world "/hello" 5 days ago Exited (0) 5 days ago tender_volhard
9b266aa1e9ac hhb4tools/hhb:2.4.5 "bash" 3 years ago Exited (0) 2 years ago your.hhb2.4
xxpcb@xxpcb-ubuntu20:~/myTest/DockerTest/debian-arm64$
3.4 docker run 的参数完整说明
这里补充说明一下 docker run 的各种参数。
3.4.1 高频参数
| 参数 |
含义 |
示例 |
说明 |
-i |
interactive,保持标准输入打开 |
docker run -i ubuntu bash |
不加则 bash 输入被关闭,交互命令无法输入 |
-t |
tty,分配伪终端 |
docker run -t ubuntu bash |
提供命令行提示符;通常与 -i 连用写成 -it |
-it |
交互式终端,进入容器 shell |
docker run -it debian-arm64-base:v1 bash |
编译、调试最常用组合 |
--name |
指定容器名称 |
--name arm-build-env |
不写则随机生成;后面 cp/stop/commit 都用名字定位 |
-v |
挂载目录,宿主机目录:容器目录 |
-v /home/xxpcb/code:/workspace |
源码放宿主机、编译在容器内,产物落宿主机,容器删了代码也不丢 |
-d |
后台运行(detach) |
docker run -d nginx |
放后台守护,不占终端;交互编译不用它 |
--rm |
容器退出后自动删除 |
docker run -it --rm ubuntu bash |
临时测试用完即焚;要做 commit 打包时绝不能加 |
3.4.2 常用参数
| 参数 |
含义 |
示例 |
说明 |
-p |
端口映射,宿主机端口:容器端口 |
-p 8080:80 |
容器 80 端口映射到宿主机 8080;交叉编译一般用不到 |
-e |
注入环境变量 |
-e PATH="/opt/bin:$PATH" |
可写多个 -e |
-w / --workdir |
容器内工作目录 |
-w /workspace |
启动后直接进入该目录,省去手动 cd |
--network |
网络模式 |
--network=host |
host 表示共享宿主机网络;默认 bridge,编译场景够用 |
3.4.3 其它
| 参数 |
含义 |
说明 |
--privileged |
特权模式 |
接近宿主机 root 权限,安全风险大,尽量不开 |
--user |
指定容器内运行用户 |
默认 root |
-m / --memory |
限制最大内存 |
如 -m 2G |
--cpus |
限制 CPU 核数 |
如 --cpus 2,编译时可控制资源占用 |
3.5 退出容器后的再次进入指令
情况 1:容器只是退出
这是最常见的情况,例如执行 exit 之后容器已经停止:
# 第一步:启动这个容器
docker start build-arm64-env
# 第二步:进入容器终端
docker exec -it build-arm64-env bash
情况 2:容器还在运行
如果只是新开一个终端想进去,直接执行这一条:
docker exec -it build-arm64-env bash

docker 三指令对比
| 命令 |
作用 |
区别 |
docker run |
新建容器并启动 |
每次 run 都会生成新容器 |
docker start 容器名 |
启动已存在的旧容器 |
不新建容器 |
docker exec -it 容器名 bash |
进入正在运行的容器 |
要求容器已经在跑 |
3.6 安装交叉工具链
3.6.1 下载与安装
# 进入 opt 目录
cd /opt
# 解压工具链
tar xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar
# 临时添加环境变量,当前这个 bash 会话生效,容器重启会失效
export PATH="/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH"
# 验证工具链
aarch64-linux-gnu-g++ -v
# 把 PATH 写入 /etc/profile,每次启动容器自动加载
echo 'export PATH="/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH"' >> /etc/profile
配置好后,可以用另一个 shell 进入 Docker 试一下:

注意:docker exec 进入的 bash 默认是交互式 shell,但不会自动加载 /etc/profile。/etc/profile 只在登录 shell,也就是 login shell 中才会执行。
可以在 exec 时用登录 shell 方式进入:
docker exec -it build-arm64-env bash -l
bash -l 表示 login shell,会加载 /etc/profile,PATH 就会自动带上交叉工具链路径。
3.6.2 编译程序验证
使用刚才安装的交叉编译工具链编译出程序,再放到对应的板子上验证是否可运行。这里先不展开演示。
3.7 打包成新镜像
先退出容器,这里容器不会被删除,只是退出 shell:
exit
回到宿主机终端执行 commit,把当前容器状态打包成新镜像:
docker commit build-arm64-env debian-arm64-base:v2
build-arm64-env:容器名字。
debian-arm64-base:v2:新生成的镜像名字和 tag。

可以看到刚才 commit 出来的 v2 镜像,大小约 1.39GB。具体多了什么导致体积增大,后面再继续探究。
3.8 使用新镜像再次创建容器
使用新镜像再创建一个容器,看看刚才手动安装的交叉工具链,是不是通过 v2 镜像创建的容器就默认自带:
docker run -it --name build-arm64-env-v2 -w /workspace -v /home/xxpcb/myTest/DockerTest/code:/workspace debian-arm64-base:v2 bash
新窗口启动后,用 login shell 进入:
docker exec -it build-arm64-env-v2 bash -l

可以看到,v2 容器已经自带了刚才在 v1 容器中安装的交叉编译工具链。
4 总结
本篇介绍了 Docker 的一些概念与操作:先在 Ubuntu 虚拟机中安装 Docker 引擎,再简单体验 hello-world 容器,并说明镜像与容器的区别;接着尝试创建自定义镜像,安装自定义的交叉编译工具链;最后使用自定义镜像再次创建容器进行验证。