Docker 容器
1. 定义
Docker 是一个容器化平台:把应用和它运行所需的一切(代码、运行时、依赖库、系统工具、配置)打包成一个标准化的镜像 (image),再以容器 (container) 的形式在任何装了 Docker 的机器上一致地运行。
一句话概括:“在我机器上能跑”这个问题,Docker 用打包整个运行环境的方式彻底解决。
类比:镜像像一个”应用光盘/安装包”,容器就是用这张光盘启动起来的一个”正在运行的实例”。同一个镜像可以跑出无数个容器。
2. Docker 解决了什么问题
传统部署的痛点:
| 问题 | 传统方式 | Docker |
|---|---|---|
| 环境不一致 | 开发/测试/生产环境依赖版本各异,“在我机器上能跑” | 镜像固化整个环境,到哪都一样 |
| 依赖冲突 | 一台机器跑多个应用,Python/Node 版本互相打架 | 每个容器环境隔离,互不干扰 |
| 部署繁琐 | 手动装依赖、配环境,几十步文档 | docker run 一条命令起服务 |
| 资源占用 | 虚拟机每个都带一整套操作系统,重 | 容器共享宿主机内核,轻量、秒级启动 |
2.5 什么时候该用 Docker(典型场景)
不确定”我到底啥时候需要 Docker”时,对照下面这些场景:
| 场景 | 具体例子 | 为什么用 Docker |
|---|---|---|
| 快速跑一个服务,不想装到本机 | 本地起 MySQL、Redis、PostgreSQL、nginx 来开发/测试 | 一条 docker run 就有,用完删掉,不污染系统、不留一堆残留 |
| 用一个工具但不想装它的一堆依赖 | 跑证书生成、代码生成器、格式转换等 CLI 工具 | 作者把工具打包成镜像,挂载目录就能用,省去装环境 |
| 保证团队/上线环境一致 | 开发、测试、生产用同一个镜像 | 消灭”我机器上能跑”,环境完全一致 |
| 一次跑多个配套服务 | web + 数据库 + 缓存 一起起 | docker compose up 一键拉起整套,见第 7 节 |
| 隔离/做实验,随时清空 | 试装可疑软件、练 Linux 命令、搭网络拓扑 | 容器隔离,玩坏了 docker rm 重来一遍即可 |
| 同一软件跑多个版本 | 同时测试 Node 18 和 Node 20、多个 Python 版本 | 每个版本一个容器,互不干扰 |
| 部署到服务器 / 云 / K8s | 把应用打成镜像推上去跑 | 标准化交付,方便扩缩容和编排 |
你在学网络时遇到的那种情况(生成文件)
学网络时”生成文件要用容器”通常是这几类:
- 某个工具被打包成镜像:比如生成 TLS 证书、生成配置文件、抓包分析等工具,直接以镜像分发。你运行容器并挂载本地目录,容器把生成的文件写进这个目录,就留在你电脑上了。
- 搭网络实验环境:
containerlab、GNS3 等用容器模拟多台主机/路由器,在一台电脑上组出虚拟网络拓扑来练习。 - 临时起个网络服务测试:本地起 DNS(dnsmasq/bind)、HTTP 服务等来做实验。
关键技巧是 -v 挂载目录,让容器生成的文件落到宿主机:
# --rm 用完即删容器;-v 把当前目录挂到容器的 /output
# 容器往 /output 写的文件 = 你本地当前目录下的文件
docker run --rm -v $(pwd):/output some-tool-image
# 例:用某镜像生成配置到本地 ./config 目录
docker run --rm -v $(pwd)/config:/output cert-generator没有 -v 的话,容器里生成的文件在容器删除时就一起没了——这也是新手常踩的坑。
3. 容器 vs 虚拟机(核心区别)
虚拟机 (VM) 容器 (Container)
┌─────────────────┐ ┌─────────────────┐
│ App A │ App B │ │ App A │ App B │
├────────┼────────┤ ├────────┼────────┤
│ Guest │ Guest │ ← 各自完整OS │ 依赖 │ 依赖 │
│ OS │ OS │ ├────────┴────────┤
├────────┴────────┤ │ Docker Engine │
│ Hypervisor │ ├─────────────────┤
├─────────────────┤ │ 宿主机内核 │ ← 共享
│ 宿主机 OS │ ├─────────────────┤
└─────────────────┘ │ 宿主机 OS │
└─────────────────┘
- 虚拟机:每个 VM 带一整套 Guest OS,通过 Hypervisor 虚拟硬件。隔离彻底但重(GB 级、启动几十秒)
- 容器:共享宿主机内核,只打包应用 + 依赖。轻量(MB 级、启动毫秒~秒级),密度高
- 代价:容器隔离性弱于 VM,且默认只能跑与宿主机同内核的系统(Linux 容器需 Linux 内核;macOS/Windows 上 Docker 其实跑在一个隐藏的 Linux 虚拟机里)
3.5 和 venv / conda 等”隔离方案”的关系
Docker 常被拿来和 Python 的 venv、conda 比较,因为思路都是”环境隔离、可随时丢弃重建”。但它们隔离的层级差一大截:
venv 隔离的: 只有 Python 包 (site-packages)
└ 共用:操作系统、系统库、Python 解释器本身、其他语言/工具
conda 隔离的: Python 包 + 部分非 Python 的 C 库
└ 共用:操作系统内核、大部分系统环境
Docker 隔离的: 整个用户态环境——OS 发行版、系统库、任意语言运行时、
环境变量、文件系统、网络、进程
└ 共用:只有宿主机内核
用”厨房”的比喻(延续 venv 笔记):
- venv = 同一间厨房里给你一个专属调料架,锅灶水电(OS、解释器)都公用,只有调料(Python 包)各管各的
- Docker = 直接给你一整间独立厨房,灶台、水电、锅具全套隔开
| 维度 | venv | conda | Docker |
|---|---|---|---|
| 隔离范围 | 只有 Python 包 | Python 包 + 部分 C 库 | 整个 OS 用户态 |
| 能隔离非 Python 依赖吗 | 不能 | 部分能 | 能 |
| 语言限制 | 仅 Python | 主要 Python(也支持 R 等) | 语言无关 |
| 轻重 | 极轻(就是个目录) | 中 | 较重(镜像 MB~GB) |
| 隔离强度 | 弱(本质是改 PATH 遮蔽) | 中 | 强(独立文件系统/进程/网络) |
一句话:venv 是”包级隔离”,conda 是”包 + 部分系统库隔离”,Docker 是”系统级隔离”。
- 只是 Python 包/版本打架 → 用
venv/uv就够 - 需要隔离系统库、装非 Python 的东西、或保证换台机器一模一样 → 才上 Docker
⚠️ 别混淆:Homebrew / apt / pnpm / uv 这些是”包管理器”,目标是”方便安装软件/依赖”,装的东西通常是全局共享的,方向和”隔离”相反。 隔离是 venv / conda / Docker 干的事。实际中常配合用:先用 Homebrew 装好 Docker 或 uv,再用它们去做隔离。
4. 核心概念
| 概念 | 是什么 |
|---|---|
| Image(镜像) | 只读模板,包含应用 + 运行环境。分层存储,可复用 |
| Container(容器) | 镜像的运行实例,可启停删。容器内改动默认不持久(删了就没) |
| Dockerfile | 构建镜像的”配方”脚本,逐行指令描述怎么装环境 |
| Registry(仓库) | 存放镜像的地方,Docker Hub 是官方公共仓库 |
| Layer(层) | 镜像由多层叠加而成,每条 Dockerfile 指令生成一层,层可缓存复用 |
| Volume(数据卷) | 把数据存到容器外,实现持久化(数据库数据必用) |
| Network(网络) | 容器间通信的虚拟网络 |
| Docker Compose | 用一个 YAML 文件编排多个容器(如 app + db + redis) |
5. 常用命令
镜像
docker pull nginx # 从仓库拉取镜像
docker images # 列出本地镜像(alias: docker image ls)
docker build -t myapp:1.0 . # 用当前目录 Dockerfile 构建镜像并打标签
docker rmi nginx # 删除镜像
docker tag myapp:1.0 user/myapp:1.0 # 打标签(推送前用)
docker push user/myapp:1.0 # 推送到仓库容器
docker run nginx # 用镜像启动容器(前台)
docker run -d nginx # 后台运行(detached)
docker run -d -p 8080:80 nginx # 端口映射:宿主机8080 -> 容器80
docker run -it ubuntu bash # 交互式进入容器 shell
docker run --name web -d nginx # 指定容器名
docker ps # 列出运行中的容器
docker ps -a # 列出所有容器(含已停止)
docker stop web # 停止容器
docker start web # 启动已停止的容器
docker rm web # 删除容器
docker logs web # 看容器日志
docker logs -f web # 实时跟踪日志
docker exec -it web bash # 进入正在运行的容器执行命令数据卷 / 挂载
docker run -v mydata:/var/lib/mysql mysql # 具名卷持久化数据
docker run -v $(pwd):/app node # 把当前目录挂进容器(开发常用)
docker volume ls # 列出数据卷清理(省磁盘)
docker system prune # 清理停止的容器、无用网络、悬空镜像
docker system prune -a # 更狠:连没被使用的镜像也删
docker container prune # 只清停止的容器
docker image prune # 只清悬空镜像6. Dockerfile 示例(构建镜像的配方)
# 基础镜像
FROM node:20-alpine
# 设置工作目录
WORKDIR /app
# 先拷依赖清单并安装(利用层缓存:代码改了但依赖没改时不重装)
COPY package*.json ./
RUN npm install
# 再拷源码
COPY . .
# 声明容器监听端口(文档作用,不自动映射)
EXPOSE 3000
# 容器启动时执行的命令
CMD ["node", "index.js"]构建并运行:
docker build -t my-node-app .
docker run -d -p 3000:3000 my-node-app关键优化点:把
COPY package*.json+RUN npm install放在COPY . .之前,这样只要依赖没变,改代码重新构建时能命中缓存、跳过装包,大幅加速。
7. Docker Compose 示例(编排多容器)
# docker-compose.yml
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:docker compose up -d # 一键启动所有服务
docker compose down # 停止并删除所有服务
docker compose logs -f # 看所有服务日志8. 常见坑
- 容器数据丢失:容器删了里面的数据就没了。数据库等有状态服务必须用
-v挂数据卷持久化 - 端口没映射就访问不了:容器内跑在 80 端口,宿主机访问要
-p 8080:80显式映射,EXPOSE只是声明不生效 - 镜像越build越大:每层都占空间,装完东西没清缓存。用多阶段构建(multi-stage build)、
alpine基础镜像、合并RUN指令来瘦身 docker run每次都新建容器:反复run会堆一堆容器,想复用已有的用start;调试完记得docker rm或加--rm自动删- Mac/Windows 上性能:容器实际跑在虚拟机里,挂载本地目录(bind mount)做大量文件 IO 会慢
latest标签的陷阱:不指定版本默认拉latest,某天镜像更新会导致环境突变,生产环境应锁定具体版本号- 修改容器内文件不持久:容器内改的东西重建就没了,配置应通过挂载或环境变量注入,而不是进容器手改
9. 延伸阅读 / 关联概念
- Docker Compose — 多容器编排,本地开发多服务(app + db + cache)标配
- Kubernetes (k8s) — 生产级容器编排,管理成百上千容器的调度、扩缩容、自愈;Docker 管单机,k8s 管集群
- 多阶段构建 (multi-stage build) — 在一个 Dockerfile 里分构建阶段和运行阶段,只把产物拷进最终镜像,大幅减小体积
- OCI (Open Container Initiative) — 容器镜像和运行时的开放标准,Docker 镜像遵循它
- containerd / runc — Docker 底层实际干活的容器运行时组件
- 官方文档:https://docs.docker.com/