Docker 基础¶
主要作者
CAICAII
安装 Docker¶
不同操作系统和 Linux 发行版的安装方式不同,请优先按照 Docker 官方安装文档配置软件仓库并安装。通过软件仓库安装更便于后续升级和获取安全更新。
Docker 也提供面向测试与开发环境的便捷脚本。不要把网络脚本直接通过管道交给 Shell(例如
curl ... | sh):管道执行意味着你没有任何机会检查脚本内容。正确的做法是"下载 → 阅读 → 预览
→ 执行"四步:
# 1. 下载:把脚本存成文件,而不是直接交给 shell(HTTPS 只保证传输过程,不保证脚本内容符合你的预期)
curl -fsSL https://get.docker.com -o get-docker.sh
# 2. 阅读:通读脚本,确认它改了哪些源、装了哪些包
less get-docker.sh
# 3. 预览:先看它打算执行哪些操作
sh get-docker.sh --dry-run
# 4. 执行:确认无误后再真正安装
sudo sh get-docker.sh
不要用 sha256sum 自我安慰
对刚下载的文件执行 sha256sum get-docker.sh 不能验证来源是否可信:如果文件在传输过程中
被替换,摘要也会随之改变,你算出的永远是"当前这份文件"的摘要。Docker 官方也没有为这个便捷
脚本公布可供比对的摘要值。需要通过签名验证软件包时,请按
Docker 官方 APT 仓库安装步骤
配置 Docker 的 GPG 公钥,或使用发行版的签名软件仓库;便捷脚本本身也只被
官方推荐用于测试与开发环境。
确认脚本来源、内容和预览结果都适合当前测试环境后,再按官方文档执行安装。该脚本不推荐用于生产 环境,也不适合替代受控的版本和升级策略。
接下来可使用如下命令来查看 docker 信息:
运行你的第一个容器¶
跟所有的技术学习起点一样,上述命令使用 docker 运行一个最简单的 hello-world 容器,它的功能仅仅是在屏幕上打印一些文字。
docker run 首先会去本地寻找 hello-world 镜像,如果本地没有,则会从默认的 Docker 镜像仓库中拉取,也就是 Docker Hub。
镜像的格式一般为
如果 repository 为空,默认为 Docker Hub;tag 为空,则默认为 latest。
如下是一个 Nginx 镜像示例,其中 repository 为 library,
image 为 nginx,tag 为 1.25.3,这是从 Docker 官方仓库拉取特定版本的 Nginx 镜像。
不要用 latest,先查镜像的支持周期再决定 tag
latest 并不是"最新稳定版"的保证,它只是上游维护者随手打的一个标签,会随上游发布而漂移:
今天能构建、明天行为就可能不同,回滚时也无法确认当时用的是哪个版本。选 tag 的正确做法是:
- 打开 Docker Hub 上该镜像的页面,查看 Tags 列表,以及标签说明里指向的官方文档;
- 在上游项目的官方文档里找到它的 release cycle / support(支持周期) 页面,确认哪些 系列仍在维护、哪些已经 EOL;
- 优先选择仍在维护的 LTS 或稳定系列,并写死具体的小版本号或明确的系列标签
(例如
nginx:1.25.3、mysql:8.4),生产环境尤其不要依赖latest。
镜像分为 public 和 private 两种,对于 public 的镜像无需登录即可拉取,对于 private 的镜像则需要登录后才能拉取,登录命令如下
实践案例:运行 Alpine Linux 容器¶
Alpine 镜像在企业生产环境中被广泛应用,它是一个极简的 Linux 发行版,
只包含最基本的命令和工具,因此镜像非常小,只有 5MB 左右,并且内置包管理系统 apk,使其成为许多其他镜像的常用起点。
拉取镜像
运行容器
交互式运行容器
docker run 命令默认使用镜像中的 Cmd 作为容器的启动命令,Cmd 可通过如下命令来查看。
可以看到默认的 Cmd 为 ["/bin/sh"],因此直接使用 docker run alpine 会启动 /bin/sh 这个 shell,
我们期望可以在这个 shell 中执行一些命令,但实际上它只是启动了 shell,退出了 shell,然后就停止了容器。
后台运行容器
run -it 命令会启动一个交互式终端,退出终端后容器也会停止,如果希望容器在后台运行,可以使用 -d 参数,如下命令会启动一个后台运行的容器。
加上 -d 参数后,容器不会执行完命令后立即退出,而是会进入后台运行,此时会返回一个唯一的 ID,使用 docker ps 命令可以查看容器运行状态。
docker attach 用于连接到一个正在运行的容器,主要作用是访问容器的主进程(PID=1)的标准输入输出流
由于 attach 是接管了 PID=1 的进程,因此如果这个进程是守护进程,
那么 attach 退出后,容器也会退出。所以一般不推荐使用 attach 命令。
而是使用 docker exec 命令来连接容器。
此时进入到容器中使用 ps -a 命令可以看到容器中存在两个进程,其中 PID=1 的进程为 /bin/sh,
而另一个 /bin/sh 进程则是我们通过 exec 命令启动的,这个进程退出不会影响 PID=1 的进程,也就不会导致容器的退出。
观察容器:从 run 到 ps 到 logs¶
前面几步都用 -d 把容器放到了后台,但我们怎么知道它到底跑起来没有?下面用一条完整链路来观察
一个容器从启动到出错的全过程。
用 docker ps 确认它是否在运行(-a 会连已停止的容器一起列出来):
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 nginx:1.25.3 "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:8080->80/tcp demo
各列的含义:CONTAINER ID 是容器 ID 的短形式(前 12 位);IMAGE 是启动时使用的镜像;
STATUS 里的 Up 5 seconds 说明容器正在运行(若是 Exited (1) ... 就说明它已经退出,括号里
是退出码);PORTS 显示宿主机的 8080 映射到容器的 80;NAMES 就是我们用 --name 指定的名字,
后续命令既可以用容器 ID,也可以用这个名字。
如果容器起不来或行为异常,第一件事是看日志:
docker logs 读的是容器标准输出/标准错误的日志文件。默认的 json-file 日志驱动会把它写在
宿主机 /var/lib/docker/containers/<容器 ID>/<容器 ID>-json.log,而且默认不做轮转,长时间
运行的服务需要配置大小限制(见后面「容器监控与管理」一节)。
需要更细的状态时,用 docker inspect 读取容器的 JSON 元数据,配合 -f / --format 只取需要的
字段:
# 查看容器状态、退出码与是否被 OOM 杀死
docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}}' demo
# 查看容器实际拿到的 IP
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' demo
最后,停止并清理这个演示容器:
平台差异与 Docker Desktop 许可¶
Docker 在各平台上的安装方式并不相同:
- Linux:直接安装 Docker Engine(
docker-ce+containerd+ CLI)即可,容器与宿主机共用 同一个内核,没有额外的虚拟化层,性能和网络行为最"原生"; - Windows:需要 WSL2 后端(推荐,在 WSL2 的 Linux 发行版里跑 Engine)或旧的 Hyper-V 后端;容器实际上跑在一个 Linux 虚拟机里;
- macOS:Docker Desktop 会在后台启动一台轻量 Linux 虚拟机来跑 Engine。因此 macOS 上
--network host之类依赖"宿主机网络栈"的特性行为与 Linux 不一致。
此外,Docker Desktop 的许可需要留意。按 Docker 官方《Docker Desktop license agreement》页面 (依据 Docker Subscription Service Agreement, 本页内容查证于 2026-09):
- 免费:小型企业(员工数少于 250 人、且年收入少于 1000 万美元,两个条件需同时满足)、 个人使用、教育用途、非商业开源项目;
- 需要付费订阅:较大规模组织中的职业使用、政府机构、超出免费额度的商业使用。
也就是说,员工数达到 250 人或年收入达到 1000 万美元(任一条件成立)的商业组织不再属于 免费的小型企业范围。许可条款可能调整,实际使用时请以官方页面为准。
如果你所在的组织属于需要付费的范围,又想避免授权问题,可以考虑这些替代方案:
- Podman:命令行与 Docker 高度兼容(
alias docker=podman基本可用),支持 rootless 容器, 且无 Docker Desktop 的桌面端许可限制; - Colima:macOS / Linux 上的开源容器运行时,提供与
dockerCLI 兼容的接口,常被用作 Docker Desktop 的免费替代; - Linux 上也可以完全不装 Docker Desktop,直接用 Engine 或 Podman。
安全基线:非 root、只读根文件系统、capabilities¶
容器"能用"不等于"安全"。以下几件事是容器上生产前的底线:
1. 不要以 root 运行进程。 容器默认以 root 运行,一旦容器内的进程被攻破、或挂载了宿主机的
目录,影响会被放大。首选做法是在 Dockerfile 里用 USER 切换到非 root 用户
(见自定义镜像之 Dockerfile 详解),或直接使用上游提供的非 root 变体:
# 官方 nginx 镜像默认以 root 启动、监听 80 端口,并要写 /var/cache/nginx
# 与 /var/run/nginx.pid。只加 --user 覆盖 UID 会同时踩到三个坑:
# 目录不可写、无权绑定 80 端口、PID 文件写不进去,容器会直接退出。
# 正确做法是使用官方提供的非 root 变体:它已把缓存/PID 目录与监听端口
# (8080)都调整好。
docker run -p 8080:8080 nginxinc/nginx-unprivileged:1.25-alpine
--user 不是"以非 root 运行"的通用开关
能否直接覆盖 UID,取决于镜像是否为非 root 场景准备过:目录权限、监听端口、
PID 文件位置都要能配合。自己没有把握时,优先选择上游提供的非 root 变体
(如 nginxinc/nginx-unprivileged、bitnami/* 系列),或在构建阶段自己用
USER + 调整目录与端口,而不是运行时硬加 --user。
2. 尽量使用只读根文件系统。 加上 --read-only 后,容器无法写自己的根文件系统,只保留显式
挂载或声明的可写位置:
# 根文件系统只读;需要写入的目录用 tmpfs 放在内存里。
# 官方 nginx 镜像启动时要写 /var/cache/nginx(临时目录)与 /var/run(PID 文件),
# 只挂 /tmp 会以 "Read-only file system" 退出。
docker run --read-only \
--tmpfs /tmp \
--tmpfs /var/cache/nginx \
--tmpfs /var/run \
nginx:1.25.3
只读根文件系统要先问清镜像需要写哪里
不同镜像需要写入的路径并不相同。做法是先跑一次,按报错补挂可写目录:例如
docker run --read-only <镜像> ... 报 Read-only file system: /var/cache/nginx,
就加 --tmpfs /var/cache/nginx。正规镜像通常会在文档里说明非 root / 只读运行的要求
(例如提供 nginx-unprivileged 这类专门适配的变体)。
3. 按最小权限裁剪 capabilities。 Linux capabilities 把 root 的特权拆成了若干细项,容器默认 仍带有不少能力。做法是先全部丢掉,再加回真正需要的:
# 丢光所有 capability,只加回这个镜像真正需要的四项(下面这组已实测可用):
# NET_BIND_SERVICE —— 绑定 80 等低端口
# SETGID / SETUID —— root master 进程要切换到 nginx worker 用户
# CHOWN —— 把 /var/cache/nginx 下的临时目录改属主给 worker 用户
docker run --cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--cap-add=SETGID \
--cap-add=SETUID \
--cap-add=CHOWN \
nginx:1.25.3
这四项是逐个试出来的
少任何一项都会在启动阶段失败,而且报错指向不同的地方:
| 缺少的能力 | 典型报错 |
|---|---|
CHOWN |
chown("/var/cache/nginx/client_temp", 101) failed (1: Operation not permitted) |
SETGID / SETUID |
切换到 worker 用户时失败(setgid(...) / setuid(...)) |
NET_BIND_SERVICE |
绑定 80 端口时 Permission denied |
所以"最小权限"不是照抄一份清单,而是先 --cap-drop=ALL 跑一次、按报错逐个加回。
这份清单依赖具体镜像,换镜像(或换非 root 变体)后要重新确认。
更彻底的做法是用非 root 镜像
上面这份清单是跟着镜像走的:官方 nginx 镜像以 root 启动 master 进程,所以必须保留
SETGID/SETUID(少了它们会以 setgid(nginx) failed (1: Operation not permitted) 退出)。
如果改用非 root 变体(如 nginx-unprivileged)并监听 8080 这类高位端口,连
NET_BIND_SERVICE、SETGID、SETUID 都不再需要,这也说明**"最小权限"没有通用答案,
要按镜像的实际行为确定**。
4. 不要用 --privileged。 --privileged 会关闭几乎所有隔离,等价于把宿主机 root 交给容器,
只应出现在明确知道后果的调试场景。
5. 不要把 /var/run/docker.sock 挂进容器。 能访问这个 socket 的进程就能在宿主机上创建任意
特权容器,等于拿到了宿主机 root: