Docker 容器化技术详解:镜像、容器、网络与应用部署设计

文章目录

    • 一、前言
    • [二、什么是 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 中最重要的对象包括:

  1. 镜像;
  2. 容器;
  3. 仓库;
  4. 数据卷;
  5. 网络。

4.1 镜像 Image

镜像是一个标准化、只读的应用程序运行包,其中可以包含:

  • 应用代码;
  • 语言运行环境;
  • 系统依赖;
  • 第三方库;
  • 配置模板;
  • 默认启动命令。

镜像具有两个重要特点:

  1. 镜像创建后不会被直接修改;
  2. 镜像由多个只读层组成。

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 容器化体系,需要同时理解以下内容:

  1. Docker Client 与 Docker Daemon;
  2. 镜像和容器的关系;
  3. 镜像分层与构建缓存;
  4. 镜像仓库与版本标签;
  5. 容器生命周期;
  6. 端口发布与网络通信;
  7. Volume 与 Bind Mount;
  8. Dockerfile 镜像构建;
  9. 非 root 用户和镜像安全;
  10. 多阶段构建;
  11. Docker Compose 服务编排;
  12. 环境变量和配置管理;
  13. 健康检查和自动重启;
  14. 日志、监控和故障排查;
  15. CI/CD 与镜像回滚。

Docker 应用的核心流程可以概括为:

text 复制代码
应用代码
   ↓
Dockerfile
   ↓
Docker镜像
   ↓
镜像仓库
   ↓
Docker容器
   ↓
网络、存储和配置
   ↓
对外提供服务

Docker 系统最重要的设计原则可以概括为:

镜像负责定义环境,容器负责运行进程,数据卷负责保存数据,网络负责连接服务,Compose 负责组织应用,而安全、监控和版本管理决定了容器能否稳定运行在生产环境中。

只有将镜像构建、容器运行、数据持久化、网络通信、配置管理和持续交付统一起来,Docker 才不只是一个启动容器的工具,而会成为完整的软件交付基础设施。

相关推荐
luiyarch1 小时前
TBOX信息安全系列7设计篇-双向认证车云通信安全方案
网络·安全·车载系统·汽车
I'mChloe1 小时前
不再逐台装Docker:我用Ansible Playbook批量统一服务器环境
服务器·docker·ansible
打工仔折腾 AI1 小时前
Qt网络编程实战:UDP、TCP到HTTP的落地经验与选型
网络·人工智能·后端·python·qt·udp·ai agent 实战
硅基造物主1 小时前
18.从 IP 到 MQTT:STM32 用 AT 指令驱动 ESP-12F 上云的完整链路(含全套实战代码 + 踩坑清单)
网络·stm32·单片机·嵌入式硬件·网络协议·tcp/ip
重生之高冷汉堡包1 小时前
JavaScript 网络爬虫教程(2026)
大数据·网络·爬虫·安全
江湖有缘2 小时前
Docker实战 | 使用Docker部署Readeck书签管理工具
java·docker·eureka
Raas1002 小时前
AI网关怎么选?MAI Gateway(魔芋企业级AI网关)入选2026年企业级AI网关推荐清单
大数据·网络·人工智能·gateway·mai gateway
白帽攻防录2 小时前
SRC 挖洞:MSMQ 端口上的 UAF 炸弹,9.8 分网络 RCE 怎么打到 SYSTEM
网络·网络安全·src漏洞挖掘
十二同学啊2 小时前
Python FastAPI 项目使用 Docker 部署并映射端口(完整教程)
python·docker·fastapi