Docker 从入门到实践:镜像、容器、Dockerfile 与 Docker Compose 详解

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

后续的 COPYRUNCMD 等指令都会以该目录作为当前目录。

与多次使用 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

不要通过 ENVARG 或镜像文件保存密码、访问令牌等敏感信息,因为这些内容可能出现在镜像层或者构建记录中。

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 已经完成初始化并且能够接收连接。

更可靠的方式是:

  1. 为依赖服务定义 healthcheck
  2. 使用健康状态作为依赖条件
  3. 应用自身仍然实现连接重试和失败恢复

即使 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 pull
  • docker run
  • docker ps
  • docker logs
  • docker exec
  • docker stop
  • docker 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 负责组织多个相互协作的容器。

相关推荐
梦梦代码精1 小时前
基于UniApp+Vue3+ThinkPHP 8,这套知识付费系统的架构设计有点东西
java·低代码·docker·uni-app·开源·php
用户3169353811832 小时前
ry.bat和ry.sh部署
运维
mennekes2 小时前
自动化产线临时配电:明装工业插座 vs 面板式工业插座 vs 模块化插座箱取舍
运维·科技·安全·制造
tedcloud1232 小时前
book-to-skill 怎么部署?把技术书和项目文档变成 AI Agent 可复用知识
linux·运维·服务器·人工智能·开源
旗开得胜马到成功2 小时前
SK海力士SSD售后翻车:企业备份介质裸奔风险与中科热备技术选型拆解
运维·服务器·网络
7177773 小时前
厘清 Gitee Test 能力边界:测试管理、自动化与 DevSecOps 协同路径
运维·gitee·自动化
brian00313 小时前
虚拟机vmware文件的后缀名汇总介绍
运维·vmware·后缀名
2301_800954993 小时前
Linux 常用系统信息与进程管理命令速查
linux·运维·服务器
ITyunwei09874 小时前
CPU 100% 排查实战复盘:4步从确认到根治(附命令)
运维·企业微信