Docker 是现代容器化技术中最常见的工具之一。
学习 Docker 的重点并不是记住大量命令,而是理解 Image、Container、Registry、Dockerfile、Layer、Network、Volume、Compose 之间的关系,以及容器出现问题时应该从哪里开始排查。
一、Docker 与容器化
1.1 什么是容器化
传统部署通常需要在服务器上直接安装:
JDKPythonNode.jsMySQLNginx各种依赖不同应用之间可能出现:
版本冲突依赖冲突环境差异部署困难容器化则把应用及其运行环境组织成一个相对独立的运行单元:
应用 +运行依赖 +配置 │ ▼ Container于是可以把应用从:
“在这台机器上安装并运行”转变为:
“运行这个容器”Docker Engine 采用客户端—服务端架构:
docker CLI │ │ Docker API ▼dockerd │ ├── Images ├── Containers ├── Networks └── Volumes其中:
docker:命令行客户端dockerd:Docker 守护进程- Image:镜像
- Container:容器
- Network:网络
- Volume:数据卷
1.2 容器不是虚拟机
容器经常被称为“轻量级虚拟化”,但它与传统虚拟机并不相同。
典型虚拟机:
物理机 │ ▼Hypervisor │ ├── VM │ ├── Guest OS │ └── Application │ └── VM ├── Guest OS └── Application容器:
Host Linux Kernel │ ├── Container │ └── Application │ ├── Container │ └── Application │ └── Container └── Application容器通过 Linux 的命名空间、控制组等机制实现进程、网络、资源等方面的隔离。
因此:
Container≠完整虚拟机而是运行在宿主机内核之上的隔离进程环境。
二、Docker 核心对象
理解 Docker 最重要的一步,就是先把几个核心对象区分开。
Docker
┌──────────────┐│ Registry ││ 镜像仓库 │└──────┬───────┘ │ pull / push ▼┌──────────────┐│ Image ││ 镜像 │└──────┬───────┘ │ run ▼┌──────────────┐│ Container ││ 容器 │└──────────────┘另外:
Dockerfile │ │ build ▼ Image以及:
Container │ ├── Network │ └── Volume / Bind Mount2.1 Image
Image(镜像)可以理解为:
创建容器所使用的只读模板。
例如:
nginxredisubuntupostgres都可以作为镜像。
查看本地镜像:
docker image ls例如:
REPOSITORY TAG IMAGE IDnginx latest ...redis 8 ...ubuntu 24.04 ...镜像本身并不是正在运行的应用。
Image │ └──► docker run │ ▼ Container2.2 Container
Container(容器)是镜像运行后的实例。
例如:
docker run -d --name web nginx这条命令的逻辑可以理解为:
nginx Image │ ▼创建 Container │ ▼启动 nginx查看运行中的容器:
docker ps查看所有容器:
docker ps -a停止:
docker stop web启动:
docker start web删除:
docker rm web因此最核心的关系就是:
Image │ │ run ▼Container同一个镜像可以创建多个容器:
nginx:latest │ ┌─────┼─────┐ ▼ ▼ ▼ web1 web2 web32.3 Registry
Registry(镜像仓库)用于保存和分发镜像。
典型流程:
开发者 │ │ docker build ▼Image │ │ docker push ▼Registry │ │ docker pull ▼服务器常见 Registry 包括:
Docker HubGitHub Container Registry私有 Registry云厂商镜像仓库镜像引用通常类似:
nginx:latestredis:8ubuntu:24.04也可以包含 Registry 地址:
ghcr.io/example/myapp:1.0因此:
Registry→ 存放和分发 Image
Image→ 创建 Container
Container→ 实际运行应用三、Dockerfile、Build 与 Layer
3.1 Dockerfile
Dockerfile 是描述镜像如何构建的文本文件。
Docker 会按照 Dockerfile 中的指令构建镜像。Dockerfile 必须以 FROM 开始建立一个基础构建阶段。
最简单的例子:
FROM nginx:alpine
COPY ./html /usr/share/nginx/html构建:
docker build -t my-nginx:1.0 .这里:
-t my-nginx:1.0表示给镜像命名。
而:
.表示当前目录作为 Build Context。
3.2 常见 Dockerfile 指令
FROM
指定基础镜像:
FROM ubuntu:24.04RUN
在构建阶段执行命令:
RUN apt-get update && \ apt-get install -y curlCOPY
把构建上下文中的文件复制到镜像:
COPY app.jar /app/app.jarWORKDIR
设置工作目录:
WORKDIR /appENV
设置环境变量:
ENV APP_ENV=productionEXPOSE
声明应用预期监听的端口:
EXPOSE 8080需要注意:
EXPOSE≠端口发布EXPOSE 更多是镜像元数据和文档说明,并不会自动把端口发布到宿主机。真正进行端口发布通常需要在运行容器时使用 -p。
CMD
定义默认启动命令:
CMD ["java", "-jar", "app.jar"]ENTRYPOINT
定义容器的主要执行程序:
ENTRYPOINT ["java", "-jar", "app.jar"]CMD 与 ENTRYPOINT 可以组合使用。
例如:
ENTRYPOINT ["java", "-jar", "app.jar"]CMD ["--server.port=8080"]3.3 Docker Build
构建流程:
Dockerfile +Build Context │ ▼ Docker Build │ ├── FROM ├── RUN ├── COPY ├── ... ▼ Image执行:
docker build -t myapp:1.0 .查看镜像:
docker image ls查看镜像详细信息:
docker image inspect myapp:1.03.4 Layer
Docker 镜像不是一个简单的大文件,而是由多个只读层组成。
例如:
Application Image┌─────────────────────┐│ COPY application │ Layer├─────────────────────┤│ RUN install deps │ Layer├─────────────────────┤│ Base Image │ Layers└─────────────────────┘Dockerfile 中的部分指令会形成新的镜像层,而底层层可以被多个镜像复用。
例如:
ubuntu │ ├── app1 └── app2两个镜像可以共享相同的基础层。
这也是 Docker 镜像缓存和分层存储的重要基础。
3.5 Build Cache
假设:
FROM node:alpine
COPY package*.json ./RUN npm install
COPY . .RUN npm run build如果只修改:
源代码而:
package.json没有改变,那么前面的依赖安装步骤通常可以复用缓存。
因此 Dockerfile 一般会尽量把:
变化少的步骤放在:
变化多的步骤之前。
3.6 .dockerignore
构建上下文不应该把所有文件都发送给 Docker。
例如:
.gitnode_modulestarget__pycache__.env可以写入:
.dockerignore减少:
Build Context 大小同时避免把无关文件甚至敏感文件带入构建上下文。Docker 官方 Dockerfile 文档也将 .dockerignore 作为控制构建上下文的重要机制。
四、运行第一个 Docker 容器
4.1 docker run
最简单:
docker run nginx后台运行:
docker run -d nginx指定名称:
docker run -d --name web nginx指定端口:
docker run -d \ --name web \ -p 8080:80 \ nginx此时:
Host:8080 │ ▼Container:80 │ ▼ nginx访问:
http://localhost:80804.2 容器的生命周期
一个简单的生命周期:
create │ ▼created │ ▼running │ ├──── stop ────► stopped │ ▼exited │ └──── rm ──────► deleted查看状态:
docker ps -a启动容器:
docker start web停止:
docker stop web删除:
docker rm web4.3 容器退出不一定代表 Docker 出错
例如:
docker run ubuntu可能立即退出。
原因是:
容器运行时依赖前台主进程如果容器中的主进程结束,容器通常也就结束。
因此:
Container │ └── PID 1 │ └── 应用主进程应用进程退出:
PID 1 exit │ ▼Container exited这也是排查“容器启动后立刻停止”时最重要的思路之一。
五、Docker 网络
Docker Networking 负责容器之间、容器与宿主机、容器与外部网络之间的通信。Linux Docker Engine 默认存在 bridge 网络,也支持 host、none、overlay 等网络驱动。
5.1 查看网络
docker network ls查看网络详情:
docker network inspect bridge5.2 Bridge
Bridge 是 Docker 最常见的网络模式。
可以理解为:
Host │ docker0 / \ / \Container A Container B每个连接到 Bridge 网络的容器都会获得自己的网络接口和 IP 地址。
Docker 的 Bridge 网络默认允许同一网络中的容器互相通信,同时通过 NAT/masquerading 为容器提供外部网络访问能力。
创建自定义 Bridge:
docker network create mynet运行容器:
docker run -d \ --name web \ --network mynet \ nginx再运行一个:
docker run -d \ --name app \ --network mynet \ alpine在同一个用户自定义 Bridge 网络中,容器可以通过容器名称进行 DNS 解析:
appweb这比直接依赖容器 IP 更方便,也更适合实际部署。
5.3 Host
Host 网络模式会减少容器与宿主机之间的网络隔离,容器直接使用宿主机的网络命名空间,因此不会获得独立的容器 IP。
例如:
docker run --network host nginx此时 nginx 如果监听:
80实际上就是使用宿主机对应的网络端口。
因此:
host network→ 更接近直接运行在 Host 上需要注意,在 host 模式下使用 -p 进行端口映射没有意义,Docker 会忽略这些发布端口选项。
5.4 Container 网络
Docker 还支持共享其他容器的网络命名空间,例如:
docker run --network container:web ...此时两个容器共享同一个网络栈。
这种方式使用场景相对特殊,日常部署中更常见的是:
自定义 bridge5.5 端口映射
最常见:
docker run -d \ -p 8080:80 \ nginx含义:
宿主机 8080 │ ▼容器 80可以进一步指定宿主机 IP:
docker run -d \ -p 127.0.0.1:8080:80 \ nginx这样发布端口只绑定到宿主机的本地回环地址。
Docker 官方文档指出,如果不指定宿主机地址,端口发布默认可能绑定到宿主机所有地址,因此发布端口时需要注意暴露范围。
5.6 端口映射与容器间通信
假设:
web └── 80
app └── 8080两者位于:
mynet那么:
web → app:8080并不需要:
-p 8080:8080因为同一 Docker 网络中的容器本身就可以直接通信。
因此:
容器之间通信→ Docker Network
外部访问容器→ Publish Port是两个不同的问题。
六、Docker Volume 与 Bind Mount
容器的可写层并不适合作为重要业务数据的唯一存储位置。
Docker 官方将 Volume 和 Bind Mount 作为两类主要的文件系统挂载方式。
6.1 为什么需要持久化
假设:
Container │ └── /data数据库把数据存放在那里。
然后删除容器:
docker rm db如果数据只存在容器自身的可写层,那么数据也可能随容器一起消失。
因此:
Container+Persistent Storage应该分开理解。
6.2 Volume
创建 Volume:
docker volume create db-data查看:
docker volume ls使用:
docker run -d \ --name mysql \ -v db-data:/var/lib/mysql \ mysql结构:
Docker Managed Volume │ ▼ db-data │ ▼Container:/var/lib/mysqlVolume 的存储位置由 Docker 管理。
Docker 官方将 Volume 定位为适合持久化容器数据,以及在多个容器之间共享数据的一种机制。
6.3 Bind Mount
Bind Mount 则直接把宿主机某个文件或目录挂载到容器中。
例如:
docker run -d \ -v /opt/myapp/config:/app/config \ myapp:1.0结构:
Host└── /opt/myapp/config │ ▼Container└── /app/configBind Mount 的特点是:
宿主机路径明确可见Docker 不负责选择存储目录因此特别适合:
配置文件源码构建产物开发环境共享目录Docker 官方也明确区分了两者:Volume 的数据目录由 Docker 管理,而 Bind Mount 直接使用宿主机指定的路径。
6.4 Volume 与 Bind Mount 对比
| 项目 | Volume | Bind Mount |
|---|---|---|
| 存储路径 | Docker 管理 | 用户指定 |
| 宿主机可见性 | 不强调具体路径 | 明确 |
| 典型用途 | 数据库、持久数据 | 配置、代码、开发 |
| 可移植性 | 相对更好 | 更依赖宿主机目录结构 |
| 管理方式 | docker volume | 文件系统路径 |
可以简单记成:
Volume→ “数据交给 Docker 管理”
Bind Mount→ “数据目录由我指定”七、Docker Logs
容器中的应用通常应该把日志输出到:
stdoutstderrDocker 再负责收集这些输出。
查看日志:
docker logs web持续查看:
docker logs -f web只查看最近内容:
docker logs --tail 100 web带时间:
docker logs -t web因此:
Application │ ├── stdout └── stderr │ ▼ Docker Logging │ ▼ docker logsDocker 的具体日志驱动可以配置,Compose 中也可以进一步配置 service 的 logging 行为。
排查容器启动失败时:
docker ps -adocker logs <container>往往是最先应该执行的两条命令。
八、Docker 资源限制
一个常见误区是:
容器隔离了→ 那么它就不会影响宿主机实际上,如果没有合理限制,容器中的程序仍可能大量消耗宿主机资源。
因此 Docker 支持对容器进行资源限制。
8.1 内存限制
例如:
docker run -d \ --memory=512m \ nginx表示限制容器可以使用的内存。
Docker run 支持包括内存限制、CPU 等多种运行时约束。
8.2 CPU 限制
例如:
docker run -d \ --cpus="1.5" \ myapp表示限制 CPU 使用量。
Compose 中也可以定义资源限制,例如:
services: app: image: myapp:1.0 deploy: resources: limits: cpus: "1.0" memory: 512MCompose Deploy Specification 将 CPU、Memory、PIDs 等限制定义为资源约束。
8.3 为什么需要资源限制
没有资源限制:
Container │ ├── CPU 高 └── Memory 高 │ ▼ Host │ ▼其他服务受到影响有资源限制:
Container │ ├── CPU ≤ limit └── Memory ≤ limit这样可以减少单个工作负载拖垮整台机器的风险。
九、Healthcheck
“容器在运行”并不一定意味着:
应用正常例如:
Container: runningProcess: runningApplication: 无法处理请求因此 Docker 提供 Healthcheck。
9.1 HEALTHCHECK
Dockerfile:
HEALTHCHECK --interval=30s \ --timeout=5s \ --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1健康检查返回:
0 → healthy1 → unhealthy容器启动后会经历:
starting │ ▼healthy或者:
starting │ ▼unhealthyDocker 官方文档明确说明,Healthcheck 用于判断容器中的应用是否仍然正常工作,而不仅仅是查看容器进程是否存在。
查看:
docker inspect <container>可以找到:
State.Health9.2 Compose Healthcheck
Compose 也可以定义:
services: app: image: myapp:1.0 healthcheck: test: - CMD - curl - -f - http://localhost:8080/health interval: 30s timeout: 5s retries: 3还可以配合:
depends_on: db: condition: service_healthy让依赖服务在健康检查通过后再启动依赖它的服务。
十、Docker Compose
10.1 为什么需要 Compose
假设一个 Web 项目包含:
NginxBackendMySQLRedis如果全部使用 docker run:
docker run ...docker run ...docker run ...docker run ...命令会非常多。
Compose 可以把多个服务写进一个 YAML 文件。
compose.yaml │ ├── web ├── backend ├── mysql └── redis然后统一管理。
Docker 官方将 Compose 定义为用于多容器应用的配置方式,可以在一个 YAML 文件中管理服务、网络和卷。
10.2 一个简单的 Compose
services: web: image: nginx:alpine ports: - "8080:80"
redis: image: redis:8
db: image: postgres:18 environment: POSTGRES_PASSWORD: example启动:
docker compose up -d查看:
docker compose ps查看日志:
docker compose logs单独看某个服务:
docker compose logs web停止:
docker compose down重新构建:
docker compose build启动并构建:
docker compose up -d --build10.3 Compose 中的服务
Compose 的核心单位是:
services:例如:
services: backend: image: my-backend:1.0
redis: image: redis:8其中:
backendredis是服务名。
同一个 Compose 项目里的服务可以通过服务名进行访问:
backend → redis:6379而不需要寻找 Redis 容器的实际 IP。
10.4 Compose 网络
Compose 默认会为项目创建网络,使服务之间可以通过服务名称通信。
例如:
frontend │ ▼backend │ ▼redis应用可以配置:
REDIS_HOST=redisREDIS_PORT=6379而不是:
REDIS_HOST=172.18.0.3这种方式更适合容器动态创建和替换的环境。
10.5 Compose Volume
例如数据库:
services: db: image: postgres:18 volumes: - db-data:/var/lib/postgresql/data
volumes: db-data:这里:
db-data是命名 Volume。
也可以使用 Bind Mount:
services: app: image: myapp:1.0 volumes: - ./config:/app/config10.6 Compose 完整示例
一个稍完整的 Web 应用:
services:
backend: build: . ports: - "8080:8080" environment: REDIS_HOST: redis REDIS_PORT: 6379 depends_on: redis: condition: service_healthy
redis: image: redis:8 volumes: - redis-data:/data healthcheck: test: - CMD - redis-cli - ping interval: 10s timeout: 3s retries: 5
volumes: redis-data:这里已经把前面几个概念串起来了:
Dockerfile │ ▼ Build │ ▼Image │ ▼Compose Service │ ▼Container │ ├── Network ├── Healthcheck └── Volume十一、Docker 常见故障排查
学习 Docker 最重要的内容之一,就是出现问题时能够确定:
到底是镜像问题?容器问题?网络问题?存储问题?应用问题?推荐按照:
状态 ↓日志 ↓进程 ↓网络 ↓存储 ↓资源逐层检查。
十二、容器启动故障
12.1 容器启动后立即退出
首先:
docker ps -a看:
STATUS例如:
Exited (1)然后:
docker logs <container>再查看:
docker inspect <container>重点关注:
EntrypointCmdEnvironmentMountsNetworkSettingsState典型原因:
启动命令错误配置文件错误环境变量缺失依赖服务不可用权限错误端口冲突12.2 Exit Code
容器退出状态可以提供重要线索。
例如:
Exited (1)通常表示应用返回了非零退出状态。
如果是:
Exited (0)则更可能是:
主进程正常结束所以排查时不要只看:
容器没启动而应该继续问:
主进程为什么退出?十三、Docker 网络故障
13.1 端口没有暴露
检查:
docker ps看:
PORTS例如:
0.0.0.0:8080->80/tcp表示:
Host 8080 ↓Container 80如果没有:
-p 8080:80即使 nginx 在容器内部监听 80,也不代表外部客户端可以通过宿主机 8080 访问。
13.2 容器内部服务监听错误地址
一个很常见的问题:
应用监听:127.0.0.1:8080而其他容器访问:
container:8080可能无法连接。
因为:
127.0.0.1表示当前容器自己的回环地址。
容器内的服务如果需要被其他容器访问,通常需要监听适当的容器网络地址,例如:
0.0.0.0:8080这是 Web 应用容器化时非常常见的问题。
13.3 DNS 问题
例如:
backend │ └── redis:6379如果:
redis解析失败,就应该检查:
docker network inspect <network>以及:
docker exec -it backend getent hosts redis13.4 网络排查顺序
可以按照:
容器是否运行 ↓网络是否连接 ↓DNS 是否解析 ↓目标端口是否监听 ↓防火墙 / 网络策略 ↓应用是否接受请求逐层排查。
十四、Docker 存储故障
14.1 容器删除后数据丢失
检查:
docker inspect <container>查看:
Mounts如果数据库没有挂载:
Volume或者:
Bind Mount那么重要数据可能只存在容器可写层。
14.2 Volume 不生效
常见问题:
路径写错权限不对挂载目标错误容器内原有数据被挂载覆盖检查:
docker inspect <container>确认:
SourceDestinationRW14.3 宿主机磁盘不足
容器大量写数据时,也可能把宿主机磁盘写满。
检查:
df -h以及 Docker 磁盘使用:
docker system df清理无用资源时可以使用:
docker system prune但是生产环境执行清理命令前必须确认要删除的对象,避免误删仍然需要的资源。
十五、Docker 镜像故障
15.1 镜像拉取失败
例如:
docker pull nginx失败时检查:
DNS网络Registry认证镜像名称Tag代理配置15.2 镜像不存在
例如:
pull access denied可能是:
镜像名称错误仓库不存在仓库为私有未登录 Registry可以检查:
docker image ls以及:
docker login15.3 镜像构建失败
执行:
docker build -t myapp:1.0 .失败时重点检查:
DockerfileBuild Context基础镜像依赖下载网络文件路径权限缓存特别需要注意:
COPY只能访问:
Build Context中的文件。
因此:
docker build -t myapp .中的:
.并不是随便写的,它决定了 Docker 可以看到哪些构建输入。
十六、Docker 资源故障
如果:
容器运行很慢不能直接判断成:
Docker 性能差应该先检查:
docker stats查看容器:
CPUMemoryNetwork I/OBlock I/O例如:
docker stats可能发现:
backendCPU 185%MEM 900MB
redisCPU 5%MEM 300MB此时就可以进一步定位:
backend→ CPU 异常再继续查看:
应用日志线程请求代码而不是直接修改 Docker 配置。
十七、Docker 故障排查总模型
面对一个容器问题,可以建立如下思维模型:
Docker 故障 │ ┌──────────────┼──────────────┐ │ │ │ ▼ ▼ ▼ Image Container Runtime │ │ │ build/pull state/logs resource │ │ │ └──────────────┼──────────────┘ │ ┌───────────┴───────────┐ ▼ ▼ Network Storage │ │ DNS / port / bridge volume / mount │ │ └───────────┬───────────┘ ▼ Application实际排查时,可以形成一个固定顺序:
1. docker ps -a ↓2. docker logs ↓3. docker inspect ↓4. docker stats ↓5. docker network inspect ↓6. 检查 Volume / Bind Mount ↓7. 宿主机系统资源 ↓8. 回到应用本身十八、Docker 整体模型
到这里,可以把 Docker 的核心知识串成一个完整体系:
Registry │ pull / push │ ▼ Image │ Dockerfile / Build │ ▼ Container / | \ / | \ ▼ ▼ ▼ Network Volume Logs │ │ │ ▼ │ Persistent │ Data │ ▼ Port Mapping │ ▼ External ClientCompose 则站在更高一层:
compose.yaml │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ backend redis db │ │ │ ▼ ▼ ▼ Container Container Container │ │ │ └─────────────┼─────────────┘ ▼ Network因此可以这样理解:
Dockerfile→ 怎么构建镜像
Image→ 应用运行模板
Container→ 镜像运行实例
Registry→ 镜像存储与分发
Network→ 容器怎么通信
Volume / Bind Mount→ 数据放在哪里
Logs→ 应用出了什么问题
Healthcheck→ 应用现在是否健康
Resource Limit→ 容器最多能消耗多少资源
Compose→ 多个容器如何组合成一个应用十九、从运维角度理解 Docker
对于运维而言,Docker 最重要的并不是:
docker rundocker stopdocker rm这些命令本身。
真正重要的是理解:
应用 │ ├── Image │ ├── Container │ ├── Network │ ├── Storage │ ├── Resource │ ├── Logs │ └── Health当一个服务出现故障:
Web 访问失败应该逐层问:
Container 在运行吗? ↓应用进程存在吗? ↓日志有没有报错? ↓端口有没有监听? ↓端口映射正确吗? ↓Network 正常吗? ↓依赖服务正常吗? ↓Volume 是否正常? ↓磁盘是否满? ↓CPU / Memory 是否异常?这比单纯背诵 Docker 命令更接近真正的容器运维。
二十、总结
Docker 可以归纳成下面这条主线:
Dockerfile │ │ build ▼ Image │ │ run ▼Container │ ├── Network │ ├── bridge │ ├── host │ └── port mapping │ ├── Storage │ ├── Volume │ └── Bind Mount │ ├── Logs ├── Healthcheck └── Resource Limits而当多个 Container 共同组成一个完整应用时:
Compose │ ├── Service ├── Network ├── Volume └── Healthcheck因此,学习 Docker 最终应该形成这样的认识:
镜像负责“装什么”,容器负责“跑起来”,网络负责“怎么通信”,存储负责“数据放哪里”,Compose 负责“多个服务怎么一起运行”,而运维负责“出现问题时找到到底是哪一层出了问题”。