Docker 教程:从镜像容器概念到 Dockerfile 与 compose 实战
“我这儿能跑啊”——部署最老的敌人就是环境差异:你机器上有的依赖、库版本、系统配置,服务器上不一定有。Docker 的解法是把程序连同它的整个运行环境(系统库、依赖、配置)打包成一个可移植的”盒子”,在哪台机器上跑,环境都一模一样。
Docker 是什么:三个核心概念
Docker 是容器引擎——用比虚拟机轻得多的方式隔离和运行程序。上手前必须分清三个概念,绝大多数新手困惑都源于把前两个混为一谈:
- 镜像(image):只读的模板,包含程序和它的完整运行环境——类比面向对象里的”类”
- 容器(container):镜像跑起来的实例——类比”对象”,同一个镜像可以同时跑出多个互不干扰的容器
- 仓库(registry):集中存放镜像的服务,默认是 Docker Hub,
docker pull从这里拉镜像
flowchart LR
D[Dockerfile] -->|docker build| I[镜像 image]
I -->|docker run| C1[容器 1]
I -->|docker run| C2[容器 2]
R[(仓库 registry)] -->|docker pull| I
I -->|docker push| R
安装:一条命令
1 | curl -fsSL https://get.docker.com | sh |
macOS/Windows 用 Docker Desktop 图形化安装(macOS 上轻量替代 OrbStack 也很流行),装完命令行用法完全一致。
docker run:参数拆解
跑一个 nginx,把最常用的参数一次讲清:
1 | docker run -d -p 8080:80 --name web nginx |
容器生命周期:从跑起来到删干净
1 | docker ps |
一个必须建立的认知:容器不是轻量虚拟机,它的生命周期跟着主进程走——docker run 启动时指定(或镜像默认)的那个进程退出了,容器就停了。跑个 docker run ubuntu 发现容器”秒退”,不是坏了,是它的默认命令执行完就结束了。
数据坑:容器删了,数据跟着没了
容器内部写的文件属于容器自己的可写层,docker rm 之后整层蒸发。数据库这类有状态的服务,必须把数据目录挂载到宿主机:
1 | docker run -d --name pg \ |
判断标准很简单:这个容器产生的数据丢了心不心疼?心疼就必须 -v 挂出来。
镜像管理与 tag
1 | docker images |
tag 有个约定俗成的坑:不写 tag 默认拉 latest,但 latest 只是个普通标签,指向的版本会随时间漂移——今天构建和明天构建拉到的可能不是同一个东西。生产环境永远写死具体版本号,postgres:17 而不是 postgres:latest。
Dockerfile:把自己的 Go 服务打成镜像
Dockerfile 是构建镜像的”配方”。Go 程序编译成单个静态二进制,特别适合多阶段构建——编译在带完整工具链的大镜像里做,运行只带二进制本身:
1 | # 阶段一:编译。golang 官方镜像自带完整编译工具链,体积几百 MB(Debian 底座的接近 1 GB),但只在构建时用 |
1 | docker build -t myapp:1.0 . |
多阶段构建的效果立竿见影:单阶段直接用 golang 镜像跑,成品接近 1 GB;两阶段下来通常只有十几 MB。
为什么差距这么大?关键在于镜像是一层层叠出来的,最终镜像 = 基础镜像的所有层 + 你新加的层,叠上去的东西删不掉。单阶段构建时基础镜像是 golang——里面装着 Go 编译器、标准库、git、gcc 这一整套构建工具(Debian 底座解压后就有 800 MB 上下),再叠上 go mod download 拉下来的依赖缓存和你的源码,成品轻松逼近 1 GB。可服务跑起来真正需要的只有那个十几 MB 的静态二进制,剩下全是”编译完就没用了”的东西。多阶段构建的本质就是把这些甩掉:FROM alpine 另起一个只有几 MB 的干净底座,只把二进制 COPY 进去——builder 阶段的所有层都不会进入最终镜像。
docker compose:多容器一把梭
服务一多(web + 数据库 + 缓存),逐个 docker run 又长又难维护。compose 把整套服务声明在一个 compose.yaml 里:
1 | services: |
1 | docker compose up -d |
compose 文件里服务名(如 redis)自动成为容器间互相访问的主机名——web 里连 redis:6379 就通,不用关心容器 IP。
排查与查看:network、inspect 与 docker info
容器出问题时光看 logs 不够,还得能看它的网络接在哪、完整配置是什么、引擎本身的状态如何。这几个都是只读的查看类命令,放心随便敲:
1 | docker network ls |
磁盘满了怎么清理:docker system df 找大头,prune 分级清
Docker 用久了最常见的问题是磁盘被悄悄占满,而且报错往往不直接说“磁盘满了”。
一个不像磁盘问题的报错
在 Mac 上用 Docker Desktop 跑 Doris(一个分析型数据库),建表时报错:
1 | ERROR 1105 (HY000): errCode = 2, detailMessage = Failed to find enough backend, |
字面意思像是副本数配置不对,真正的线索是 capExceedLimit=1:磁盘容量超限。可 Doris 自己的数据只有 19MB,进容器看磁盘:
1 | docker exec doris df -h |
1 | Filesystem Size Used Avail Use% Mounted on |
满的不是 Doris,是整个 Docker。Mac 上的容器跑在 Docker Desktop 的 Linux 虚拟机里,所有镜像、容器、数据卷、构建缓存共用这台虚拟机的一块虚拟磁盘,任何一个容器看到的都是这块盘的总使用率。
Doris 有两个磁盘保护阈值:使用率 storage_flood_stage_usage_percent 和剩余空间 storage_flood_stage_left_capacity_bytes,在 4.1.3 版本上查到的值分别是 95% 和 1GB。这次使用率 99%、剩余不到 500MB,两个都超了,所以 Doris 拒绝建表。
先看谁占的:docker system df
1 | docker system df |
当时的输出:
1 | TYPE TOTAL ACTIVE SIZE RECLAIMABLE |
几列的含义:
- TOTAL:总数。
- ACTIVE:正在被容器使用的数量。
- SIZE:占用的磁盘。
- RECLAIMABLE:清理后能腾出来的空间,也就是没被使用的部分。
这次的大头一目了然:构建缓存能回收 9.9GB,镜像能回收 10.6GB。数据卷几乎都在用,而且里面是数据库数据,不在考虑范围内。
按风险从低到高清理
以下命令和参数基于 Docker 29.8(Docker Desktop for Mac)核对过 --help 和官方文档。每个 prune 命令执行前都会列出要删什么并要求确认,不加 -f 时输入 y 才会真正删除。下面按“删错了的代价”从低到高排:
1 | docker builder prune -f |
docker builder prune 和 docker image prune 不加 -a 时,只删“没名字”和“可再生”的东西,日常清理用这两条就够了。这次就只跑了这两条,Doris 建表恢复正常。加了 -a 的 docker image prune、docker volume prune,以及 docker system prune,范围都大,本机还跑着其他项目时,先用 docker system df -v 确认要删的内容。
治本:调大 Docker Desktop 的磁盘上限
清理只是腾空间。经常构建镜像的话(每次重新构建都会留下旧镜像和缓存),磁盘迟早又满,可以在 Docker Desktop 的 Settings → Resources → Advanced 里调大 Disk usage limit(虚拟磁盘上限),设置项名称以官方文档为准。
常用命令速查表
| 命令 | 作用 |
|---|---|
docker run -d 镜像 |
后台启动容器 |
docker run -p 宿主:容器 |
端口映射,冒号左边是宿主机 |
docker run -v 宿主:容器 |
目录挂载,有状态数据必挂 |
docker run -e KEY=VAL |
设置容器内环境变量 |
docker run --name 名字 |
给容器命名 |
docker run --rm |
退出后自动删除,临时容器用 |
docker run -it 镜像 sh |
交互式进入容器 shell |
docker ps / ps -a |
运行中的容器 / 含已退出的 |
docker logs -f 容器 |
实时跟踪容器日志 |
docker exec -it 容器 sh |
进入运行中的容器 |
docker stop / rm |
停止 / 删除容器 |
docker images |
本地镜像列表 |
docker pull 镜像:tag |
拉取镜像,生产写死具体 tag |
docker rmi 镜像 |
删除本地镜像 |
docker build -t 名:tag . |
按 Dockerfile 构建镜像 |
docker network ls |
列出网络 |
docker network inspect 网络 |
网络详情:网段、网关、容器 IP |
docker inspect 容器/镜像 |
完整配置 JSON |
docker inspect -f 模板 |
只取 JSON 里指定的字段 |
docker port 容器 |
只看端口映射 |
docker info |
引擎配置与状态 |
docker stats |
实时资源占用,容器版 top |
docker system df |
镜像/容器/数据卷磁盘占用 |
docker builder prune -f |
清悬空的构建缓存,-f 跳过确认;下次构建变慢 |
docker image prune -f |
清悬空镜像(<none>),-f 跳过确认 |
docker system prune |
一次清停止的容器、闲置网络、悬空镜像和构建缓存 |
docker compose up -d |
按 compose.yaml 拉起整套服务 |
docker compose down |
整套停掉并删容器 |
docker compose logs -f |
聚合跟踪所有服务日志 |
写到这里
Docker 上手的关键是概念先行:镜像是模板、容器是实例、容器生死跟着主进程走、心疼的数据必须挂载出来。这四句话立住之后,命令都只是查表的事。Dockerfile 多阶段构建和 compose 是从”会用”到”用好”的两步——前者让镜像从 GB 瘦到 MB,后者让多服务环境一条命令起停。再往深走就是网络模型和编排(Kubernetes)的领域了,日常开发部署,本文这套已经够用。









