初探 Docker

分割线

概念速览

刚接触 Docker 时容易被一家子工具绕晕, 安装时也会冒出几个相关选项:

20210131002505 20210131004332
工具定位解决什么问题
Dockerfile构建镜像的脚本把环境/依赖/启动命令写死, 镜像可复现
docker-compose多服务编排一个 YAML 起一组容器 (DB + 后端 + 网关…)
docker-machine多 Docker 主机管理远程机器/集群上批量管理 Docker daemon (已淡出)

docker-machine 现在基本被 K8s / Swarm / Portainer 取代, 新项目用不到了, 只在老文档里偶尔见.

Dockerfile

构建镜像的文本脚本, 由一行行指令 (FROM / RUN / COPY / CMD 等) 组成, 描述"基础镜像 + 我装啥 + 我跑啥".

Docker Dockerfile - runoob

docker-compose

单机起多服务的编排工具. 一个 docker-compose.yml 描述所有服务、网络、卷, docker compose up -d 一键拉起.

新版 Docker 已内置 docker compose (空格分隔) 子命令, 不再依赖独立的 docker-compose Python 脚本.

docker-machine

跨主机管理 Docker daemon 的命令行工具. 现在生产环境一般用 K8s 或 Swarm, 这个工具维护停滞, 仅作了解.

Docker 三剑客之 docker-machine - 知乎

分割线

安装与升级

Windows

Docker 引擎本质是 Linux 内核技术 (namespace + cgroup), 不能直接跑在 Windows 上. Docker Desktop 在底层依赖 WSL2 提供 Linux 内核.

详见 🎇 尝试转投 wsl 生态.#名词解释

Ubuntu / Debian

Ubuntu-Server 装系统时勾选 Docker 选项即可一键装好. 默认 docker 命令需要 sudo, 把当前用户加进 docker 组就能免 sudo:

sudo groupadd docker          # 用户组通常已存在, 报 already exists 可忽略
sudo usermod -aG docker $USER
# 重新登录或 newgrp docker 让组权限生效

加入 docker 组等同于 root 权限 (能挂任意目录到容器再读 host 文件), 多人服务器慎用.

升级到最新版

Docker CE 通过官方 apt 源管理, 升级走标准 apt 流程, 不会动现有容器和卷:

sudo apt-get update
sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin
  • --only-upgrade: 只升级已装的, 不会拉新的依赖.
  • containerd.io: 容器运行时, 跟 docker-ce 是绑死版本关系, 一起升才不会版本错位.
  • docker-buildx-plugin: BuildKit 前端, 新版 docker build 默认走它.

升级完用 docker version 看 Client / Server 两个版本是否对齐. 如果只升了 client 没升 server, 容器拉不起来时会报 API 版本不兼容.

大版本跨越 (比如 20.x -> 24.x) 升级前最好 docker compose down 让所有服务停掉, 升完再 up -d. 在线升级 daemon 时正在运行的容器会被一起重启, 有状态服务可能丢数据.

服务管理 (systemctl)

# 启动
sudo systemctl start docker

# 开机自启
sudo systemctl enable docker

# 改完 /etc/docker/daemon.json 后, 先 reload 再 restart
sudo systemctl daemon-reload
sudo systemctl restart docker

分割线

加速与代理

镜像加速

国内直连 Docker Hub 经常慢甚至超时, 配镜像源是必备操作. Windows 在 Docker Desktop 的 Settings → Docker Engine 里改 JSON:

{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
]
}
20210205010825

Linux 改 /etc/docker/daemon.json, 然后 systemctl daemon-reload && systemctl restart docker. 用 docker info | grep -A 5 Mirrors 验证生效.

大厂镜像源近两年陆续失效或限速, 上面只是示例, 实际可用列表 DaoCloud 维护的清单 比较新.

Docker 自身走代理

配了镜像加速一般就够了; 镜像源也拉不到时再考虑挂代理.

Docker Desktop 在 Settings → Resources → Proxies 里设, 例如 http://localhost:7890.

Linux 走 systemd drop-in:

sudo mkdir -p /etc/systemd/system/docker.service.d/
sudo nano /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1"
sudo systemctl daemon-reload
sudo systemctl restart docker

容器内使用宿主机代理

容器里跑的程序 (比如 docker-osx 里的虚拟机) 想用宿主机的代理时, 默认桥接网络下容器看不到宿主机的 localhost:7890.

两种方法:

  • docker run --net=host ... — 容器直接用宿主机网络栈, 简单粗暴, 但端口冲突风险高
  • 在容器里把代理填成 host.docker.internal:7890 — Docker 自动把这个域名解析到宿主机内网 IP, 推荐

host.docker.internal 在 Docker Desktop (Win/Mac) 默认可用; Linux 上需要 docker run --add-host=host.docker.internal:host-gateway ... 显式开启.

分割线

常用命令

跟 shell 相关的更多命令: 💧 一些常用终端命令.#docker

运行容器

很多文档的 docker run 命令带换行:

docker run -d \
-p 8088:8088 \
--name next-terminal \
--restart always ghcr.io/dushixiang/next-terminal:latest

行末的 \ 是 POSIX shell 续行符, Windows cmd / pwsh 不认. 直接拼成一行就行:

docker run -d -p 8088:8088 --name next-terminal --restart always ghcr.io/dushixiang/next-terminal:latest

pwsh 可以用 backtick ` 续行, cmd 用 ^, 但最稳的还是拼一行.

系统清理

跑久了磁盘会被一堆悬空镜像、停止的容器、未挂载的卷、构建缓存吃掉, 定期清理:

# 看占了多少
docker system df

# 安全清理: 只动当前没被任何容器使用的镜像/网络/缓存
docker system prune

# 激进清理: 加 -a 把所有未使用的镜像也清掉 (会清掉你 pull 但当前没跑的镜像)
docker system prune -a

# 跳过确认
docker system prune -a -f

# 精细化清理 (只清单个对象类型)
docker image prune # 悬空镜像 (untagged)
docker builder prune # 构建缓存
docker volume prune # 未挂载的卷 (注意: 卷里的数据会丢)

docker volume prune 是最容易误伤的, 没在跑的服务的数据卷也会被清, 跑之前先 docker volume ls 看一眼.

镜像导入导出

把镜像打包成 tarball 拷到内网或离线机器:

# 导出 (gzip 压缩节省空间)
docker save ollama/ollama:0.15.6 | gzip > ollama.tar.gz

# 导入
gunzip -c ollama.tar.gz | docker load

docker save 导出的是镜像 (image), 跨机器迁移用这个.
docker export 导出的是容器文件系统 (container), 会丢掉镜像层信息, 用途完全不同别搞混.

分割线

Nginx 反代 Docker Registry

自建 Docker Registry 并在前面挂 Nginx 反代时, push 大镜像容易踩到 Nginx 的请求体大小限制.

问题现象

client intended to send too large body: 4634645939 bytes

请求还没到 Registry 就被 Nginx 拦截了.

原因: Nginx 默认 client_max_body_size 1m, 而 Docker 上传 layer 可能达到几 GB (LLM 镜像、CUDA 环境、数据集等). 只要单个 layer 超过限制值就会触发 413 错误.

解决方案

解除 body 限制并关闭缓冲, 大文件直接流式传输:

client_max_body_size 0;          # 0 = 不限制请求大小
proxy_request_buffering off; # 不缓存请求体, 直接流式转发
proxy_buffering off; # 不缓存响应
proxy_connect_timeout 600s;
proxy_send_timeout 3600s; # 大镜像上传可能持续几十分钟
proxy_read_timeout 3600s;
send_timeout 3600s;
  • client_max_body_size 0: 适合 Docker Registry / Git LFS / 大文件上传服务
  • proxy_request_buffering off: 关闭后 Nginx 不再先完整缓存再转发, 避免临时目录爆满
  • 超时参数: 上传超过默认 60s 会断连, 大镜像建议拉到 3600s (1 小时)

最终配置参考

server {
listen 443 ssl;
server_name docker.example.com;

client_max_body_size 0;
proxy_request_buffering off;
proxy_buffering off;

proxy_connect_timeout 600s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
send_timeout 3600s;

location / {
proxy_pass http://registry:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}

验证配置:

nginx -t && nginx -s reload

大模型镜像优化建议

不要把模型权重直接打进去:

image
└── model.bin 80GB ← 不推荐

推荐分离架构:

Docker Image            模型文件
├── runtime 环境 NAS / OSS / NFS
└── inference 代码 → 启动时挂载 /models

这样镜像从几十 GB 缩到几百 MB, 推送和存储都更合理.

VSCode Docker 扩展

在远程 Linux (Manjaro / Ubuntu) 上起 Docker 服务, VSCode 通过 SSH 连过去, 装 Docker 扩展, 容器/镜像/卷/网络全都能在侧边栏可视化点点点, 比纯命令行舒服很多.

20210206153528

搭配 Resource Monitor 扩展能在状态栏看远程机的 CPU / 内存 / 磁盘占用 (上图最下方那条).

分割线

借物表