1. Docker 是什么
Docker 是一个用于构建、分发和运行应用程序的容器化平台。
它可以把应用程序以及运行应用所需要的依赖,例如:
- 操作系统基础文件
- JDK、Node.js、Python 等运行环境
- 第三方依赖
- 配置文件
- 启动命令
统一打包成一个标准化的镜像,然后在开发、测试和生产环境中运行。
Docker 解决的核心问题,可以用一句话概括:
将应用程序及其依赖打包成标准化镜像,使应用能够在不同环境中以相同的方式运行。
在没有 Docker 时,我们经常会遇到下面的问题:
- 开发环境运行正常,测试环境却无法运行
- 不同服务器安装的软件版本不一致
- 部署应用前需要手动安装大量依赖
- 多个应用依赖不同版本的 JDK、MySQL 或 Node.js
- 新服务器的环境搭建过程复杂且容易出错
- 应用升级失败后不容易快速回滚
使用 Docker 后,可以把运行环境和应用程序一起交付,从而减少环境差异带来的问题。
2. Docker 与虚拟机有什么区别
Docker 容器和虚拟机都可以实现环境隔离,但它们的实现方式并不相同。
2.1 虚拟机
虚拟机通过 Hypervisor 在物理机上模拟完整的计算机。
每个虚拟机通常包含:
- 完整的操作系统
- 系统内核
- 系统工具
- 应用程序
- 应用依赖
因此,虚拟机通常占用更多的磁盘、内存和 CPU。
2.2 Docker 容器
Docker 容器不需要运行完整的客户机操作系统。
多个容器可以共享宿主机内核,但每个容器拥有独立的:
- 文件系统
- 进程空间
- 网络空间
- 用户空间
- 资源限制
- 环境变量
Linux 容器主要依赖以下内核能力:
- Namespace:实现进程、网络、文件系统等资源隔离
- Cgroups:限制和统计 CPU、内存等资源
- Union File System:实现镜像分层与文件复用
- Capabilities:拆分传统 root 用户的系统权限
- Seccomp:限制容器能够执行的系统调用
2.3 Docker 与虚拟机对比
| 对比项 | Docker 容器 | 虚拟机 |
|---|---|---|
| 操作系统 | 共享宿主机内核 | 每台虚拟机都有完整操作系统 |
| 启动速度 | 通常为秒级甚至更快 | 通常需要几十秒或更长 |
| 磁盘占用 | 相对较小 | 相对较大 |
| 资源消耗 | 较低 | 较高 |
| 隔离程度 | 进程级隔离 | 操作系统级隔离 |
| 部署密度 | 高 | 相对较低 |
| 典型用途 | 微服务、持续集成、应用交付 | 强隔离、多操作系统运行 |
需要注意的是:
容器提供了隔离能力,但容器并不天然等同于完整的安全沙箱。
对于安全要求较高的场景,仍然需要配合权限控制、镜像扫描、网络隔离和运行时安全策略。
3. Docker 的核心概念
学习 Docker 时,需要重点理解以下几个概念:
- 镜像
- 容器
- Dockerfile
- 镜像仓库
- 数据卷
- 网络
4. Docker 镜像
Docker 镜像是创建容器的只读模板。
一个镜像通常包含:
- 基础文件系统
- 应用程序
- 运行环境
- 第三方依赖
- 默认环境变量
- 启动命令
例如:
text
nginx:alpine
mysql:8.4
redis:7.4-alpine
eclipse-temurin:21-jre
其中:
text
nginx:alpine
可以拆分为:
text
镜像名称:nginx
镜像标签:alpine
如果没有明确指定标签,Docker 通常会使用 latest:
bash
docker pull nginx
等价于:
bash
docker pull nginx:latest
但是生产环境不建议长期依赖 latest,因为它指向的镜像内容可能发生变化。
更推荐使用明确版本:
bash
docker pull nginx:1.27-alpine
对于要求更严格的场景,还可以使用镜像摘要锁定内容:
text
nginx@sha256:具体摘要值
标签可以被重新指向,而摘要对应的是确定的镜像内容。
5. Docker 镜像为什么要分层
Docker 镜像由多个只读层组成。
例如,一个 Java 应用镜像可能包含:
text
应用 JAR
↓
JRE 运行环境
↓
Linux 基础文件系统
Dockerfile 中的大部分构建指令都会形成新的镜像层:
dockerfile
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/app.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
镜像分层具有以下优点:
5.1 复用公共层
多个镜像可以共享同一个基础镜像层,减少磁盘占用。
5.2 提高构建速度
没有变化的层可以直接使用构建缓存。
5.3 提高传输效率
推送或者拉取镜像时,只需要传输本地不存在的层。
5.4 方便版本管理
每次构建都可以生成新的镜像,便于部署和回滚。
容器启动后,Docker 会在镜像的只读层之上增加一个可写层:
text
容器可写层
应用镜像层
运行环境层
基础系统层
直接写入容器文件系统的数据,默认写在容器可写层中。
如果删除容器,这部分数据通常也会被删除。因此,数据库、上传文件等重要数据应该存储在数据卷或者外部存储中。
6. Docker 容器
容器是镜像的运行实例。
可以把镜像理解为类,把容器理解为根据这个类创建出来的对象。
同一个镜像可以启动多个容器:
bash
docker run -d --name nginx-1 -p 8081:80 nginx:alpine
docker run -d --name nginx-2 -p 8082:80 nginx:alpine
虽然两个容器使用相同的镜像,但它们拥有各自独立的:
- 容器名称
- 进程
- 网络
- 文件系统可写层
- 环境变量
- 端口映射
- 生命周期
7. Docker 的整体架构
Docker 采用客户端与服务端架构。
主要组件包括:
text
Docker CLI
↓
Docker Daemon
↓
containerd
↓
runc
↓
Linux Container
7.1 Docker CLI
Docker CLI 是我们在终端中使用的 docker 命令。
例如:
bash
docker pull nginx
docker build -t demo-app:1.0.0 .
docker run -d nginx
docker ps
Docker CLI 本身通常不直接创建容器,而是向 Docker Daemon 发送请求。
7.2 Docker Daemon
Docker Daemon,也就是 dockerd,负责管理:
- 镜像
- 容器
- 网络
- 数据卷
- 构建任务
- Docker API
7.3 containerd
containerd 负责容器生命周期管理,例如:
- 创建容器
- 启动容器
- 停止容器
- 管理容器镜像
- 管理容器快照
7.4 runc
runc 是底层 OCI 容器运行时,负责按照 OCI 标准真正创建和启动容器进程。
7.5 Registry
Registry 是存储和分发 Docker 镜像的服务。
常见镜像仓库包括:
- Docker Hub
- Harbor
- 云厂商容器镜像服务
- 企业内部私有镜像仓库
8. 安装完成后的基础检查
安装 Docker 后,可以执行:
bash
docker version
查看 Docker 客户端和服务端版本。
执行:
bash
docker info
查看 Docker 运行环境、存储驱动、容器数量和镜像数量等信息。
运行测试容器:
bash
docker run --rm hello-world
如果能够正常拉取并运行镜像,说明 Docker 基础环境已经可以使用。
其中:
text
--rm
表示容器停止后自动删除容器。
9. 第一个 Docker 容器
使用下面的命令启动一个 Nginx 容器:
bash
docker run -d \
--name my-nginx \
-p 8080:80 \
nginx:alpine
Windows PowerShell 可以写成一行:
powershell
docker run -d --name my-nginx -p 8080:80 nginx:alpine
参数说明:
| 参数 | 作用 |
|---|---|
docker run |
创建并启动容器 |
-d |
在后台运行 |
--name my-nginx |
指定容器名称 |
-p 8080:80 |
将宿主机 8080 端口映射到容器 80 端口 |
nginx:alpine |
使用的镜像 |
启动成功后访问:
text
http://localhost:8080
端口映射格式为:
text
宿主机端口:容器端口
因此:
text
8080:80
表示访问宿主机的 8080 端口时,请求会被转发到容器的 80 端口。
查看正在运行的容器:
bash
docker ps
查看包括已停止容器在内的全部容器:
bash
docker ps -a
停止容器:
bash
docker stop my-nginx
重新启动容器:
bash
docker start my-nginx
删除容器:
bash
docker rm my-nginx
强制删除运行中的容器:
bash
docker rm -f my-nginx
10. 常用镜像命令
10.1 搜索镜像
bash
docker search nginx
10.2 拉取镜像
bash
docker pull nginx:alpine
10.3 查看本地镜像
bash
docker image ls
也可以使用:
bash
docker images
10.4 查看镜像详细信息
bash
docker image inspect nginx:alpine
10.5 查看镜像构建历史
bash
docker image history nginx:alpine
10.6 删除镜像
bash
docker image rm nginx:alpine
简写形式:
bash
docker rmi nginx:alpine
10.7 给镜像添加标签
bash
docker tag demo-app:1.0.0 registry.example.com/team/demo-app:1.0.0
10.8 导出镜像
bash
docker save -o demo-app-1.0.0.tar demo-app:1.0.0
10.9 导入镜像
bash
docker load -i demo-app-1.0.0.tar
11. 常用容器命令
11.1 创建并启动容器
bash
docker run -d --name demo-app -p 8080:8080 demo-app:1.0.0
11.2 只创建容器,不启动
bash
docker create --name demo-app demo-app:1.0.0
11.3 启动容器
bash
docker start demo-app
11.4 停止容器
bash
docker stop demo-app
Docker 停止容器时,通常会先向容器主进程发送终止信号,并等待一段时间;如果进程仍未退出,再强制结束。
可以指定等待时间:
bash
docker stop -t 30 demo-app
11.5 重启容器
bash
docker restart demo-app
11.6 查看日志
bash
docker logs demo-app
持续查看日志:
bash
docker logs -f demo-app
查看最后 100 行日志:
bash
docker logs --tail 100 demo-app
查看最近 10 分钟的日志:
bash
docker logs --since 10m demo-app
11.7 进入容器
bash
docker exec -it demo-app /bin/sh
如果镜像中安装了 Bash:
bash
docker exec -it demo-app /bin/bash
极简镜像中可能没有 Bash,甚至可能没有 Shell。
11.8 在容器中执行命令
bash
docker exec demo-app java -version
11.9 查看容器详细信息
bash
docker inspect demo-app
查看容器 IP:
bash
docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" demo-app
11.10 查看容器进程
bash
docker top demo-app
11.11 查看资源使用情况
bash
docker stats
只查看指定容器:
bash
docker stats demo-app
11.12 复制文件
从宿主机复制到容器:
bash
docker cp ./application.yml demo-app:/app/application.yml
从容器复制到宿主机:
bash
docker cp demo-app:/app/logs ./logs
docker cp 更适合临时排查,不建议把它作为正式发布流程。
12. 容器的前台与后台运行
执行:
bash
docker run nginx:alpine
容器会在前台运行,终端将直接显示容器输出。
执行:
bash
docker run -d nginx:alpine
容器会在后台运行。
需要注意,容器是否继续运行取决于容器中的主进程是否仍然存在。
容器不是传统虚拟机,不应该仅仅通过启动一个后台服务来维持运行。容器的主进程应该以前台方式运行。
例如,错误的思路是:
text
启动应用到后台
启动脚本立即退出
容器随之停止
正确的思路是让应用进程成为容器的前台主进程。
13. Dockerfile 是什么
Dockerfile 是用于描述镜像构建过程的文本文件。
它定义了:
- 使用哪个基础镜像
- 工作目录是什么
- 复制哪些文件
- 安装哪些依赖
- 暴露哪些端口
- 设置哪些环境变量
- 容器如何启动
一个简单的 Java 应用 Dockerfile:
dockerfile
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/demo-app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
构建镜像:
bash
docker build -t demo-app:1.0.0 .
其中:
| 内容 | 说明 |
|---|---|
docker build |
构建镜像 |
-t demo-app:1.0.0 |
设置镜像名称和标签 |
. |
使用当前目录作为构建上下文 |
启动容器:
bash
docker run -d \
--name demo-app \
-p 8080:8080 \
demo-app:1.0.0
14. Dockerfile 常用指令
14.1 FROM
指定基础镜像:
dockerfile
FROM eclipse-temurin:21-jre
Dockerfile 通常以 FROM 开始。
14.2 WORKDIR
设置工作目录:
dockerfile
WORKDIR /app
后续的 COPY、RUN、CMD 等指令都会以该目录作为当前目录。
与多次使用 cd 相比,WORKDIR 更清晰可靠。
14.3 COPY
复制构建上下文中的文件到镜像:
dockerfile
COPY target/demo-app.jar app.jar
复制目录:
dockerfile
COPY config/ /app/config/
一般情况下优先使用 COPY。
14.4 ADD
ADD 也可以复制文件,并具有自动解压本地压缩包等额外能力:
dockerfile
ADD app.tar.gz /app/
如果不需要这些额外能力,优先使用语义更明确的 COPY。
14.5 RUN
在构建镜像时执行命令:
dockerfile
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
RUN 发生在镜像构建阶段,不是在容器每次启动时执行。
14.6 ENV
设置环境变量:
dockerfile
ENV JAVA_OPTS="-Xms256m -Xmx512m"
容器运行时也可以覆盖:
bash
docker run -e JAVA_OPTS="-Xms512m -Xmx1024m" demo-app:1.0.0
不要通过 ENV、ARG 或镜像文件保存密码、访问令牌等敏感信息,因为这些内容可能出现在镜像层或者构建记录中。
14.7 ARG
定义构建阶段变量:
dockerfile
ARG APP_VERSION=1.0.0
构建时传入:
bash
docker build \
--build-arg APP_VERSION=1.1.0 \
-t demo-app:1.1.0 .
ARG 主要用于构建阶段,ENV 则可以保留到容器运行阶段。
14.8 EXPOSE
声明应用监听端口:
dockerfile
EXPOSE 8080
EXPOSE 只是镜像元数据,不会自动把端口发布到宿主机。
真正发布端口仍然需要:
bash
docker run -p 8080:8080 demo-app:1.0.0
14.9 CMD
设置容器的默认启动命令或默认参数:
dockerfile
CMD ["java", "-jar", "app.jar"]
运行容器时传入命令,可以覆盖 CMD:
bash
docker run demo-app:1.0.0 java -version
14.10 ENTRYPOINT
设置容器的固定入口:
dockerfile
ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT 常用于定义主要程序,CMD 可以为它提供默认参数:
dockerfile
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--spring.profiles.active=prod"]
运行时可以替换默认参数:
bash
docker run demo-app:1.0.0 --spring.profiles.active=test
14.11 USER
指定容器运行用户:
dockerfile
USER appuser
生产环境应尽量避免让业务应用以 root 用户运行。
14.12 HEALTHCHECK
定义容器健康检查:
dockerfile
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
需要确保镜像内存在健康检查命令所依赖的工具。
15. CMD 与 ENTRYPOINT 的区别
下面是一个常见组合:
dockerfile
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--spring.profiles.active=prod"]
最终执行效果类似于:
bash
java -jar app.jar --spring.profiles.active=prod
如果运行:
bash
docker run demo-app:1.0.0 --spring.profiles.active=test
最终效果类似于:
bash
java -jar app.jar --spring.profiles.active=test
可以这样理解:
ENTRYPOINT:容器固定运行什么程序CMD:给程序提供默认参数docker run后面的内容:通常覆盖CMD
对于需要正确接收停止信号的应用,推荐使用 JSON 数组形式:
dockerfile
ENTRYPOINT ["java", "-jar", "app.jar"]
不推荐:
dockerfile
ENTRYPOINT java -jar app.jar
JSON 数组形式不会额外经过 Shell,更有利于信号正确传递给应用进程。
16. 使用多阶段构建 Java 镜像
传统方式通常需要先在宿主机执行 Maven 打包,然后再构建 Docker 镜像。
多阶段构建可以直接在 Dockerfile 中完成编译和运行镜像的制作。
dockerfile
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /workspace
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY src ./src
RUN mvn -B clean package -DskipTests
FROM eclipse-temurin:21-jre
WORKDIR /app
RUN groupadd --system app \
&& useradd --system --gid app --home-dir /app app
COPY --from=builder /workspace/target/*.jar app.jar
RUN chown app:app app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
构建镜像:
bash
docker build -t demo-app:1.0.0 .
多阶段构建的优点:
- 构建环境和运行环境分离
- 最终镜像不包含 Maven
- 最终镜像不包含源代码
- 减少镜像体积
- 减少不必要的软件和攻击面
- 构建流程更容易标准化
17. .dockerignore 文件
Docker 构建时会把构建上下文发送给构建器。
如果目录中包含 .git、日志、临时文件等无关内容,会导致:
- 构建上下文变大
- 构建速度变慢
- 缓存更容易失效
- 敏感文件可能被意外复制
- 镜像构建过程不稳定
可以创建 .dockerignore:
text
.git
.gitignore
.idea
.vscode
target
logs
*.log
.env
README.md
docker-compose.yml
需要根据项目实际情况调整。
如果 Dockerfile 依赖宿主机已经生成的 target 目录,就不能忽略 target。
18. Dockerfile 最佳实践
18.1 使用明确的镜像版本
不推荐:
dockerfile
FROM eclipse-temurin:latest
推荐:
dockerfile
FROM eclipse-temurin:21-jre
对供应链可重复性要求更高时,可以进一步使用镜像摘要。
18.2 使用较小的运行镜像
构建环境通常包含大量编译工具,不应该全部放进运行镜像。
推荐使用多阶段构建。
18.3 合理利用构建缓存
下面的写法会导致源代码变化时重新下载全部 Maven 依赖:
dockerfile
COPY . .
RUN mvn clean package
更合理的写法:
dockerfile
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package
将变化频率较低的文件放在前面,可以提高缓存命中率。
18.4 不要把敏感信息写入镜像
不要这样做:
dockerfile
ENV DATABASE_PASSWORD=123456
即使后续删除,敏感内容也可能仍然存在于历史镜像层中。
密码、密钥和访问令牌应在运行时通过安全的配置系统注入。
18.5 使用非 root 用户
dockerfile
RUN groupadd --system app \
&& useradd --system --gid app app
USER app
18.6 一个容器运行一个主要职责
通常建议:
- Java 应用一个容器
- MySQL 一个容器
- Redis 一个容器
- Nginx 一个容器
这里的"一个职责"并不等于容器中只能有一个进程,而是容器应该围绕一个明确的服务职责设计。
18.7 正确处理应用日志
容器中的应用日志优先输出到:
- 标准输出
- 标准错误
这样可以通过下面的命令查看:
bash
docker logs demo-app
生产环境再由日志驱动或日志采集系统统一收集。
19. Docker 数据存储
容器自身的可写层通常不适合存储重要数据。
Docker 常见的数据存储方式包括:
- Volume
- Bind Mount
- tmpfs
20. Volume 数据卷
Volume 是由 Docker 管理的数据存储方式。
创建数据卷:
bash
docker volume create mysql-data
查看数据卷:
bash
docker volume ls
查看详情:
bash
docker volume inspect mysql-data
启动 MySQL 并挂载数据卷:
bash
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=change-me \
-v mysql-data:/var/lib/mysql \
-p 3306:3306 \
mysql:8.4
Windows PowerShell 单行写法:
powershell
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=change-me -v mysql-data:/var/lib/mysql -p 3306:3306 mysql:8.4
挂载格式:
text
数据卷名称:容器内目录
删除容器:
bash
docker rm -f mysql
只要没有删除 mysql-data,数据通常仍然保留。
删除数据卷:
bash
docker volume rm mysql-data
删除数据卷可能导致数据不可恢复,操作前应确认数据是否已经备份。
21. Bind Mount 目录挂载
Bind Mount 是把宿主机文件或目录直接挂载到容器中。
例如:
bash
docker run -d \
--name nginx \
-p 8080:80 \
-v /opt/nginx/html:/usr/share/nginx/html:ro \
nginx:alpine
其中:
text
/opt/nginx/html
是宿主机目录。
text
/usr/share/nginx/html
是容器内目录。
text
ro
表示容器只读挂载。
Bind Mount 常用于:
- 本地开发时代码挂载
- 配置文件挂载
- 日志目录挂载
- 需要明确管理宿主机路径的场景
它的缺点是容器与宿主机目录结构耦合较强。
22. Volume 与 Bind Mount 对比
| 对比项 | Volume | Bind Mount |
|---|---|---|
| 管理者 | Docker | 用户 |
| 宿主机路径 | Docker 管理 | 用户明确指定 |
| 可移植性 | 较好 | 依赖宿主机目录 |
| 典型场景 | 数据库持久化 | 开发代码、配置文件 |
| 使用难度 | 相对简单 | 需要处理路径和权限 |
| 推荐程度 | 生产数据优先考虑 | 适合明确的目录映射 |
23. tmpfs 临时存储
在 Linux 环境中,tmpfs 可以把数据保存在内存中:
bash
docker run -d \
--name temp-app \
--tmpfs /app/tmp \
demo-app:1.0.0
适合存放:
- 临时文件
- 缓存数据
- 不希望写入磁盘的数据
容器停止后,tmpfs 中的数据会丢失。
24. Docker 网络
Docker 常见网络驱动包括:
- bridge
- host
- none
- overlay
- macvlan
单机容器最常用的是 bridge 网络。
25. Bridge 网络
创建自定义网络:
bash
docker network create demo-network
查看网络:
bash
docker network ls
启动 MySQL:
bash
docker run -d \
--name mysql \
--network demo-network \
-e MYSQL_ROOT_PASSWORD=change-me \
-e MYSQL_DATABASE=demo \
mysql:8.4
启动应用:
bash
docker run -d \
--name demo-app \
--network demo-network \
-e DB_HOST=mysql \
-p 8080:8080 \
demo-app:1.0.0
在同一个自定义网络中,应用可以通过容器名称访问 MySQL:
text
mysql:3306
不应该写成:
text
localhost:3306
因为容器中的 localhost 指向容器自身,而不是另一个容器,也不是宿主机。
自定义 Bridge 网络通常能够提供容器名称解析,因此多个服务之间不需要依赖不稳定的容器 IP 地址。
26. Host 和 None 网络
26.1 Host 网络
Host 网络模式下,容器直接使用宿主机网络:
bash
docker run --network host nginx:alpine
它减少了部分网络转换,但网络隔离能力也会降低。
Docker Desktop 通常运行在虚拟化环境中,其 Host 网络行为可能与原生 Linux 不完全相同,因此需要结合实际运行环境验证。
26.2 None 网络
None 网络模式表示容器不接入普通外部网络:
bash
docker run --network none alpine
适用于不需要网络或者需要自行配置网络的特殊场景。
27. 端口映射与容器通信
启动容器时:
bash
docker run -d -p 8080:80 nginx:alpine
这里的 8080 用于宿主机或宿主机外部访问。
如果两个容器已经在同一个 Docker 网络中,它们通常直接通过:
text
容器名称:容器内部端口
通信,而不是通过宿主机映射端口。
例如,应用连接 MySQL:
text
jdbc:mysql://mysql:3306/demo
其中:
mysql是服务名或者容器名3306是 MySQL 在容器中监听的端口
28. Docker Compose 是什么
当一个项目包含多个容器时,逐个执行 docker run 会变得非常复杂。
例如一个后端项目可能需要:
- Java 应用
- MySQL
- Redis
- Nginx
Docker Compose 可以通过一个 YAML 文件统一定义和管理多个服务。
常用命令使用 docker compose,这是 Docker Compose v2 的命令形式。
29. Docker Compose 实战
下面是一个 Java、MySQL 和 Redis 的 Compose 示例:
yaml
services:
app:
build:
context: .
dockerfile: Dockerfile
image: demo-app:1.0.0
container_name: demo-app
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: "jdbc:mysql://mysql:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"
SPRING_DATASOURCE_USERNAME: "demo"
SPRING_DATASOURCE_PASSWORD: "${MYSQL_PASSWORD}"
SPRING_DATA_REDIS_HOST: "redis"
SPRING_DATA_REDIS_PORT: "6379"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
networks:
- demo-network
mysql:
image: mysql:8.4
container_name: demo-mysql
environment:
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
MYSQL_DATABASE: "demo"
MYSQL_USER: "demo"
MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test:
[
"CMD-SHELL",
"mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"
]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
networks:
- demo-network
redis:
image: redis:7.4-alpine
container_name: demo-redis
command: ["redis-server", "--appendonly", "yes"]
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- demo-network
volumes:
mysql-data:
redis-data:
networks:
demo-network:
driver: bridge
创建 .env 文件:
dotenv
MYSQL_ROOT_PASSWORD=replace-with-strong-root-password
MYSQL_PASSWORD=replace-with-strong-app-password
不要把真实的 .env 文件提交到公开代码仓库。
可以在 .gitignore 中添加:
text
.env
启动全部服务:
bash
docker compose up -d
同时重新构建应用镜像:
bash
docker compose up -d --build
查看服务状态:
bash
docker compose ps
查看全部日志:
bash
docker compose logs
持续查看应用日志:
bash
docker compose logs -f app
停止并删除容器和网络:
bash
docker compose down
同时删除 Compose 数据卷:
bash
docker compose down -v
-v 会删除 Compose 管理的数据卷,数据库数据可能因此丢失,执行前必须确认。
30. depends_on 不等于应用一定可用
仅仅配置:
yaml
depends_on:
- mysql
通常只代表 Compose 会按照依赖顺序启动容器,并不代表 MySQL 已经完成初始化并且能够接收连接。
更可靠的方式是:
- 为依赖服务定义
healthcheck - 使用健康状态作为依赖条件
- 应用自身仍然实现连接重试和失败恢复
即使 Compose 检测到 MySQL 健康,应用也需要考虑运行期间数据库重启、网络抖动等情况。
31. Docker Compose 常用命令
31.1 启动服务
bash
docker compose up -d
31.2 构建镜像
bash
docker compose build
禁用构建缓存:
bash
docker compose build --no-cache
31.3 查看状态
bash
docker compose ps
31.4 查看日志
bash
docker compose logs -f
查看指定服务:
bash
docker compose logs -f app
31.5 重启服务
bash
docker compose restart app
31.6 扩容服务
bash
docker compose up -d --scale app=3
如果多个实例都映射同一个固定宿主机端口,会产生端口冲突。实际扩容时一般由反向代理或编排平台统一提供流量入口。
31.7 停止服务
bash
docker compose stop
31.8 删除服务
bash
docker compose down
31.9 查看最终配置
bash
docker compose config
这个命令可以帮助检查:
- YAML 格式
- 环境变量替换
- 服务合并后的最终配置
- 网络和数据卷定义
32. 容器资源限制
如果不设置资源限制,异常容器可能消耗大量 CPU 或内存。
使用 docker run 限制内存:
bash
docker run -d \
--name demo-app \
--memory 512m \
demo-app:1.0.0
同时限制内存和 CPU:
bash
docker run -d \
--name demo-app \
--memory 512m \
--cpus 1.5 \
demo-app:1.0.0
Compose 示例:
yaml
services:
app:
image: demo-app:1.0.0
mem_limit: 512m
cpus: 1.5
资源限制需要结合应用特性设置。
对于 Java 应用,还需要关注:
- JVM 最大堆内存
- 元空间
- 线程栈
- 直接内存
- JIT 编译内存
- 容器中的其他本地内存
不能简单地把 JVM 最大堆设置为容器全部内存。
例如容器限制为 1 GB 时,可以根据应用情况把最大堆设置为较低的比例,并通过监控继续调整。
33. 容器重启策略
Docker 支持以下常见重启策略:
| 策略 | 说明 |
|---|---|
no |
默认值,不自动重启 |
always |
容器退出后总是尝试重启 |
unless-stopped |
除非被手动停止,否则自动重启 |
on-failure |
只有非正常退出时才重启 |
使用示例:
bash
docker run -d \
--name demo-app \
--restart unless-stopped \
demo-app:1.0.0
Compose 中:
yaml
services:
app:
image: demo-app:1.0.0
restart: unless-stopped
重启策略可以提升服务恢复能力,但不能代替:
- 健康检查
- 日志告警
- 应用重试
- 数据一致性设计
- 服务监控
34. 镜像仓库的使用
登录镜像仓库:
bash
docker login registry.example.com
给镜像添加仓库标签:
bash
docker tag demo-app:1.0.0 registry.example.com/backend/demo-app:1.0.0
推送镜像:
bash
docker push registry.example.com/backend/demo-app:1.0.0
拉取镜像:
bash
docker pull registry.example.com/backend/demo-app:1.0.0
退出登录:
bash
docker logout registry.example.com
一个常见的镜像命名结构如下:
text
registry.example.com/backend/demo-app:1.0.0
可以拆分为:
text
镜像仓库地址:registry.example.com
项目或命名空间:backend
镜像名称:demo-app
镜像标签:1.0.0
35. 镜像版本设计
不推荐只发布:
text
demo-app:latest
更推荐同时维护明确版本,例如:
text
demo-app:1.4.2
demo-app:1.4
demo-app:latest
也可以使用提交标识:
text
demo-app:git-a1b2c3d
生产部署时建议使用不可变版本:
text
demo-app:1.4.2
或者直接使用镜像摘要。
这样能够:
- 明确当前部署版本
- 快速回滚
- 避免
latest指向变化 - 方便定位镜像与代码的对应关系
- 提升发布流程的可审计性
36. Docker 日志管理
查看容器日志:
bash
docker logs -f demo-app
Docker 默认日志如果没有控制,可能持续占用磁盘空间。
可以在启动容器时限制日志大小:
bash
docker run -d \
--name demo-app \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
demo-app:1.0.0
Compose 配置:
yaml
services:
app:
image: demo-app:1.0.0
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
生产环境通常还会把日志采集到:
- Elasticsearch
- Loki
- 云日志服务
- 企业日志平台
应用应该尽量把日志写入标准输出和标准错误,再由容器平台统一采集。
37. Docker 常见问题排查
37.1 容器启动后立即退出
查看容器状态:
bash
docker ps -a
查看日志:
bash
docker logs demo-app
查看退出码:
bash
docker inspect demo-app --format "{{.State.ExitCode}}"
常见原因:
- 启动命令执行失败
- 配置文件不存在
- 环境变量缺失
- 端口被占用
- 应用连接数据库失败
- 容器主进程执行结束
- 文件权限不足
- 镜像架构不兼容
- 内存不足被系统终止
37.2 宿主机无法访问容器
检查端口映射:
bash
docker port demo-app
查看容器是否正在运行:
bash
docker ps
确认应用是否监听:
text
0.0.0.0
如果应用只监听:
text
127.0.0.1
它可能只能在容器内部访问。
还需要检查:
- 宿主机防火墙
- 云服务器安全组
- 端口是否冲突
- 应用实际监听端口
- Docker 网络配置
37.3 容器无法连接数据库
不要把另一个容器的地址写成:
text
localhost
同一 Docker 网络中应该使用数据库服务名:
text
mysql
例如:
text
jdbc:mysql://mysql:3306/demo
检查网络:
bash
docker network inspect demo-network
检查数据库日志:
bash
docker logs demo-mysql
37.4 磁盘空间不足
查看 Docker 磁盘占用:
bash
docker system df
查看详细信息:
bash
docker system df -v
可能占用磁盘的内容包括:
- 未使用镜像
- 已停止容器
- 构建缓存
- 未使用数据卷
- 未限制大小的容器日志
37.5 镜像构建缓存没有生效
可能原因:
- 过早执行了
COPY . . - 构建上下文中的文件频繁变化
- 依赖文件与源代码一起复制
- 使用了
--no-cache - 基础镜像发生变化
- 构建参数发生变化
可以调整 Dockerfile 指令顺序,让变化较少的内容放在前面。
38. Docker 清理命令
删除已停止容器:
bash
docker container prune
删除未使用网络:
bash
docker network prune
删除未使用镜像:
bash
docker image prune
删除构建缓存:
bash
docker builder prune
删除未使用数据卷:
bash
docker volume prune
清理未使用的容器、网络、镜像和构建缓存:
bash
docker system prune
包含未被任何容器使用的镜像:
bash
docker system prune -a
清理命令可能删除后续仍需要使用的镜像或数据。
尤其是数据卷清理,执行前必须确认是否包含重要数据。
39. Docker 安全实践
39.1 不要使用来源不明的镜像
优先选择:
- 官方镜像
- 可信机构发布的镜像
- 企业内部经过审核的镜像
39.2 使用非 root 用户
dockerfile
USER app
即使应用存在安全漏洞,也可以降低攻击者直接获得高权限的风险。
39.3 减少镜像中的软件
不要在运行镜像中保留不必要的:
- 编译器
- 调试工具
- 包管理缓存
- Shell 工具
- 源代码
- 测试文件
39.4 不要把 Docker Socket 挂载给普通容器
下面的挂载风险很高:
text
/var/run/docker.sock:/var/run/docker.sock
能够操作 Docker Socket 的容器,通常可以控制宿主机上的其他容器,甚至获得接近宿主机 root 的能力。
39.5 限制 Linux Capabilities
可以删除容器不需要的权限:
bash
docker run \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
demo-app:1.0.0
具体权限需要根据应用实际行为确定。
39.6 使用只读文件系统
对于不需要修改根文件系统的应用:
bash
docker run \
--read-only \
--tmpfs /tmp \
demo-app:1.0.0
39.7 限制资源
bash
docker run \
--memory 512m \
--cpus 1 \
demo-app:1.0.0
资源限制可以降低单个异常容器影响整台服务器的风险。
39.8 扫描镜像漏洞
发布前应该通过镜像扫描工具检查:
- 操作系统软件包漏洞
- 语言依赖漏洞
- 敏感信息
- 不安全配置
- 软件许可证问题
39.9 定期更新基础镜像
即使业务代码没有变化,基础镜像中的系统组件也可能出现安全漏洞。
应该建立定期重新构建和扫描机制,而不是让镜像构建完成后永久不变。
40. Docker 在开发环境中的作用
Docker 可以快速启动开发依赖。
例如启动 Redis:
bash
docker run -d \
--name local-redis \
-p 6379:6379 \
redis:7.4-alpine
启动 MySQL:
bash
docker run -d \
--name local-mysql \
-e MYSQL_ROOT_PASSWORD=change-me \
-e MYSQL_DATABASE=demo \
-p 3306:3306 \
-v local-mysql-data:/var/lib/mysql \
mysql:8.4
这样不需要直接在开发电脑中安装和维护多个数据库版本。
当项目结束时,可以停止并删除容器,而不影响系统中的其他项目。
41. Docker 在 CI/CD 中的作用
一个典型的持续集成和部署流程如下:
text
开发人员提交代码
↓
CI 拉取代码
↓
执行单元测试
↓
构建应用程序
↓
构建 Docker 镜像
↓
执行镜像扫描
↓
推送到镜像仓库
↓
部署测试环境
↓
执行接口或集成测试
↓
部署生产环境
Docker 镜像可以作为标准发布物。
同一个镜像经过测试后再部署到生产环境,可以减少"测试环境和生产环境运行内容不一致"的问题。
需要注意:
应该在不同环境中复用同一个镜像,通过运行时配置区分环境,而不是为测试和生产分别修改代码并重新构建不同内容。
42. Docker 与 Kubernetes 的关系
Docker 主要解决:
- 应用如何打包
- 镜像如何构建
- 容器如何运行
- 单机多容器如何管理
当容器数量和服务器数量增加后,还需要解决:
- 多服务器调度
- 服务发现
- 自动扩容
- 滚动更新
- 故障迁移
- 配置管理
- 密钥管理
- 负载均衡
Kubernetes 是用于大规模编排和管理容器化应用的平台。
可以简单理解为:
text
Docker:构建和运行容器
Kubernetes:在集群中调度和管理容器
需要注意的是,Kubernetes 已经不直接依赖 Docker Engine 作为唯一运行方式,而是通过容器运行时接口使用符合要求的容器运行时。
但是使用 Docker 构建出来的 OCI 镜像,通常仍然可以部署到 Kubernetes。
43. Docker 典型发布流程
假设当前版本为 1.0.0。
43.1 构建镜像
bash
docker build -t demo-app:1.0.0 .
43.2 本地启动验证
bash
docker run -d \
--name demo-app-test \
-p 8080:8080 \
demo-app:1.0.0
43.3 查看日志
bash
docker logs -f demo-app-test
43.4 执行健康检查
bash
curl http://localhost:8080/actuator/health
43.5 添加仓库标签
bash
docker tag \
demo-app:1.0.0 \
registry.example.com/backend/demo-app:1.0.0
43.6 推送镜像
bash
docker push registry.example.com/backend/demo-app:1.0.0
43.7 服务器拉取镜像
bash
docker pull registry.example.com/backend/demo-app:1.0.0
43.8 使用新版本启动
bash
docker run -d \
--name demo-app-1.0.0 \
-p 8080:8080 \
--restart unless-stopped \
registry.example.com/backend/demo-app:1.0.0
真实生产环境还应该包含:
- 配置注入
- 密钥管理
- 数据库变更
- 健康检查
- 流量切换
- 监控告警
- 失败回滚
- 操作审计
44. Docker 常见误区
44.1 容器就是轻量级虚拟机
容器和虚拟机的使用体验有相似之处,但底层实现不同。
容器通常共享宿主机内核,不包含完整的客户机操作系统。
44.2 删除容器不会丢数据
如果数据只保存在容器可写层中,删除容器后数据通常也会被删除。
重要数据必须存储在:
- Docker Volume
- Bind Mount
- 外部数据库
- 对象存储
- 分布式存储
44.3 EXPOSE 会自动开放端口
EXPOSE 只是声明容器使用的端口,不会自动创建宿主机端口映射。
仍然需要:
bash
docker run -p 8080:8080 demo-app
44.4 容器之间通过 localhost 通信
容器中的 localhost 指向当前容器自身。
不同容器之间应该通过 Docker 网络和服务名称通信。
44.5 使用 latest 最方便
latest 虽然方便,但不利于:
- 版本确认
- 问题定位
- 部署审计
- 快速回滚
- 构建复现
生产环境应该使用明确版本或镜像摘要。
44.6 容器正常运行就表示应用健康
容器进程存在,不代表应用一定能够正常处理请求。
应用可能出现:
- 数据库连接失败
- 线程池耗尽
- 依赖服务异常
- 内存持续增长
- 接口无法响应
因此需要健康检查和业务监控。
44.7 Docker 能自动解决所有部署问题
Docker 统一了交付形式,但不会自动解决:
- 数据一致性
- 数据库迁移
- 服务容错
- 分布式事务
- 配置安全
- 监控告警
- 容量规划
- 应用自身缺陷
Docker 是应用交付工具,不是所有架构问题的万能解决方案。
45. Docker 常用命令速查表
镜像
bash
docker pull nginx:alpine
docker image ls
docker image inspect nginx:alpine
docker image history nginx:alpine
docker image rm nginx:alpine
docker build -t demo-app:1.0.0 .
docker tag demo-app:1.0.0 registry.example.com/demo-app:1.0.0
docker push registry.example.com/demo-app:1.0.0
容器
bash
docker run -d --name demo-app -p 8080:8080 demo-app:1.0.0
docker ps
docker ps -a
docker logs -f demo-app
docker exec -it demo-app /bin/sh
docker inspect demo-app
docker stats demo-app
docker stop demo-app
docker start demo-app
docker restart demo-app
docker rm demo-app
docker rm -f demo-app
数据卷
bash
docker volume create app-data
docker volume ls
docker volume inspect app-data
docker volume rm app-data
docker volume prune
网络
bash
docker network create demo-network
docker network ls
docker network inspect demo-network
docker network connect demo-network demo-app
docker network disconnect demo-network demo-app
docker network rm demo-network
Docker Compose
bash
docker compose config
docker compose build
docker compose up -d
docker compose up -d --build
docker compose ps
docker compose logs -f
docker compose restart app
docker compose stop
docker compose down
系统信息与清理
bash
docker version
docker info
docker system df
docker system df -v
docker container prune
docker image prune
docker network prune
docker builder prune
docker system prune
46. Docker 学习路线
可以按照以下顺序学习 Docker:
第一阶段:基础使用
掌握:
- 镜像与容器的区别
docker pulldocker rundocker psdocker logsdocker execdocker stopdocker rm
第二阶段:镜像构建
掌握:
- Dockerfile
- 构建上下文
- 镜像分层
- 构建缓存
.dockerignore- 多阶段构建
第三阶段:数据与网络
掌握:
- Volume
- Bind Mount
- Bridge 网络
- 容器名称解析
- 端口映射
第四阶段:多容器应用
掌握:
- Docker Compose
- 服务依赖
- 健康检查
- 环境变量
- 数据持久化
- 日志与重启策略
第五阶段:生产实践
掌握:
- 私有镜像仓库
- CI/CD
- 镜像漏洞扫描
- 非 root 运行
- 资源限制
- 日志采集
- 监控告警
- 版本回滚
- 容器编排
47. 总结
Docker 的核心价值不是简单地把应用"装进一个盒子",而是建立一套标准化的软件交付方式。
通过 Docker,我们可以:
- 统一应用运行环境
- 减少开发、测试和生产环境差异
- 快速启动和销毁服务
- 提高服务器资源利用率
- 简化持续集成和持续部署
- 通过镜像版本实现快速回滚
- 让应用交付过程更加标准化和可重复
学习 Docker 时,最重要的是理解下面几个关系:
text
Dockerfile 用于描述镜像如何构建
↓
docker build 根据 Dockerfile 构建镜像
↓
docker run 根据镜像创建并启动容器
↓
Volume 保存需要持久化的数据
↓
Network 负责容器之间的通信
↓
Docker Compose 统一管理多个容器
如果只记住一句话,可以记住:
镜像是应用的标准化交付物,容器是镜像运行起来后的实例,Dockerfile 定义镜像的构建过程,Docker Compose 负责组织多个相互协作的容器。