文章目录
-
- 一、前言
- [二、什么是 Docker](#二、什么是 Docker)
- [三、Docker 的整体架构](#三、Docker 的整体架构)
-
- [3.1 Docker Client](#3.1 Docker Client)
- [3.2 Docker Daemon](#3.2 Docker Daemon)
- [3.3 containerd 与容器运行时](#3.3 containerd 与容器运行时)
- [3.4 镜像仓库](#3.4 镜像仓库)
- [四、Docker 的核心对象](#四、Docker 的核心对象)
-
- [4.1 镜像 Image](#4.1 镜像 Image)
- [4.2 容器 Container](#4.2 容器 Container)
- [4.3 镜像与容器的关系](#4.3 镜像与容器的关系)
- [4.4 数据卷 Volume](#4.4 数据卷 Volume)
- [4.5 Docker 网络](#4.5 Docker 网络)
- [五、Docker 应用的完整运行流程](#五、Docker 应用的完整运行流程)
-
- [5.1 编写 Dockerfile](#5.1 编写 Dockerfile)
- [5.2 构建镜像](#5.2 构建镜像)
- [5.3 推送镜像](#5.3 推送镜像)
- [5.4 创建容器](#5.4 创建容器)
- [5.5 容器运行](#5.5 容器运行)
- [六、Docker 安装与基础验证](#六、Docker 安装与基础验证)
-
- [6.1 添加 Docker 官方软件源](#6.1 添加 Docker 官方软件源)
- [6.2 安装 Docker](#6.2 安装 Docker)
- [6.3 启动 Docker](#6.3 启动 Docker)
- [6.4 验证安装](#6.4 验证安装)
- 七、镜像管理
-
- [7.1 查看镜像](#7.1 查看镜像)
- [7.2 拉取镜像](#7.2 拉取镜像)
- [7.3 查看镜像详情](#7.3 查看镜像详情)
- [7.4 给镜像添加标签](#7.4 给镜像添加标签)
- [7.5 保存和加载镜像](#7.5 保存和加载镜像)
- [7.6 删除镜像](#7.6 删除镜像)
- 八、容器生命周期管理
-
- [8.1 创建并运行容器](#8.1 创建并运行容器)
- [8.2 查看容器](#8.2 查看容器)
- [8.3 停止和启动容器](#8.3 停止和启动容器)
- [8.4 查看日志](#8.4 查看日志)
- [8.5 进入容器](#8.5 进入容器)
- [8.6 查看资源使用情况](#8.6 查看资源使用情况)
- [8.7 删除容器](#8.7 删除容器)
- [九、Docker 数据持久化](#九、Docker 数据持久化)
-
- [9.1 为什么需要持久化](#9.1 为什么需要持久化)
- [9.2 使用数据卷](#9.2 使用数据卷)
- [9.3 使用绑定挂载](#9.3 使用绑定挂载)
- [9.4 Volume 与 Bind Mount 的区别](#9.4 Volume 与 Bind Mount 的区别)
- [十、Docker 网络与服务通信](#十、Docker 网络与服务通信)
-
- [10.1 默认网络](#10.1 默认网络)
- [10.2 端口映射](#10.2 端口映射)
- [10.3 创建自定义网络](#10.3 创建自定义网络)
- [10.4 容器通信原则](#10.4 容器通信原则)
- [十一、使用 Dockerfile 构建应用镜像](#十一、使用 Dockerfile 构建应用镜像)
-
- [11.1 项目结构](#11.1 项目结构)
- [11.2 编写应用](#11.2 编写应用)
- [11.3 编写 Dockerfile](#11.3 编写 Dockerfile)
- [11.4 编写 .dockerignore](#11.4 编写 .dockerignore)
- [11.5 构建镜像](#11.5 构建镜像)
- [11.6 运行应用](#11.6 运行应用)
- [十二、Dockerfile 优化](#十二、Dockerfile 优化)
-
- [12.1 合理利用构建缓存](#12.1 合理利用构建缓存)
- [12.2 使用非 root 用户](#12.2 使用非 root 用户)
- [12.3 多阶段构建](#12.3 多阶段构建)
- [12.4 不在镜像中保存敏感信息](#12.4 不在镜像中保存敏感信息)
- [十三、使用 Docker Compose 编排服务](#十三、使用 Docker Compose 编排服务)
-
- [13.1 Compose 项目结构](#13.1 Compose 项目结构)
- [13.2 编写 Compose 文件](#13.2 编写 Compose 文件)
- [13.3 Compose 核心对象](#13.3 Compose 核心对象)
- [13.4 检查配置](#13.4 检查配置)
- [13.5 启动服务](#13.5 启动服务)
- [13.6 查看日志](#13.6 查看日志)
- [13.7 进入容器](#13.7 进入容器)
- [13.8 停止服务](#13.8 停止服务)
- [十四、Docker 的配置、资源与健康管理](#十四、Docker 的配置、资源与健康管理)
-
- [14.1 环境变量](#14.1 环境变量)
- [14.2 CPU 和内存限制](#14.2 CPU 和内存限制)
- [14.3 自动重启](#14.3 自动重启)
- [14.4 健康检查](#14.4 健康检查)
- 十五、日志、监控与故障排查
-
- [15.1 容器日志](#15.1 容器日志)
- [15.2 资源监控](#15.2 资源监控)
- [15.3 容器启动后立即退出](#15.3 容器启动后立即退出)
- [15.4 端口无法访问](#15.4 端口无法访问)
- [15.5 磁盘空间不足](#15.5 磁盘空间不足)
- 十六、安全与生产实践
-
- [16.1 使用可信基础镜像](#16.1 使用可信基础镜像)
- [16.2 固定镜像版本](#16.2 固定镜像版本)
- [16.3 使用非 root 用户](#16.3 使用非 root 用户)
- [16.4 最小化镜像](#16.4 最小化镜像)
- [16.5 只发布必要端口](#16.5 只发布必要端口)
- [16.6 文件系统尽量只读](#16.6 文件系统尽量只读)
- [16.7 不将 Docker Socket 暴露给普通容器](#16.7 不将 Docker Socket 暴露给普通容器)
- [十七、CI/CD 中的 Docker](#十七、CI/CD 中的 Docker)
-
- [17.1 镜像版本设计](#17.1 镜像版本设计)
- [17.2 镜像不可变](#17.2 镜像不可变)
- [17.3 镜像回滚](#17.3 镜像回滚)
- 十八、常见设计误区
-
- [18.1 把容器当成虚拟机](#18.1 把容器当成虚拟机)
- [18.2 在容器中手工修改配置](#18.2 在容器中手工修改配置)
- [18.3 将重要数据保存在容器层](#18.3 将重要数据保存在容器层)
- [18.4 写死容器 IP](#18.4 写死容器 IP)
- [18.5 在镜像中保存密码](#18.5 在镜像中保存密码)
- [18.6 认为容器运行就等于应用健康](#18.6 认为容器运行就等于应用健康)
- [18.7 所有服务都发布到公网](#18.7 所有服务都发布到公网)
- [18.8 只使用 latest 标签](#18.8 只使用 latest 标签)
- [18.9 使用 docker commit 制作生产镜像](#18.9 使用 docker commit 制作生产镜像)
- [十九、Docker 清理与维护](#十九、Docker 清理与维护)
-
- [19.1 清理停止容器](#19.1 清理停止容器)
- [19.2 清理悬空镜像](#19.2 清理悬空镜像)
- [19.3 清理未使用网络](#19.3 清理未使用网络)
- [19.4 清理构建缓存](#19.4 清理构建缓存)
- [19.5 查看磁盘占用](#19.5 查看磁盘占用)
- [19.6 综合清理](#19.6 综合清理)
- 二十、总结
一、前言
在传统的软件部署模式中,一个应用从开发环境迁移到测试环境和生产环境时,通常需要重新准备:
- 操作系统;
- 语言运行环境;
- 系统依赖;
- 第三方软件;
- 配置文件;
- 启动脚本;
- 网络和存储环境。
不同机器之间的环境差异,很容易造成下面的问题:
text
开发环境运行正常
↓
测试环境缺少依赖
↓
生产环境软件版本不同
↓
应用启动失败
Docker 通过镜像和容器,将应用程序与运行所需的环境封装在一起,使应用能够以一种更加标准化的方式进行构建、交付和运行。
一个完整的 Docker 应用部署体系,不只是执行一条 docker run 命令,还需要解决:
- 镜像如何构建;
- 镜像版本如何管理;
- 容器如何启动;
- 服务如何对外提供访问;
- 容器之间如何通信;
- 数据如何持久化;
- 配置和密钥如何管理;
- 容器如何进行健康检查;
- 服务异常后如何恢复;
- 多个容器如何统一编排;
- 镜像如何进行安全扫描;
- 日志和监控如何建设;
- Docker 如何接入 CI/CD 流水线。
本文将从核心概念、运行流程和工程实践三个角度,系统介绍 Docker 容器化技术。
二、什么是 Docker
Docker 是一种容器化技术,可以将应用程序、运行环境、依赖库和配置文件打包成标准化镜像,再通过镜像创建相互隔离的容器。
例如,一个 Python Web 应用的运行环境可能包括:
text
Linux基础文件
Python运行环境
Python依赖包
应用源代码
配置文件
启动命令
使用 Docker 后,可以将这些内容统一构建成镜像:
text
应用代码
+
运行环境
+
依赖包
+
启动配置
↓
Docker镜像
↓
Docker容器
2.1 Docker 解决了什么问题
环境一致性
开发、测试和生产环境可以运行同一个镜像,减少不同环境之间的差异。
应用隔离
每个容器拥有相对独立的:
- 文件系统;
- 进程空间;
- 网络空间;
- 用户空间;
- 环境变量;
- 资源限制。
快速部署
构建完成的镜像可以直接运行,不需要在每台服务器上重新安装完整依赖。
版本管理
镜像可以通过标签管理不同版本:
text
my-app:1.0.0
my-app:1.1.0
my-app:2.0.0
出现问题时,可以重新运行旧版本镜像完成回滚。
资源利用
容器通常共享宿主机内核,不需要为每个应用启动完整操作系统,因此资源开销通常低于传统虚拟机。
三、Docker 的整体架构
Docker 通常采用客户端与服务端架构。
text
Docker Client
↓
Docker Daemon
↓
containerd
↓
容器运行时
↓
Linux Kernel
3.1 Docker Client
Docker Client 是用户执行命令的入口。
例如:
bash
docker pull nginx
docker build -t my-app:1.0.0 .
docker run -d my-app:1.0.0
docker ps
Docker Client 会将命令发送给 Docker Daemon。
3.2 Docker Daemon
Docker Daemon 负责:
- 管理镜像;
- 创建容器;
- 启动和停止容器;
- 管理网络;
- 管理数据卷;
- 与镜像仓库通信;
- 调用底层容器运行时。
查看 Docker Daemon 状态:
bash
sudo systemctl status docker
3.3 containerd 与容器运行时
Docker Daemon 不会直接完成所有底层容器操作,而是通过 containerd 和相关运行时管理容器生命周期。
简化关系如下:
text
Docker命令
↓
dockerd
↓
containerd
↓
容器运行时
↓
创建和运行容器进程
3.4 镜像仓库
镜像仓库用于保存和分发镜像。
常见镜像仓库包括:
- Docker Hub;
- Harbor;
- GitHub Container Registry;
- 云厂商容器镜像服务;
- 企业内部私有仓库。
完整镜像名称通常表示为:
text
[仓库地址/]命名空间/镜像名称:标签
例如:
text
docker.io/library/nginx:alpine
对应关系为:
| 部分 | 含义 |
|---|---|
docker.io |
镜像仓库 |
library |
命名空间 |
nginx |
镜像名称 |
alpine |
镜像标签 |
四、Docker 的核心对象
Docker 中最重要的对象包括:
- 镜像;
- 容器;
- 仓库;
- 数据卷;
- 网络。
4.1 镜像 Image
镜像是一个标准化、只读的应用程序运行包,其中可以包含:
- 应用代码;
- 语言运行环境;
- 系统依赖;
- 第三方库;
- 配置模板;
- 默认启动命令。
镜像具有两个重要特点:
- 镜像创建后不会被直接修改;
- 镜像由多个只读层组成。
Docker 官方将镜像描述为包含运行容器所需文件、二进制程序、库和配置的标准化包。Docker 镜像说明
4.2 容器 Container
容器是镜像的运行实例。
可以将二者理解为:
text
镜像:应用运行模板
容器:模板启动后的进程
同一个镜像可以创建多个容器:
text
my-app:1.0.0
├── app-container-1
├── app-container-2
└── app-container-3
每个容器可以拥有不同的:
- 容器名称;
- 环境变量;
- 网络配置;
- 数据卷;
- 端口映射;
- CPU 和内存限制。
4.3 镜像与容器的关系
text
Dockerfile
↓
构建镜像
↓
通过镜像创建容器
↓
容器运行应用进程
镜像是静态的,容器是动态运行的。
当容器启动时,Docker 会在只读镜像层之上创建一个可写层。
text
容器可写层
────────────
镜像层3
镜像层2
镜像层1
容器运行期间生成的临时文件会写入可写层。容器被删除后,这些数据通常也会被删除。
重要数据应该存放到数据卷或外部存储中。
4.4 数据卷 Volume
数据卷用于将数据生命周期与容器生命周期分离。
适合存放:
- 数据库文件;
- 用户上传文件;
- 应用日志;
- 消息队列数据;
- 缓存持久化文件;
- 需要跨容器复用的数据。
4.5 Docker 网络
Docker 网络用于解决:
- 容器访问外部网络;
- 宿主机访问容器;
- 容器之间通信;
- 服务名称解析;
- 不同应用之间网络隔离。
五、Docker 应用的完整运行流程
一个应用从代码变成 Docker 服务,通常需要经历以下流程:
text
编写应用代码
↓
编写Dockerfile
↓
构建Docker镜像
↓
为镜像设置版本标签
↓
推送镜像仓库
↓
服务器拉取镜像
↓
创建并启动容器
↓
挂载数据卷
↓
加入Docker网络
↓
发布服务端口
↓
执行健康检查
↓
对外提供服务
5.1 编写 Dockerfile
Dockerfile 描述镜像如何构建,包括:
- 使用哪个基础镜像;
- 应用文件复制到哪里;
- 安装哪些依赖;
- 设置什么环境变量;
- 暴露什么端口;
- 使用什么命令启动应用。
5.2 构建镜像
bash
docker build \
-t my-app:1.0.0 \
.
其中:
text
docker build:构建镜像
-t:设置镜像名称和标签
my-app:镜像名称
1.0.0:版本标签
.:构建上下文目录
5.3 推送镜像
bash
docker push \
registry.example.com/team/my-app:1.0.0
5.4 创建容器
bash
docker run \
-d \
--name my-app \
-p 8080:8080 \
my-app:1.0.0
5.5 容器运行
容器启动后,Docker 会执行镜像中设置的 CMD 或 ENTRYPOINT。
容器的生命周期通常与主进程保持一致:
text
主进程运行
↓
容器保持运行
主进程退出
↓
容器停止
因此,不能通过在容器启动命令后简单追加一个后台进程,来维持容器运行。
六、Docker 安装与基础验证
本文以 Ubuntu 为例。
其他操作系统应该参考相应的官方安装文档。Docker Engine 安装文档
6.1 添加 Docker 官方软件源
bash
sudo apt update
sudo apt install -y \
ca-certificates \
curl
sudo install \
-m 0755 \
-d /etc/apt/keyrings
sudo curl -fsSL \
https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r \
/etc/apt/keyrings/docker.asc
添加软件源:
bash
sudo tee \
/etc/apt/sources.list.d/docker.sources \
> /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
更新索引:
bash
sudo apt update
6.2 安装 Docker
bash
sudo apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
以上步骤参考 Docker 官方 Ubuntu 安装说明。Docker Ubuntu 安装文档
6.3 启动 Docker
bash
sudo systemctl start docker
设置开机启动:
bash
sudo systemctl enable docker
查看状态:
bash
sudo systemctl status docker
6.4 验证安装
bash
sudo docker run hello-world
该命令会完成:
text
Docker客户端连接Daemon
↓
检查本地镜像
↓
从仓库拉取hello-world
↓
创建容器
↓
运行容器程序
↓
输出验证结果
七、镜像管理
7.1 查看镜像
bash
docker image ls
示例:
text
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx alpine a72860cb95fd 2 weeks ago 52MB
redis 7 b123456789ab 3 weeks ago 117MB
字段说明:
| 字段 | 说明 |
|---|---|
| REPOSITORY | 镜像名称 |
| TAG | 镜像标签 |
| IMAGE ID | 镜像标识 |
| CREATED | 创建时间 |
| SIZE | 镜像大小 |
7.2 拉取镜像
bash
docker pull nginx:alpine
如果不指定标签:
bash
docker pull nginx
等价于:
bash
docker pull nginx:latest
生产环境不建议只使用 latest,因为它不能准确表示具体版本。
7.3 查看镜像详情
bash
docker image inspect \
nginx:alpine
可以查看:
- 镜像 ID;
- 镜像层;
- CPU 架构;
- 环境变量;
- 启动命令;
- 工作目录;
- 暴露端口。
7.4 给镜像添加标签
bash
docker image tag \
my-app:latest \
my-app:1.0.0
完整仓库标签:
bash
docker image tag \
my-app:1.0.0 \
registry.example.com/team/my-app:1.0.0
Docker 镜像名称通常决定镜像应该推送到哪个仓库。Docker 镜像构建与发布
7.5 保存和加载镜像
保存镜像:
bash
docker image save \
-o my-app-1.0.0.tar \
my-app:1.0.0
加载镜像:
bash
docker image load \
-i my-app-1.0.0.tar
适合:
- 离线服务器;
- 内网部署;
- 临时迁移;
- 没有镜像仓库的环境。
7.6 删除镜像
bash
docker image rm \
nginx:alpine
清理悬空镜像:
bash
docker image prune
清理所有未被容器使用的镜像:
bash
docker image prune -a
执行清理命令前,需要确认镜像是否还会继续使用。
八、容器生命周期管理
8.1 创建并运行容器
bash
docker run \
-d \
--name web \
-p 8080:80 \
nginx:alpine
参数说明:
text
-d:后台运行
--name:设置容器名称
-p:发布容器端口
端口映射关系:
text
宿主机8080端口
↓
容器80端口
访问地址:
text
http://localhost:8080
8.2 查看容器
查看运行中的容器:
bash
docker ps
查看全部容器:
bash
docker ps -a
查看容器详细配置:
bash
docker inspect web
8.3 停止和启动容器
停止容器:
bash
docker stop web
重新启动:
bash
docker start web
重启容器:
bash
docker restart web
8.4 查看日志
bash
docker logs web
持续跟踪:
bash
docker logs \
-f \
web
查看最后 100 行:
bash
docker logs \
--tail 100 \
web
8.5 进入容器
bash
docker exec \
-it \
web \
/bin/sh
如果镜像中包含 Bash:
bash
docker exec \
-it \
web \
/bin/bash
通常不需要在容器中安装 SSH 服务。
容器排查可以使用:
text
docker exec
docker logs
docker inspect
docker stats
docker top
8.6 查看资源使用情况
bash
docker stats
只查看一次:
bash
docker stats --no-stream
查看容器内进程:
bash
docker top web
8.7 删除容器
删除已停止容器:
bash
docker rm web
强制删除运行中的容器:
bash
docker rm -f web
运行一次性容器:
bash
docker run \
--rm \
alpine \
echo "Hello Docker"
--rm 表示容器退出后自动删除。
九、Docker 数据持久化
9.1 为什么需要持久化
容器自身的可写层与容器生命周期绑定。
如果 MySQL 将数据直接写入容器可写层:
text
MySQL容器
↓
容器可写层
↓
删除容器
↓
数据库文件丢失
因此,数据库和业务文件应该使用:
- Docker Volume;
- Bind Mount;
- 对象存储;
- 外部数据库;
- 分布式存储。
9.2 使用数据卷
创建数据卷:
bash
docker volume create \
mysql-data
查看数据卷:
bash
docker volume ls
运行 MySQL:
bash
docker run \
-d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=change-me \
-v mysql-data:/var/lib/mysql \
mysql:8
挂载关系:
text
Docker数据卷mysql-data
↓
容器目录/var/lib/mysql
删除容器:
bash
docker rm -f mysql
数据卷默认不会随容器一起删除。
9.3 使用绑定挂载
bash
docker run \
-d \
--name web \
-p 8080:80 \
-v "$(pwd)/html:/usr/share/nginx/html:ro" \
nginx:alpine
其中:
text
$(pwd)/html:宿主机目录
/usr/share/nginx/html:容器目录
ro:容器只读
9.4 Volume 与 Bind Mount 的区别
| 对比项 | Volume | Bind Mount |
|---|---|---|
| 管理方式 | Docker 管理 | 用户管理 |
| 路径 | Docker 决定 | 用户指定 |
| 可移植性 | 相对较好 | 依赖宿主机目录 |
| 常见用途 | 数据库持久化 | 配置文件、开发代码 |
| 权限风险 | 相对集中 | 可能暴露宿主机目录 |
十、Docker 网络与服务通信
10.1 默认网络
查看网络:
bash
docker network ls
常见网络包括:
text
bridge
host
none
10.2 端口映射
bash
docker run \
-d \
--name web \
-p 8080:80 \
nginx
如果只允许宿主机本地访问:
bash
docker run \
-d \
--name web \
-p 127.0.0.1:8080:80 \
nginx
需要注意,Docker 的端口发布规则可能与 UFW、firewalld 等主机防火墙的预期行为不同,上线前需要确认实际访问范围。Docker Ubuntu 安装文档
10.3 创建自定义网络
bash
docker network create \
app-network
创建 Web 容器:
bash
docker run \
-d \
--name web \
--network app-network \
nginx
创建客户端容器:
bash
docker run \
-d \
--name client \
--network app-network \
alpine \
sleep 1d
通过容器名访问:
bash
docker exec \
client \
wget -qO- http://web
自定义网络中的容器可以通过服务名或容器名通信,不应依赖可能变化的容器 IP。
10.4 容器通信原则
推荐:
text
应用容器
↓
通过服务名访问
↓
数据库容器
不推荐:
text
应用容器
↓
写死172.18.0.3
↓
数据库容器
容器重建后 IP 可能变化,而服务名称可以保持不变。
十一、使用 Dockerfile 构建应用镜像
下面使用 Python Flask 构建一个简单 Web 应用。
11.1 项目结构
text
docker-demo/
├── app.py
├── requirements.txt
├── Dockerfile
└── .dockerignore
11.2 编写应用
app.py:
python
from flask import Flask
app = Flask(__name__)
@app.get("/")
def index():
return {
"message": "Hello Docker"
}
@app.get("/health")
def health():
return {
"status": "UP"
}
if __name__ == "__main__":
app.run(
host="0.0.0.0",
port=5000
)
注意:
容器内的 Web 服务应该监听
0.0.0.0。如果只监听127.0.0.1,宿主机可能无法通过端口映射访问。
requirements.txt:
text
Flask
gunicorn
11.3 编写 Dockerfile
dockerfile
# syntax=docker/dockerfile:1
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
-r requirements.txt
COPY app.py .
EXPOSE 5000
CMD [
"gunicorn",
"--bind",
"0.0.0.0:5000",
"app:app"
]
主要指令说明:
| 指令 | 作用 |
|---|---|
FROM |
指定基础镜像 |
WORKDIR |
设置工作目录 |
COPY |
复制文件 |
RUN |
构建镜像时执行命令 |
ENV |
设置环境变量 |
EXPOSE |
声明应用端口 |
CMD |
设置默认启动命令 |
ENTRYPOINT |
设置容器入口程序 |
11.4 编写 .dockerignore
text
.git
.venv
__pycache__
*.pyc
.env
test-results
README.md
.dockerignore 可以:
- 减少构建上下文;
- 提高构建速度;
- 防止无关文件进入镜像;
- 避免将敏感配置复制进镜像。
11.5 构建镜像
bash
docker build \
-t flask-demo:1.0.0 \
.
11.6 运行应用
bash
docker run \
-d \
--name flask-demo \
-p 8000:5000 \
flask-demo:1.0.0
访问:
text
http://localhost:8000
健康检查:
bash
curl \
http://localhost:8000/health
十二、Dockerfile 优化
12.1 合理利用构建缓存
推荐:
dockerfile
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
-r requirements.txt
COPY . .
不推荐:
dockerfile
COPY . .
RUN pip install \
--no-cache-dir \
-r requirements.txt
第一种写法中,修改业务代码不会导致依赖安装层立即失效。
12.2 使用非 root 用户
dockerfile
FROM python:3.13-slim
RUN useradd \
--create-home \
appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
-r requirements.txt
COPY --chown=appuser:appuser \
app.py .
USER appuser
EXPOSE 5000
CMD [
"gunicorn",
"--bind",
"0.0.0.0:5000",
"app:app"
]
如果应用不需要 root 权限,就不应该以 root 用户运行。
12.3 多阶段构建
下面以 Java 应用为例:
dockerfile
# syntax=docker/dockerfile:1
FROM maven:3-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn \
clean \
package \
-DskipTests
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build \
/workspace/target/app.jar \
/app/app.jar
EXPOSE 8080
ENTRYPOINT [
"java",
"-jar",
"/app/app.jar"
]
构建阶段包含:
- Maven;
- 编译工具;
- 项目源代码;
- 构建缓存。
运行阶段只包含:
- Java 运行环境;
- 构建完成的 JAR。
多阶段构建能够减少最终镜像中的无关文件和构建工具,从而降低镜像大小和攻击面。Docker 多阶段构建文档
12.4 不在镜像中保存敏感信息
不推荐:
dockerfile
ENV DB_PASSWORD=123456
不应该写入镜像的内容包括:
- 数据库密码;
- API Token;
- SSH 私钥;
- TLS 私钥;
- 云平台密钥;
- 生产环境配置。
十三、使用 Docker Compose 编排服务
一个实际应用通常不只包含一个容器。
例如:
text
Web应用
Redis
MySQL
消息队列
如果逐个执行 docker run,需要手动管理:
- 容器名称;
- 启动顺序;
- 环境变量;
- 网络;
- 数据卷;
- 端口映射。
Docker Compose 可以通过一个 YAML 文件统一管理多个服务。
13.1 Compose 项目结构
text
compose-demo/
├── app.py
├── requirements.txt
├── Dockerfile
├── compose.yaml
└── .dockerignore
13.2 编写 Compose 文件
yaml
services:
web:
build:
context: .
ports:
- "8000:5000"
environment:
REDIS_HOST: redis
depends_on:
redis:
condition: service_healthy
restart: unless-stopped
redis:
image: redis:7-alpine
command:
- redis-server
- --appendonly
- "yes"
volumes:
- redis-data:/data
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
redis-data:
13.3 Compose 核心对象
| 配置项 | 作用 |
|---|---|
services |
定义应用服务 |
image |
指定镜像 |
build |
根据 Dockerfile 构建 |
ports |
发布端口 |
environment |
设置环境变量 |
volumes |
挂载数据 |
depends_on |
定义服务依赖 |
healthcheck |
健康检查 |
restart |
重启策略 |
Compose 默认会为项目创建网络,因此 Web 服务可以直接通过服务名 redis 访问 Redis。
13.4 检查配置
bash
docker compose config
该命令可以检查:
- YAML 格式;
- 环境变量替换;
- 服务定义;
- 网络配置;
- 数据卷配置。
Docker 官方也推荐在排查变量替换和多文件配置问题时查看 Compose 的最终解析结果。Docker Compose 入门文档
13.5 启动服务
bash
docker compose up -d
重新构建并启动:
bash
docker compose up \
-d \
--build
查看状态:
bash
docker compose ps
13.6 查看日志
bash
docker compose logs -f
只查看 Web 服务:
bash
docker compose logs \
-f \
web
13.7 进入容器
bash
docker compose exec \
web \
/bin/sh
13.8 停止服务
bash
docker compose down
同时删除数据卷:
bash
docker compose down -v
注意:
docker compose down -v会删除 Compose 项目中的数据卷,数据库和缓存持久化数据可能因此丢失。
十四、Docker 的配置、资源与健康管理
14.1 环境变量
bash
docker run \
-d \
-e APP_ENV=production \
-e APP_PORT=8080 \
my-app:1.0.0
从文件读取:
bash
docker run \
-d \
--env-file .env \
my-app:1.0.0
普通配置可以使用环境变量,但敏感信息应该使用专门的 Secret 管理系统。
14.2 CPU 和内存限制
限制内存:
bash
docker run \
-d \
--memory 512m \
my-app:1.0.0
限制 CPU:
bash
docker run \
-d \
--cpus 1.0 \
my-app:1.0.0
如果不设置资源限制,单个异常容器可能占用大量宿主机资源。
14.3 自动重启
bash
docker run \
-d \
--restart unless-stopped \
my-app:1.0.0
常见策略:
| 策略 | 说明 |
|---|---|
no |
不自动重启 |
always |
退出后自动重启 |
on-failure |
异常退出时重启 |
unless-stopped |
除非手动停止,否则重启 |
14.4 健康检查
dockerfile
HEALTHCHECK \
--interval=30s \
--timeout=3s \
--retries=3 \
CMD curl -f \
http://localhost:5000/health \
|| exit 1
容器进程存在,不代表应用一定能够正常处理请求。
健康检查可以验证:
- HTTP 接口是否响应;
- 数据库连接是否正常;
- 缓存是否可用;
- 应用是否完成初始化。
十五、日志、监控与故障排查
15.1 容器日志
应用日志建议输出到标准输出和标准错误:
text
stdout
stderr
Docker 可以通过以下命令读取:
bash
docker logs \
-f \
container-name
不要只把日志写到容器内部文件,否则容器删除后日志也可能丢失。
15.2 资源监控
bash
docker stats
重点关注:
- CPU 使用率;
- 内存使用量;
- 网络流量;
- 磁盘读写;
- 进程数量。
15.3 容器启动后立即退出
检查状态:
bash
docker ps -a
查看日志:
bash
docker logs \
container-name
查看退出代码:
bash
docker inspect \
--format='{{.State.ExitCode}}' \
container-name
常见原因包括:
- 启动命令错误;
- 主进程执行完成;
- 环境变量缺失;
- 配置文件错误;
- 端口冲突;
- 文件权限不足;
- 依赖服务不可用。
15.4 端口无法访问
检查端口:
bash
docker port \
container-name
检查容器状态:
bash
docker ps
检查应用监听地址。
如果应用只监听:
text
127.0.0.1
宿主机可能无法通过映射端口访问。
应用通常应该监听:
text
0.0.0.0
15.5 磁盘空间不足
查看 Docker 磁盘使用情况:
bash
docker system df
详细信息:
bash
docker system df -v
可能占用磁盘的内容包括:
- 历史镜像;
- 已停止容器;
- 数据卷;
- 构建缓存;
- 容器日志。
十六、安全与生产实践
16.1 使用可信基础镜像
优先选择:
- Docker Official Images;
- 可信厂商镜像;
- 持续维护的镜像;
- 具有明确版本的镜像;
- 已完成漏洞扫描的镜像。
16.2 固定镜像版本
不推荐:
dockerfile
FROM python:latest
推荐:
dockerfile
FROM python:3.13-slim
如果对构建可复现性要求较高,还可以固定镜像摘要。
16.3 使用非 root 用户
容器内应用没有必要时,不应使用 root 用户运行。
16.4 最小化镜像
生产镜像中不应该包含:
- 编译器;
- Git;
- 调试工具;
- 项目源代码;
- 测试报告;
- 本地缓存;
- 无关软件包。
Docker 官方将多阶段构建、可信基础镜像和合理利用构建缓存列为重要构建实践。Dockerfile 构建最佳实践
16.5 只发布必要端口
如果数据库只需要被同一 Docker 网络中的应用访问,就不应该将数据库端口发布到宿主机公网。
推荐:
yaml
services:
app:
ports:
- "8080:8080"
mysql:
image: mysql:8
MySQL 没有配置 ports,但应用仍然可以通过 Docker 网络访问它。
16.6 文件系统尽量只读
如果应用不需要修改容器文件系统,可以使用:
bash
docker run \
--read-only \
my-app:1.0.0
需要写入的临时目录可以单独挂载。
16.7 不将 Docker Socket 暴露给普通容器
下面的挂载具有很高风险:
bash
-v /var/run/docker.sock:/var/run/docker.sock
能够访问 Docker Socket 的容器,通常可以控制宿主机上的其他容器,甚至获得接近宿主机 root 的权限。
十七、CI/CD 中的 Docker
Docker 常见的持续交付流程如下:
text
开发人员提交代码
↓
CI拉取代码
↓
运行单元测试
↓
构建Docker镜像
↓
执行镜像安全扫描
↓
推送镜像仓库
↓
部署测试环境
↓
执行集成测试
↓
部署生产环境
17.1 镜像版本设计
可以使用:
text
my-app:1.2.0
my-app:git-a1b2c3d
my-app:20261014-001
不建议只保留:
text
my-app:latest
因为 latest 无法准确关联:
- 代码提交;
- 构建时间;
- 发布版本;
- 回滚目标。
17.2 镜像不可变
推荐做法:
text
构建一次镜像
↓
测试环境运行
↓
验证通过
↓
同一镜像部署生产
不推荐:
text
测试环境重新构建一次
生产环境再重新构建一次
多次构建可能因为依赖更新或基础镜像变化产生不同结果。
17.3 镜像回滚
如果新版本出现问题:
text
my-app:1.2.0 运行异常
↓
重新部署 my-app:1.1.0
因此,镜像仓库应保留一定数量的历史版本。
十八、常见设计误区
18.1 把容器当成虚拟机
不推荐在一个容器中同时运行:
text
SSH
Nginx
MySQL
Redis
Java应用
定时任务
更合理的方式是:
text
Nginx容器
Java应用容器
MySQL容器
Redis容器
18.2 在容器中手工修改配置
手工进入容器修改文件,容器重建后修改会丢失。
正式配置应通过:
- Dockerfile;
- 环境变量;
- 数据卷;
- 配置中心;
- Secret 管理系统;
进行管理。
18.3 将重要数据保存在容器层
删除容器后,容器可写层会被删除。数据库和上传文件必须使用持久化存储。
18.4 写死容器 IP
容器重建后 IP 可能发生变化。服务通信应该使用自定义网络和服务名称。
18.5 在镜像中保存密码
镜像可能被导出、共享、缓存或推送到仓库,敏感信息可能从镜像层中被读取。
18.6 认为容器运行就等于应用健康
容器主进程存在,但应用可能已经无法处理请求,因此需要健康检查和业务监控。
18.7 所有服务都发布到公网
数据库、Redis 和消息队列通常只需要被内部应用访问,不应该默认映射到公网。
18.8 只使用 latest 标签
latest 不能清楚表示版本,也不利于问题定位和回滚。
18.9 使用 docker commit 制作生产镜像
docker commit 可以用于临时实验,但生产镜像更适合通过 Dockerfile 构建。
Dockerfile 能够:
- 保存完整构建过程;
- 进行代码审查;
- 重复构建;
- 纳入版本管理;
- 接入 CI/CD。
十九、Docker 清理与维护
19.1 清理停止容器
bash
docker container prune
19.2 清理悬空镜像
bash
docker image prune
19.3 清理未使用网络
bash
docker network prune
19.4 清理构建缓存
bash
docker builder prune
19.5 查看磁盘占用
bash
docker system df
19.6 综合清理
bash
docker system prune
清理未被使用的所有镜像:
bash
docker system prune -a
注意:
清理操作可能删除后续仍需要使用的镜像、容器和构建缓存。涉及数据卷的清理可能造成数据丢失,执行前必须确认影响和备份情况。
二十、总结
Docker 通过镜像和容器,为应用提供了一种标准化的构建、交付和运行方式。
一套完整的 Docker 容器化体系,需要同时理解以下内容:
- Docker Client 与 Docker Daemon;
- 镜像和容器的关系;
- 镜像分层与构建缓存;
- 镜像仓库与版本标签;
- 容器生命周期;
- 端口发布与网络通信;
- Volume 与 Bind Mount;
- Dockerfile 镜像构建;
- 非 root 用户和镜像安全;
- 多阶段构建;
- Docker Compose 服务编排;
- 环境变量和配置管理;
- 健康检查和自动重启;
- 日志、监控和故障排查;
- CI/CD 与镜像回滚。
Docker 应用的核心流程可以概括为:
text
应用代码
↓
Dockerfile
↓
Docker镜像
↓
镜像仓库
↓
Docker容器
↓
网络、存储和配置
↓
对外提供服务
Docker 系统最重要的设计原则可以概括为:
镜像负责定义环境,容器负责运行进程,数据卷负责保存数据,网络负责连接服务,Compose 负责组织应用,而安全、监控和版本管理决定了容器能否稳定运行在生产环境中。
只有将镜像构建、容器运行、数据持久化、网络通信、配置管理和持续交付统一起来,Docker 才不只是一个启动容器的工具,而会成为完整的软件交付基础设施。