Docker 基础:镜像、容器、卷与网络
Docker 解决的核心问题是“环境一致性”:镜像把代码、运行时、系统库一起打包,服务器上不再需要装 Python 或 JDK。但容器默认是易失的——删掉容器,里面的数据也一起消失。所以真正要建立直觉的是四件事:镜像与容器的关系、docker run 的关键参数、数据怎么落到卷上、容器之间怎么互相找到。
镜像、容器、仓库的关系
| 概念 | 是什么 | 类比 | 生命周期 |
|---|---|---|---|
| 镜像 Image | 只读的分层文件模板 | 类 / 安装包 | 长期存在,可跨机器分发 |
| 容器 Container | 镜像之上的一层可写运行实例 | 对象 / 进程 | 随时可删,删除即丢可写层 |
| 仓库 Registry | 存放与分发镜像的服务 | 应用商店 | 长期存在 |
| 卷 Volume | 由 Docker 管理的数据目录 | 外接硬盘 | 删除容器不影响 |
| 网络 Network | 容器之间的虚拟网络 | 交换机 | 删除容器不影响 |
一句话概括链路:docker build 产出镜像并 push 到仓库,服务器 pull 或 compose up 后变成容器,容器接上卷存数据、接上网络互相访问。
安装 Docker 与国内镜像加速
# 官方脚本安装,--mirror Aliyun 走国内源
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
sudo systemctl enable --now docker
# 让普通用户直接用 docker(等价于给了 root 权限,谨慎)
sudo usermod -aG docker $USER
// /etc/docker/daemon.json —— 镜像加速与日志轮转
{
"registry-mirrors": ["https://docker.m.daocloud.io"],
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "5" },
"data-root": "/data/docker",
"live-restore": true
}
sudo systemctl daemon-reload && sudo systemctl restart docker
docker info | grep -A3 'Registry Mirrors'
log-opts 一定要配:默认容器日志不轮转,跑几个月就能把系统盘写满。
docker run 关键参数
| 参数 | 作用 | 生产建议 |
|---|---|---|
-d | 后台运行 | 部署必加 |
--name | 指定容器名 | 有语义的名字,便于看日志与 exec |
-p 127.0.0.1:8000:8000 | 端口映射 | 绑 127.0.0.1,公网入口交给 Nginx |
-v /data/x:/app/data | 挂载数据 | 数据必须落卷,别写在容器里 |
-e KEY=VALUE | 注入环境变量 | 配置与密钥走这里,不要打进镜像 |
--restart unless-stopped | 重启策略 | 长驻服务必加 |
--network appnet | 加入自定义网络 | 多服务互访的前提 |
docker run -d \
--name myapi \
--restart unless-stopped \
--network appnet \
-p 127.0.0.1:8000:8000 \
-e TZ=Asia/Shanghai -e APP_ENV=prod \
-e DATABASE_URL='mysql://app:pass@db:3306/appdb' \
-v /data/myapi/uploads:/app/uploads \
--log-opt max-size=50m \
myapi:1.4.0
| 重启策略 | 行为 | 适用 |
|---|---|---|
no(默认) | 不自动重启 | 一次性任务 |
on-failure[:N] | 非 0 退出时重启,最多 N 次 | 批处理、迁移脚本 |
always | 总是重启,手动 stop 后也会被拉起 | 需谨慎 |
unless-stopped | 除手动 stop 外都重启 | 长驻服务的推荐值 |
数据卷与绑定挂载
| 维度 | 数据卷 -v volname:/path | 绑定挂载 -v /host/path:/path |
|---|---|---|
| 存储位置 | data-root 下的 volumes/ | 宿主机任意目录 |
| 可移植性 | 高,可用 docker volume 迁移 | 低,依赖宿主机目录结构 |
| 权限控制 | Docker 管理,较省心 | 宿主机 uid/gid 需自己对齐 |
| 备份 | docker run --rm -v vol:/d -v $PWD:/b alpine tar czf /b/v.tgz -C /d . | 直接 tar 宿主机目录 |
| 适合 | 数据库数据 | 配置文件、代码目录、外挂日志 |
取舍建议:数据库数据用命名卷,配置文件与需要人工查看的日志用绑定挂载。把宿主机空目录绑到镜像里有内容的路径,会出现“文件被盖住”的现象,这是新手最容易懵的一类问题。
自定义网络与按服务名互访
docker network create appnet
# 同一网络内的容器可直接用容器名当主机名
docker run -d --name db --network appnet -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
docker run -d --name myapi --network appnet -p 127.0.0.1:8000:8000 myapi:1.4.0
# 在应用容器里验证解析与连通性
docker exec -it myapi sh -c 'getent hosts db'
docker exec -it myapi sh -c 'nc -vz db 3306'
默认的 bridge 网络不支持按容器名解析,必须用自定义网络;容器名就是 DNS 名,重建后 IP 会变,所以连接配置里永远写服务名而不是 IP。
常用诊断与清理命令
docker ps -a # 含已退出容器,看退出码
docker logs -f --tail 200 myapi # 跟随日志
docker logs --since 30m myapi # 只看最近 30 分钟
docker exec -it myapi sh # 进容器排查(无 sh 用 bash)
docker inspect myapi --format '{{.State.Status}} {{.State.ExitCode}}'
docker stats --no-stream # 资源占用快照
docker system df # 先看占用,别盲删
docker image prune # 清理悬空镜像(<none>)
docker image prune -a --filter "until=168h" # 清理一周前未使用的镜像
docker volume prune # 清理未使用的卷(会删数据,先确认)
生产固定策略:保留最近 3 个镜像 tag,docker system prune --volumes 当危险操作对待。
验证方法
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
sudo ss -lntp | grep 8000 # 期望 127.0.0.1:8000
docker exec -it myapi sh -c 'nc -vz db 3306' # 依赖是否真连通
# 删容器重建后数据仍在,才算卷配对了
docker rm -f myapi && docker run -d --name myapi -v mydata:/app/data myapi:1.4.0
docker exec -it myapi ls -l /app/data
常见坑
| 坑 | 现象 | 做法 |
|---|---|---|
| 数据写在容器里 | 重建容器数据全丢 | 用命名卷或绑定挂载 |
-p 3306:3306 | 数据库暴露到公网且绕过 ufw | 写 -p 127.0.0.1:3306:3306 或不映射 |
| 默认 bridge 网络 | 容器间用名字连不上 | docker network create 建自定义网络 |
| 容器日志不轮转 | 磁盘被写满 | daemon.json 配 log-opts |
容器里没有 sh | docker exec 失败,无法排查 | 选带 shell 的基础镜像 |
prune 顺手带 --volumes | 数据卷被删,数据库没了 | 生产禁用该参数 |
小结:镜像是只读模板、容器是可写实例,凡是需要留存的东西必须落到卷上;docker run 记住 -d/--name/-p 127.0.0.1/-v/-e/--restart/--network 七个参数就够覆盖绝大多数场景;自定义网络让容器按服务名互访;最后把日志轮转、镜像保留策略和数据卷备份定成规矩,Docker 才不会变成新的运维债。