Docker 与 Docker Compose 使用指南

本篇内容着重说明日常使用docker常用的部分,其他非常用操作这里不再说明

第一部分:Docker 基础

1. Docker 简介

什么是 Docker?

Docker 是一个开源的应用容器引擎,让开发者可以打包应用及其依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 或 Windows 操作系统上。

Docker vs 虚拟机

特性 Docker 容器 虚拟机
启动速度 秒级 分钟级
资源占用 MB 级 GB 级
性能 接近原生 有损耗
隔离性 进程级 系统级
镜像大小 通常 MB 级 通常 GB 级

Docker 的优势

  • 快速部署:容器启动时间极短
  • 资源高效:共享宿主机内核,资源占用少
  • 一致性:开发、测试、生产环境完全一致
  • 可移植性:一次构建,随处运行
  • 版本控制:镜像版本管理简单

2. Docker 安装

Linux 安装(Ubuntu/Debian)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 更新包索引
sudo apt-get update

# 安装依赖
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release

# 添加 Docker 官方 GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 添加 Docker 源
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# 将当前用户添加到 docker 组(避免每次使用 sudo)
sudo usermod -aG docker $USER

# 验证安装
docker --version

CentOS/RHEL 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 卸载旧版本
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

# 安装 yum-utils
sudo yum install -y yum-utils

# 添加 Docker 源
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 安装 Docker
sudo yum install -y docker-ce docker-ce-cli containerd.io

# 启动 Docker
sudo systemctl start docker
sudo systemctl enable docker

# 验证安装
docker --version

Windows/macOS 安装

  1. 下载 Docker Desktop
  2. 运行安装程序
  3. 按照向导完成安装
  4. 启动 Docker Desktop

验证安装

1
2
3
4
5
6
7
8
# 检查 Docker 版本
docker --version

# 运行测试容器
docker run hello-world

# 查看详细信息
docker info

3. Docker 核心概念

镜像(Image)

镜像是一个只读模板,包含运行应用所需的代码、运行时、库、环境变量和配置文件。

容器(Container)

容器是镜像的运行实例。可以创建、启动、停止、删除容器。

仓库(Registry)

仓库是存储和分发镜像的服务。Docker Hub 是最大的公共仓库。

Docker 架构

graph TB
    subgraph "Docker Host"
        subgraph "Container 1"
            C1App["App"]
            C1Libs["Bins/Libs"]
        end
        subgraph "Container 2"
            C2App["App"]
            C2Libs["Bins/Libs"]
        end
        Engine["Docker Engine"]
    end
    
    C1App --> C1Libs
    C2App --> C2Libs
    Engine --> C1Libs
    Engine --> C2Libs

4. Docker 镜像管理

搜索镜像

1
2
3
4
5
6
7
8
# 搜索 Docker Hub 上的镜像
docker search nginx

# 搜索官方镜像
docker search --filter=is-official=true nginx

# 搜索星数超过 1000 的镜像
docker search --filter=stars=1000 nginx

拉取镜像

1
2
3
4
5
6
7
8
# 拉取最新版本
docker pull nginx

# 拉取指定版本
docker pull nginx:1.20

# 拉取指定架构
docker pull --platform linux/amd64 nginx:latest

查看镜像

1
2
3
4
5
6
7
8
9
10
11
12
# 列出本地所有镜像
docker images

# 查看镜像详细信息
docker inspect nginx:latest

# 查看镜像构建历史
docker history nginx:latest

# 过滤镜像
docker images --filter "dangling=true"
docker images --filter "reference=nginx:*"

镜像操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 重命名镜像
docker tag nginx:latest my-nginx:v1

# 推送镜像到仓库
docker push my-nginx:v1

# 删除镜像
docker rmi nginx:latest

# 删除所有未使用的镜像
docker image prune -a

# 导出镜像为 tar 文件
docker save -o nginx.tar nginx:latest

# 从 tar 文件导入镜像
docker load -i nginx.tar

构建镜像

1
2
3
4
5
6
7
8
9
10
11
# 从 Dockerfile 构建
docker build -t my-app:v1 .

# 构建时指定上下文
docker build -t my-app:v1 -f Dockerfile.prod .

# 构建时不使用缓存
docker build --no-cache -t my-app:v1 .

# 构建时传递参数
docker build --build-arg NODE_ENV=production -t my-app:v1 .

5. Docker 容器管理

创建和启动容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建并启动容器(前台运行)
docker run nginx

# 创建并启动容器(后台运行)
docker run -d nginx

# 创建并启动容器(交互模式)
docker run -it ubuntu bash

# 创建并启动容器(自动删除)
docker run --rm nginx

# 创建并启动容器(命名)
docker run --name my-nginx -d nginx

端口映射

1
2
3
4
5
6
7
8
# 容器 80 端口映射到主机 8080 端口
docker run -d -p 8080:80 nginx

# 多个端口映射
docker run -d -p 8080:80 -p 8443:443 nginx

# 绑定到特定 IP
docker run -d -p 127.0.0.1:8080:80 nginx

环境变量

1
2
3
4
5
6
7
8
# 设置环境变量
docker run -d -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

# 从文件读取环境变量
docker run -d --env-file .env mysql:8.0

# 多个环境变量
docker run -d -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=mydb mysql:8.0

卷挂载

1
2
3
4
5
6
7
8
9
10
11
# 命名卷
docker run -d -v mysql-data:/var/lib/mysql mysql:8.0

# 绑定卷
docker run -d -v /host/path:/container/path nginx

# 只读挂载
docker run -d -v /host/config:/etc/nginx/conf.d:ro nginx

# 多个卷挂载
docker run -d -v mysql-data:/var/lib/mysql -v ./config:/etc/mysql/conf.d mysql:8.0

容器管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看运行中的容器
docker ps

# 查看所有容器(包括停止的)
docker ps -a

# 查看容器日志
docker logs my-nginx

# 实时查看日志
docker logs -f my-nginx

# 查看容器详细信息
docker inspect my-nginx

# 查看容器资源使用情况
docker stats my-nginx

# 查看容器进程
docker top my-nginx

容器操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 停止容器
docker stop my-nginx

# 启动已停止的容器
docker start my-nginx

# 重启容器
docker restart my-nginx

# 暂停容器
docker pause my-nginx

# 恢复容器
docker unpause my-nginx

# 删除容器
docker rm my-nginx

# 强制删除运行中的容器
docker rm -f my-nginx

# 删除所有停止的容器
docker container prune

执行命令

1
2
3
4
5
6
7
8
9
10
11
# 在容器中执行命令
docker exec my-nginx ls -la

# 进入容器(交互模式)
docker exec -it my-nginx bash

# 进入容器(sh)
docker exec -it my-nginx sh

# 以特定用户执行
docker exec -it --user root my-nginx bash

6. Docker 网络管理

先搞懂:为什么需要网络?

想象一下你家里有几台电脑:

  • 电脑A要访问电脑B上的服务
  • 它们需要在同一个”局域网”里才能互相找到

Docker容器也是一样:

  • 容器之间要互相通信
  • 容器要被外部访问
  • 这些都需要通过”网络”来实现

Docker 网络模式一览

graph TB
    subgraph "Docker 网络模式"
        Bridge["bridge 桥接网络<br/>(默认模式)"]
        Host["host 主机网络"]
        None["none 无网络"]
        Overlay["overlay 覆盖网络"]
        Macvlan["macvlan MAC地址网络"]
    end
    
    Bridge --- BridgeUse["容器间通信"]
    Host --- HostUse["高性能场景"]
    None --- NoneUse["完全隔离"]
    Overlay --- OverlayUse["跨主机通信"]
    Macvlan --- MacvlanUse["特殊硬件设备"]
    
    style Bridge fill:#4CAF50
    style Host fill:#2196F3
    style None fill:#9E9E9E
    style Overlay fill:#FF9800
    style Macvlan fill:#9C27B0
    style BridgeUse fill:#81C784
    style HostUse fill:#64B5F6
    style NoneUse fill:#BDBDBD
    style OverlayUse fill:#FFB74D
    style MacvlanUse fill:#BA68C8

模式一:bridge 桥接网络(最常用,必学!)

通俗解释

把Docker的bridge网络想象成你家里的路由器

  • 所有设备(容器)都连接到这个路由器上
  • 路由器会给每个设备分配一个内网IP
  • 设备之间可以通过内网IP互相访问
  • 外面的人要访问你家设备,需要通过路由器做”端口映射”

工作原理图

graph TB
    subgraph "宿主机"
        subgraph "bridge 网络 172.17.0.0/16"
            C1["容器A<br/>172.17.0.2"]
            C2["容器B<br/>172.17.0.3"]
            C3["容器C<br/>172.17.0.4"]
        end
        Docker0["docker0 虚拟网桥"]
    end
    
    Internet["互联网"]
    
    C1 --> Docker0
    C2 --> Docker0
    C3 --> Docker0
    Docker0 --> Internet
    
    style Docker0 fill:#4CAF50
    style C1 fill:#2196F3
    style C2 fill:#2196F3
    style C3 fill:#2196F3

实际演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 启动两个容器(默认使用bridge网络)
docker run -d --name web1 nginx
docker run -d --name web2 nginx

# 查看容器IP
docker inspect web1 | grep IPAddress
# 输出:172.17.0.2

docker inspect web2 | grep IPAddress
# 输出:172.17.0.3

# 测试容器间通信(默认bridge网络下,容器可以通过IP互相访问)
docker exec web1 ping 172.17.0.3
# 输出:64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.089 ms

# 但是!默认bridge网络不支持通过容器名访问
docker exec web1 ping web2
# 输出:ping: web2: Name or service not known

自定义bridge网络(推荐!)

1
2
3
4
5
6
7
8
9
10
# 创建自定义bridge网络
docker network create my-network

# 启动容器并加入自定义网络
docker run -d --name web1 --network my-network nginx
docker run -d --name web2 --network my-network nginx

# 现在可以通过容器名访问了!
docker exec web1 ping web2
# 输出:64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.078 ms

为什么推荐自定义bridge网络?

对比项 默认bridge 自定义bridge
容器名访问 ❌ 不支持 ✅ 支持
DNS解析 ❌ 没有 ✅ 有
隔离性 所有容器都在同一网络 可以创建多个隔离网络
灵活性

模式二:host 主机网络(高性能场景)

通俗解释

把host模式想象成直接把电脑连到公网

  • 容器不再有自己独立的IP
  • 容器直接使用宿主机的IP和端口
  • 性能最高,因为没有NAT转换
  • 但端口会冲突,因为所有容器共享宿主机端口

工作原理图

graph TB
    subgraph "宿主机"
        C1["容器A<br/>直接使用宿主机IP"]
        C2["容器B<br/>直接使用宿主机IP"]
    end
    
    Internet["互联网"]
    
    C1 --> Internet
    C2 --> Internet
    
    style C1 fill:#FF9800
    style C2 fill:#FF9800

实际演示

1
2
3
4
5
6
7
8
9
# 使用host模式启动nginx
docker run -d --name web-host --network host nginx

# 容器直接占用宿主机的80端口
# 访问:http://宿主机IP:80

# 再启动一个nginx(会失败,因为80端口已被占用)
docker run -d --name web-host2 --network host nginx
# 输出:Error starting userland proxy: Bind for 0.0.0.0:80: address already in use

适用场景

适合

  • 对网络性能要求极高的应用
  • 需要处理大量网络连接的服务
  • 单容器应用(不用担心端口冲突)

不适合

  • 多个容器都需要用相同端口
  • 需要容器间隔离的场景
  • 需要端口映射的场景

模式三:none 无网络(完全隔离)

通俗解释

把none模式想象成把电脑网线拔掉

  • 容器没有任何网络连接
  • 完全隔离,不能访问外部网络
  • 其他容器也无法访问它
  • 用于完全不需要网络的场景

实际演示

1
2
3
4
5
6
7
8
9
10
# 启动一个无网络的容器
docker run -d --name web-none --network none nginx

# 尝试访问外网(会失败)
docker exec web-none ping baidu.com
# 输出:ping: baidu.com: Name or service not known

# 查看网络配置
docker exec web-none ip addr
# 只有lo(本地回环),没有eth0

适用场景

适合

  • 数据处理任务(不需要网络)
  • 安全敏感的应用(完全隔离)
  • 批处理作业

不适合

  • 需要访问外部网络的应用
  • 需要与其他容器通信的应用
  • 需要被外部访问的服务

模式四:overlay 覆盖网络(跨主机通信)

通俗解释

把overlay网络想象成VPN

  • 多台物理服务器上的容器
  • 通过overlay网络可以像在同一个局域网一样通信
  • 用于Docker Swarm集群或多主机部署
  • 普通单机环境用不到

工作原理图

graph TB
    subgraph "主机A"
        C1["容器A<br/>10.0.0.2"]
    end
    
    subgraph "主机B"
        C2["容器B<br/>10.0.0.3"]
    end
    
    subgraph "overlay 网络"
        VPN["虚拟网络隧道"]
    end
    
    C1 --> VPN
    C2 --> VPN
    VPN --> C1
    VPN --> C2
    
    style C1 fill:#2196F3
    style C2 fill:#2196F3
    style VPN fill:#FF9800

实际演示(需要Swarm模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 初始化Swarm
docker swarm init

# 创建overlay网络
docker network create --driver overlay my-overlay

# 在不同主机上运行容器
# 主机A
docker service create --name web --network my-overlay nginx

# 主机B
docker service create --name db --network my-overlay mysql

# 两个容器可以互相访问!

模式五:macvlan MAC地址网络(特殊硬件设备)

通俗解释

把macvlan模式想象成给容器办一张独立的网卡

  • 容器有自己独立的MAC地址
  • 可以直接获得物理网络的IP
  • 容器看起来就像一台独立的物理机
  • 用于需要容器直接接入物理网络的场景

实际演示

1
2
3
4
5
6
7
8
9
10
11
# 创建macvlan网络
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
my-macvlan

# 使用macvlan网络启动容器
docker run -d --name web --network my-macvlan nginx

# 容器会获得192.168.1.x的IP,就像一台独立的物理机

网络操作常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建网络
docker network create my-network

# 创建指定子网的网络
docker network create --subnet=172.18.0.0/16 my-network

# 查看所有网络
docker network ls

# 查看网络详细信息
docker network inspect my-network

# 连接容器到网络
docker network connect my-network my-nginx

# 断开容器与网络的连接
docker network disconnect my-network my-nginx

# 删除网络
docker network rm my-network

# 删除所有未使用的网络
docker network prune

容器间通信实战

1
2
3
4
5
6
7
8
9
10
11
# 创建网络
docker network create app-network

# 启动 MySQL 容器
docker run -d --name mysql --network app-network -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

# 启动应用容器(通过容器名访问 MySQL)
docker run -d --name app --network app-network -e DB_HOST=mysql my-app:v1

# 验证连接
docker exec app ping mysql

网络模式选择指南

graph TB
    Start["选择网络模式"]
    
    Q1{"需要跨主机通信?"}
    Q2{"需要完全隔离?"}
    Q3{"需要最高性能?"}
    Q4{"需要容器间通信?"}
    
    Overlay["overlay 模式"]
    None["none 模式"]
    Host["host 模式"]
    Bridge["bridge 模式"]
    
    Start --> Q1
    Q1 -->|"是"| Overlay
    Q1 -->|"否"| Q2
    Q2 -->|"是"| None
    Q2 -->|"否"| Q3
    Q3 -->|"是"| Host
    Q3 -->|"否"| Q4
    Q4 -->|"是"| Bridge
    Q4 -->|"否"| None
    
    style Overlay fill:#FF9800
    style None fill:#9E9E9E
    style Host fill:#2196F3
    style Bridge fill:#4CAF50
模式 适用场景 性能 隔离性 复杂度
bridge 多容器应用(默认) 简单
host 高性能单容器 简单
none 完全隔离任务 最高 简单
overlay 跨主机集群 复杂
macvlan 直接接入物理网络 复杂

Docker Compose 中的网络配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: '3.9'

services:
web:
image: nginx
networks:
- frontend
- backend

database:
image: mysql
networks:
- backend

networks:
frontend:
driver: bridge # 默认bridge模式
backend:
driver: bridge
ipam:
config:
- subnet: 172.18.0.0/16 # 自定义子网
1
2
3
4
5
# 查看Compose创建的网络
docker network ls | grep myproject

# 查看网络详情
docker network inspect myproject_backend

7. Docker 卷管理

卷类型

  • 命名卷:由 Docker 管理,可重用
  • 绑定卷:挂载主机目录
  • tmpfs 卷:临时文件系统

卷操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建卷
docker volume create my-volume

# 查看所有卷
docker volume ls

# 查看卷详细信息
docker volume inspect my-volume

# 删除卷
docker volume rm my-volume

# 删除所有未使用的卷
docker volume prune

# 使用卷运行容器
docker run -d -v my-volume:/data my-app:v1

# 使用绑定卷运行容器
docker run -d -v /host/data:/container/data my-app:v1

数据备份和恢复

1
2
3
4
5
# 备份卷数据
docker run --rm -v my-volume:/source -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /source .

# 恢复卷数据
docker run --rm -v my-volume:/target -v $(pwd):/backup alpine tar xzf /backup/backup.tar.gz -C /target

8. Dockerfile 编写指南

RUN、CMD、ENTRYPOINT 区别详解

可以把 Docker 镜像想象成一个做菜的菜谱,而容器就是最终做出来的一道菜

graph LR
    subgraph "构建阶段 docker build"
        A["FROM 基础镜像"]
        B["RUN 执行命令<br/>安装软件/配置环境"]
        C["COPY/ADD 复制文件"]
        D["构建完成"]
    end
    
    subgraph "运行阶段 docker run"
        E["ENTRYPOINT 入口程序"]
        F["CMD 默认参数"]
        G["容器运行"]
    end
    
    A --> B --> C --> D
    D --> E
    E --> F --> G
    
    style A fill:#4CAF50
    style B fill:#FF9800
    style C fill:#2196F3
    style D fill:#9C27B0
    style E fill:#F44336
    style F fill:#FF5722
    style G fill:#795548

RUN:准备食材的阶段(构建镜像时执行)

RUN 是在**制作菜谱(构建镜像)**的过程中执行的命令。

  • 做什么:安装软件、创建目录、下载依赖等。这些都是为最终运行容器做准备。
  • 什么时候执行:当你执行 docker build 时。
  • 结果:会生成一个新的镜像层。比如你 RUN apt-get install python,这就会把 Python 打包进镜像里。

打个比方:就像你在写菜谱时,先把土豆削皮、切块(RUN),这些步骤做完之后,土豆块就成了菜谱的一部分。以后每次照着这个菜谱做菜,土豆块都是现成的。

1
2
3
FROM ubuntu
RUN apt-get update && apt-get install -y nginx # 构建时安装nginx
RUN mkdir /app # 构建时创建目录

CMD:默认的烹饪方式(容器启动时执行)

CMD容器启动时默认要执行的命令。它就像一个菜谱上写的”推荐做法”。

  • 做什么:指定容器启动后默认运行的程序,比如 nginx -g "daemon off;"
  • 什么时候执行:当你执行 docker run没有附加其他命令时。
  • 特点:可以被覆盖。如果你在 docker run 后面加了别的命令,CMD 就会被替换掉。

打个比方:菜谱上写着”建议清蒸”(CMD)。但你今天想红烧,就可以在 docker run 时改成红烧。菜谱的建议被覆盖了。

1
2
FROM ubuntu
CMD ["echo", "Hello, World!"] # 默认打印 Hello

运行时:

1
2
docker run myimage              # 输出: Hello, World!
docker run myimage echo Hi # 输出: Hi(CMD被覆盖)

ENTRYPOINT:固定的烹饪方式(容器启动时执行)

ENTRYPOINT 也是容器启动时执行的命令,但它更固定

  • 做什么:定义容器的主要入口程序。通常配合 CMD 一起使用。
  • 什么时候执行:同样在 docker run 时执行。
  • 特点不容易被覆盖。如果你在 docker run 后面加了参数,这些参数会作为追加参数传给 ENTRYPOINT,而不是替换它。

打个比方:菜谱规定”必须用蒸的”(ENTRYPOINT)。你可以选择蒸5分钟还是10分钟(通过 CMD 或命令行传参),但你不能改成炒的。

1
2
3
FROM ubuntu
ENTRYPOINT ["echo"] # 固定使用 echo 命令
CMD ["Hello, World!"] # 默认参数

运行时:

1
2
3
docker run myimage            # 输出: Hello, World!
docker run myimage Hi # 输出: Hi(Hi作为参数传给echo)
docker run myimage --help # 输出: --help(--help作为参数传给echo)

核心区别总结表

指令 执行时机 能否被覆盖 典型用途
RUN docker build 构建时 不可覆盖(已固化到镜像) 安装软件、配置环境
CMD docker run 启动时 可以docker run 后的命令完全替换 提供默认启动命令和参数
ENTRYPOINT docker run 启动时 不能被替换,只能追加参数 定义容器的主程序入口

实战中的黄金搭档:ENTRYPOINT + CMD

这是最常见的用法,组合起来非常灵活:

1
2
3
4
5
6
7
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt # RUN:构建时安装依赖

ENTRYPOINT ["python"] # 固定:使用python解释器
CMD ["app.py"] # 默认:运行app.py
  • 默认启动:docker run myapp → 执行 python app.py
  • 运行其他脚本:docker run myapp test.py → 执行 python test.py(CMD被覆盖,但ENTRYPOINT不变)
  • 加参数:docker run myapp app.py --debug → 执行 python app.py --debug

这样既保证了容器的主功能(Python解释器)不被篡改,又保留了灵活性(可以运行不同的脚本)。


EXPOSE 使用详解

EXPOSE 可以理解为在容器的墙上开了一扇窗,告诉别人:”我这个容器里面有个服务在用这个端口号”。

但是注意:这扇窗只是声明,并没有真正打开!真正的开门(端口映射)需要在 docker run 时用 -p 参数来做。

基本语法

1
EXPOSE <端口号> [<端口号>/<协议>]
  • 端口号:容器内部服务的端口
  • 协议:可选,默认是 TCP,也可以指定 UDP
1
2
3
4
EXPOSE 80                    # 暴露80端口,TCP协议
EXPOSE 53/udp # 暴露53端口,UDP协议
EXPOSE 8080/tcp # 暴露8080端口,明确指定TCP
EXPOSE 3000 # 暴露3000端口

实际作用

1. 文档作用:告诉使用者这个容器提供什么服务

1
2
3
4
# 一个 Nginx 镜像的典型写法
FROM nginx
EXPOSE 80 # 明示:我用80端口提供HTTP服务
EXPOSE 443 # 明示:我用443端口提供HTTPS服务

2. 配合 -P 参数自动映射

1
2
3
4
5
# 构建镜像时声明了 EXPOSE 80
docker run -P mynginx

# 这时候 Docker 会自动把宿主机的随机端口映射到容器的 80 端口
# 相当于:docker run -p 32768:80 mynginx

3. 不写 EXPOSE 也能映射端口

1
2
# 就算没写 EXPOSE 80,也能手动映射
docker run -p 8080:80 mynginx

常见误区澄清

误区 真相
写了EXPOSE就能从外部访问 ❌ 还需要 -p-P 做端口映射
多个EXPOSE会互相影响 ✅ 每个EXPOSE独立声明,互不影响
EXPOSE必须写在最后 ❌ 可以写在Dockerfile任意位置,习惯放末尾
不写EXPOSE就不能映射端口 ❌ 照样可以用 -p 手动映射

完整示例:从 Dockerfile 到运行

1
2
3
4
5
6
7
8
9
10
# Dockerfile
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

EXPOSE 3000 # 声明:我的Node应用跑在3000端口

CMD ["node", "app.js"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 构建镜像
docker build -t my-node-app .

# 方式一:手动指定端口映射(最常用)
docker run -p 8080:3000 my-node-app
# 宿主机8080 → 容器3000

# 方式二:自动分配端口
docker run -P my-node-app
# Docker自动分配宿主机端口 → 容器3000

# 查看映射情况
docker port my-node-app
# 输出:3000/tcp -> 0.0.0.0:32769

结合 docker-compose 使用

1
2
3
4
5
6
7
8
# docker-compose.yml
services:
web:
build: .
ports:
- "8080:3000" # 这才是真正映射
expose:
- "3000" # 这只是声明,用于服务间通信

ADD 与 COPY 区别详解

ADDCOPY 是 Dockerfile 里两个看起来很像的指令,很多新手容易混淆。

核心区别一句话

  • COPY:纯粹的”复制文件”,只做一件事。
  • ADD:COPY 的增强版,除了复制还能自动解压支持URL下载

COPY:老实本分的搬运工

只做一件事:把文件从宿主机(构建上下文)复制到镜像里。

1
2
3
4
# 语法:COPY <源路径> <目标路径>
COPY ./app /app # 把本地的app目录复制到镜像的/app
COPY package.json /app/ # 复制单个文件
COPY *.txt /tmp/ # 支持通配符

特点

  • 简单、透明、可预期
  • 不会做任何额外操作
  • 推荐优先使用

ADD:多才多艺的多面手

除了 COPY 的功能,还有两个特殊能力:

能力1:自动解压压缩包

1
2
3
# 如果源文件是压缩包,ADD会自动解压到目标目录
ADD app.tar.gz /app/ # 自动解压到/app
ADD source.zip /data/ # 自动解压zip

对比 COPY 的行为:

1
COPY app.tar.gz /app/              # 只是复制压缩包本身,不解压

能力2:支持URL下载

1
2
# 可以直接从URL下载文件到镜像
ADD https://example.com/file.tar.gz /tmp/

注意:这个功能在实际生产中很少用,因为:

  • 无法利用缓存(每次构建都会重新下载)
  • 不好控制版本
  • 推荐用 curlwget 替代

实战对比表

场景 用 COPY 用 ADD
复制本地代码文件 ✅ 首选 ❌ 没必要
复制配置文件 ✅ 首选 ❌ 没必要
复制并自动解压 tar.gz ❌ 不解压 ✅ 自动解压
复制并自动解压 zip ❌ 不解压 ✅ 自动解压
从URL下载文件 ❌ 不支持 ✅ 支持(但不推荐)
复制目录结构 ✅ 首选 ❌ 没必要

最佳实践:90%的情况用 COPY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 推荐的写法
FROM python:3.9-slim

WORKDIR /app

# 先复制依赖文件(利用缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 再复制源代码
COPY . .

# 只有需要解压时才用 ADD
ADD model_weights.tar.gz /app/models/

CMD ["python", "app.py"]

为什么推荐优先用 COPY?

1. 行为更清晰

1
2
COPY app.tar.gz /tmp/    # 我知道结果是:/tmp/app.tar.gz
ADD app.tar.gz /tmp/ # 结果可能是:/tmp/app.tar.gz 或 解压后的文件

2. 安全性更好

  • ADD 的 URL 下载功能可能带来安全隐患
  • 自动解压可能产生意想不到的文件结构

3. Docker 官方也推荐

Docker 官方文档明确建议:除非你需要自动解压功能,否则始终使用 COPY。

特殊场景:什么时候非用 ADD 不可?

场景1:需要自动解压

1
2
3
4
5
6
# 有一个预训练模型压缩包,需要解压到指定目录
ADD model.tar.gz /models/

# 如果用 COPY,还需要额外一步
COPY model.tar.gz /tmp/
RUN tar -xzf /tmp/model.tar.gz -C /models/ && rm /tmp/model.tar.gz

常见的坑

坑1:ADD 解压行为不一致

1
2
3
4
5
# 本地 tar.gz 文件 → 自动解压
ADD ./files.tar.gz /dest/ # 解压到/dest

# URL 的 tar.gz 文件 → 不会解压!
ADD https://example.com/files.tar.gz /dest/ # 只是下载,不解压

坑2:ADD 的 URL 下载没有缓存

1
2
3
4
5
# 每次构建都会重新下载,即使文件没变
ADD https://example.com/data.txt /data/

# 更好的做法
RUN curl -o /data.txt https://example.com/data.txt

COPY 多文件语法与 –from 使用

COPY 可以一次复制多个文件

1
2
3
4
5
6
7
# 语法:COPY <源路径1> [<源路径2>...] <目标路径>
COPY package.json package-lock.json /app/

# 使用通配符
COPY package*.json /app/ # 匹配 package.json 和 package-lock.json
COPY *.txt /app/ # 复制所有txt文件
COPY src/**/*.js /app/dist/ # 递归匹配

注意事项

1
2
3
4
5
6
7
8
# ✅ 正确:多个源文件,目标是一个目录
COPY file1.txt file2.txt config.json /app/config/

# ❌ 错误:多个源文件,目标是文件路径
COPY file1.txt file2.txt /app/output.txt # 会报错

# ✅ 正确:通配符匹配多个文件
COPY *.json /app/config/

–from 主要用于多阶段构建

目前支持的指令是:

1. COPY –from(最常用)

1
2
3
4
5
6
7
8
# 从另一个阶段复制文件
FROM node:16 AS builder
WORKDIR /app
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

2. COPY –from 还可以从外部镜像复制

1
2
# 直接从已有镜像复制文件
COPY --from=nginx:latest /etc/nginx/nginx.conf /etc/nginx/

完整的多阶段构建示例

graph TB
    subgraph "阶段1:Go后端构建"
        G1["FROM golang:1.19 AS go-builder"]
        G2["COPY go.mod go.sum ./"]
        G3["RUN go mod download"]
        G4["COPY . ."]
        G5["RUN go build -o server"]
    end
    
    subgraph "阶段2:前端构建"
        N1["FROM node:16 AS frontend-builder"]
        N2["COPY frontend/package*.json ./"]
        N3["RUN npm ci"]
        N4["COPY frontend/ ."]
        N5["RUN npm run build"]
    end
    
    subgraph "阶段3:最终镜像"
        A1["FROM alpine:latest"]
        A2["COPY --from=go-builder 二进制文件"]
        A3["COPY --from=frontend-builder 静态文件"]
        A4["EXPOSE 8080"]
    end
    
    G1 --> G2 --> G3 --> G4 --> G5
    N1 --> N2 --> N3 --> N4 --> N5
    A1 --> A2
    A1 --> A3
    A2 --> A4
    A3 --> A4
    
    G5 -.-> A2
    N5 -.-> A3

常见用法总结表

指令 是否支持 --from 用途
COPY ✅ 支持 从其他阶段或外部镜像复制文件
ADD ❌ 不支持 只能用常规的源路径
RUN ❌ 不支持 不能直接用 --from

分层构建与缓存机制

可以把 Docker 镜像想象成一个千层蛋糕

  • 每一层:对应 Dockerfile 里的一条指令(FROMRUNCOPYADD 等)
  • 每一层都是只读的:一旦创建就不会改变
  • 层叠起来:形成最终的镜像
graph TB
    subgraph "Docker 镜像分层结构"
        L5["第5层:CMD 启动命令(元数据层)"]
        L4["第4层:COPY index.html 复制文件"]
        L3["第3层:RUN apt-get install 安装nginx"]
        L2["第2层:RUN apt-get update 更新包列表"]
        L1["第1层:FROM ubuntu:20.04 基础层"]
    end
    
    L5 --> L4
    L4 --> L3
    L3 --> L2
    L2 --> L1
    
    style L1 fill:#4CAF50
    style L2 fill:#8BC34A
    style L3 fill:#CDDC39
    style L4 fill:#FFEB3B
    style L5 fill:#FFC107

缓存机制:像 Git 一样聪明

Docker 构建时会逐条执行 Dockerfile 中的指令,并对每条指令的结果进行缓存

缓存命中条件(缺一不可)

  1. 父层缓存命中:上一层的缓存必须存在且有效
  2. 指令完全相同RUN apt-get install -y nginxRUN apt-get install -y nginx 一样
  3. 上下文文件未变:对于 COPYADD,会校验文件的 checksum

示例

1
2
3
4
5
FROM node:16-alpine           # 拉取基础镜像
RUN apk add --no-cache git # 执行,耗时30秒
COPY package.json /app/ # 复制文件
RUN npm install # 执行,耗时60秒
COPY . /app # 复制所有文件

第一次构建耗时90秒,第二次构建时如果 package.json 没变,只需要重新执行最后一步,构建时间从90秒降到几秒!

缓存失效的连锁反应

一旦某层缓存失效,后续所有层都会重建

graph TB
    subgraph "缓存失效连锁反应"
        L1["FROM node:16-alpine ✅ 缓存命中"]
        L2["COPY package.json /app/ ✅ 缓存命中"]
        L3["RUN npm install ✅ 缓存命中"]
        L4["COPY src/ /app/src ❌ 缓存失效"]
        L5["RUN npm run build ❌ 被迫重新构建"]
        L6["COPY dist/ /app/dist ❌ 被迫重新构建"]
    end
    
    L1 --> L2
    L2 --> L3
    L3 --> L4
    L4 --> L5
    L5 --> L6
    
    style L1 fill:#90EE90
    style L2 fill:#90EE90
    style L3 fill:#90EE90
    style L4 fill:#FFB6C1
    style L5 fill:#FFB6C1
    style L6 fill:#FFB6C1

这就是为什么把不常变的指令放在前面如此重要!

分层构建的最佳实践

1. 分离依赖和源码(最重要!)

1
2
3
4
5
6
7
8
9
# ❌ 错误的写法:依赖和源码一起复制
COPY . /app
RUN npm install
# 改一行代码,npm install 就要重跑

# ✅ 正确的写法:分步复制
COPY package.json package-lock.json /app/
RUN npm install # 依赖层,只有package.json变化才重建
COPY . /app # 源码层,经常变化

2. 合理排序指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM python:3.9-slim

# 第1组:系统依赖(极少变化)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev && \
rm -rf /var/lib/apt/lists/*

# 第2组:Python依赖(偶尔变化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 第3组:应用代码(经常变化)
COPY . .

CMD ["python", "app.py"]

3. 利用 .dockerignore 减少无效缓存失效

1
2
3
4
5
6
7
8
# .dockerignore
node_modules
.git
*.log
.env
dist
.cache
__pycache__

这样 COPY . 时就不会因为这些无关文件的变化导致缓存失效。

查看和清理缓存的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看构建缓存占用了多少空间
docker system df

# 查看详细的缓存信息
docker builder ls

# 清理所有构建缓存
docker builder prune

# 构建时不使用缓存(强制重建)
docker build --no-cache -t myapp .

# 查看镜像的历史层
docker history myapp

一个完整的优化案例

原始 Dockerfile(低效):

1
2
3
4
5
6
7
FROM node:16
COPY . /app
WORKDIR /app
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

优化后(高效):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FROM node:16-alpine AS builder

WORKDIR /app

# 1. 先复制依赖文件(利用缓存)
COPY package.json package-lock.json ./
RUN npm ci --only=production

# 2. 再复制源码
COPY . .

# 3. 构建
RUN npm run build

# 4. 生产阶段
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .

EXPOSE 3000
CMD ["node", "dist/server.js"]

优化效果

  • 首次构建:约120秒
  • 仅改代码:约5秒(只重建最后几层)
  • 改依赖:约30秒(重建依赖层和后续层)
  • 镜像大小:从500MB降到150MB

Dockerfile 指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 基础镜像
FROM ubuntu:20.04

# 维护者信息
LABEL maintainer="your@email.com"

# 设置工作目录
WORKDIR /app

# 复制文件
COPY . .

# 或者更精确地复制
COPY requirements.txt .
COPY . .

# 执行命令
RUN apt-get update && apt-get install -y python3 python3-pip

# 安装依赖
RUN pip3 install -r requirements.txt

# 设置环境变量
ENV PYTHONUNBUFFERED=1

# 暴露端口
EXPOSE 8000

# 设置启动命令
CMD ["python3", "app.py"]

多阶段构建

1
2
3
4
5
6
7
8
9
10
11
12
13
# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

最佳实践示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 使用官方基础镜像
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 先复制依赖文件(利用缓存)
COPY package*.json ./
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
USER nodejs

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", "server.js"]

常见 Dockerfile 示例

Python 应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM python:3.9-slim

WORKDIR /app

# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制代码
COPY . .

EXPOSE 8000

CMD ["python", "app.py"]

Java 应用

1
2
3
4
5
6
7
8
9
10
11
12
FROM maven:3.8-openjdk-11 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

9. Docker 常见问题和坑

问题 1:权限问题

现象:容器内文件权限错误
解决方案

1
2
3
4
5
# 修改目录权限
sudo chown -R 1000:1000 /host/path

# 或在 Dockerfile 中设置
RUN chown -R nodejs:nodejs /app

问题 2:网络问题

现象:容器无法访问外网
解决方案

1
2
3
4
5
# 检查 DNS 配置
docker exec container cat /etc/resolv.conf

# 配置 DNS
docker run --dns=8.8.8.8 my-image

问题 3:磁盘空间

现象:Docker 占用大量磁盘空间
解决方案

1
2
3
4
5
6
7
8
9
10
# 查看磁盘使用情况
docker system df

# 清理未使用的资源
docker system prune -a

# 清理特定资源
docker image prune -a
docker container prune
docker volume prune

问题 4:镜像拉取失败

现象:拉取镜像超时或失败
解决方案

1
2
3
4
5
6
7
8
9
# 配置镜像加速器
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

问题 5:容器日志过大

现象:容器日志占用大量空间
解决方案

1
2
3
4
5
# 限制日志大小
docker run --log-opt max-size=10m --log-opt max-file=3 my-image

# 清理容器日志
sudo truncate -s 0 /var/lib/docker/containers/*/*-json.log

问题 6:容器时间同步

现象:容器内时间与主机不一致
解决方案

1
2
3
4
5
# 挂载本地时间
docker run -v /etc/localtime:/etc/localtime:ro my-image

# 或设置时区
docker run -e TZ=Asia/Shanghai my-image

问题 7:容器资源限制

现象:容器占用过多资源
解决方案

1
2
3
4
# 限制 CPU 和内存
docker run -d --cpus=1 --memory=512m my-image

# 在 Docker Compose 中配置

问题 8:容器无法删除

现象:容器删除失败
解决方案

1
2
3
4
5
6
7
8
# 强制删除
docker rm -f container_name

# 删除所有停止的容器
docker container prune

# 如果还不行,重启 Docker
sudo systemctl restart docker

第二部分:Docker Compose

10. Docker Compose 简介

什么是 Docker Compose?

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过一个 YAML 文件,你可以配置应用程序的所有服务,然后使用一个命令创建并启动所有服务。

为什么使用 Docker Compose?

  • 多容器管理:轻松管理 Web 服务器、数据库、缓存等多个服务
  • 环境一致性:开发、测试、生产环境使用相同的配置
  • 快速搭建开发环境:一条命令启动整个技术栈
  • 配置版本控制:YAML 文件可以纳入版本控制
  • 易于扩展:可以轻松添加新服务或修改现有配置

11. Docker Compose 安装

前提条件

确保已安装 Docker Engine:

1
docker --version

Linux 安装

方法一:使用包管理器(推荐)

1
2
3
4
5
6
7
8
# 下载 Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 添加可执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

方法二:使用 pip 安装

1
sudo pip install docker-compose

Windows/macOS 安装

Docker Desktop 用户

Docker Desktop 已内置 Docker Compose,无需单独安装。

验证安装

1
2
3
docker-compose --version
# 或者(新版)
docker compose version

版本说明

  • V1docker-compose(Python 版本,已停止维护)
  • V2docker compose(Go 版本,推荐使用)

12. Docker Compose 文件详解

基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3.9'    # Compose 文件版本

services: # 服务配置
web:
# 服务配置项
database:
# 服务配置项

networks: # 网络配置
frontend:

volumes: # 卷配置
data:

服务配置项详解

镜像配置

1
2
3
4
5
6
7
8
9
services:
web:
image: nginx:latest # 使用预构建镜像
build: ./app # 从 Dockerfile 构建(简写形式)
build:
context: ./app # 构建上下文路径
dockerfile: Dockerfile # Dockerfile 文件名
args:
NODE_ENV: production

build.context 参数详解

contextbuild 配置中的核心参数,用于指定**构建上下文(Build Context)**的路径。

什么是构建上下文?

构建上下文是 Docker 守护进程在构建镜像时可以访问的文件和目录集合。Docker 会将这个目录的内容发送给 Docker 守护进程,用于构建镜像。

语法格式

1
2
3
4
services:
web:
build:
context: ./path/to/context # 相对路径或绝对路径

context 的三种形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 形式1:简写形式(直接指定路径,等同于 context)
services:
web:
build: ./app

# 形式2:完整形式(指定 context 和 dockerfile)
services:
web:
build:
context: ./app
dockerfile: Dockerfile

# 形式3:绝对路径
services:
web:
build:
context: /absolute/path/to/app

context 的实际作用

1
2
3
4
5
6
7
8
9
10
11
# 示例项目结构
myapp/
├── docker-compose.yml
├── frontend/
├── Dockerfile
└── src/
├── backend/
├── Dockerfile
└── src/
└── shared/
└── config/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# docker-compose.yml
services:
frontend:
build:
context: ./frontend # 构建上下文是 frontend 目录
dockerfile: Dockerfile # 使用 frontend 目录下的 Dockerfile

backend:
build:
context: ./backend # 构建上下文是 backend 目录
dockerfile: Dockerfile # 使用 backend 目录下的 Dockerfile

# 共享配置:可以从其他目录复制文件
app:
build:
context: . # 构建上下文是项目根目录
dockerfile: ./app/Dockerfile # 可以访问所有子目录

context 与 COPY/ADD 的关系

1
2
3
4
5
# Dockerfile 中的 COPY/ADD 路径是相对于 context 的
# 如果 context 是 ./frontend
COPY . . # 复制 frontend 目录所有内容
COPY package.json . # 复制 frontend/package.json
COPY ../shared/config . # ❌ 错误!不能访问 context 之外的文件

多阶段构建中的 context

1
2
3
4
5
services:
app:
build:
context: . # 根目录作为构建上下文
dockerfile: Dockerfile # Dockerfile 可以引用所有子目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Dockerfile(多阶段构建)
FROM node:16 AS frontend
WORKDIR /app
COPY frontend/package*.json ./ # 引用 context 中的 frontend 目录
RUN npm ci
COPY frontend/ .
RUN npm run build

FROM python:3.9 AS backend
WORKDIR /app
COPY backend/requirements.txt . # 引用 context 中的 backend 目录
RUN pip install -r requirements.txt
COPY backend/ .

FROM alpine:latest
COPY --from=frontend /app/dist /var/www/html
COPY --from=backend /app /opt/app

常见使用场景

场景 context 配置 说明
单服务项目 context: . 根目录作为上下文
多服务项目 context: ./service-name 每个服务独立上下文
共享配置 context: . 可以访问所有子目录
monorepo context: ./packages/app 指向具体包目录

性能优化建议

1
2
3
4
5
6
7
8
9
10
11
12
# ❌ 不推荐:使用根目录作为 context
services:
web:
build:
context: . # 会包含所有文件,包括 node_modules、.git 等

# ✅ 推荐:使用 .dockerignore 排除不需要的文件
# 或者使用精确的 context 路径
services:
web:
build:
context: ./app # 只包含 app 目录
1
2
3
4
5
6
# .dockerignore(放在 context 目录下)
node_modules
.git
*.log
.env
dist

容器配置

1
2
3
4
5
6
7
services:
web:
container_name: my-web # 容器名称
hostname: web-server # 主机名
restart: always # 重启策略
user: "1000:1000" # 运行用户
working_dir: /app # 工作目录

端口映射

1
2
3
4
5
6
7
services:
web:
ports:
- "80:80" # host:container
- "443:443"
- "127.0.0.1:8080:8080" # 仅本地访问
- "8080-8090:8080-8090" # 端口范围

环境变量

1
2
3
4
5
6
7
8
services:
database:
environment:
- MYSQL_ROOT_PASSWORD=secret
- MYSQL_DATABASE=mydb
env_file:
- .env
- ./config/database.env

卷挂载

1
2
3
4
5
6
services:
database:
volumes:
- mysql-data:/var/lib/mysql # 命名卷
- ./init-scripts:/docker-entrypoint-initdb.d # 绑定卷
- ./config/my.cnf:/etc/mysql/conf.d/my.cnf:ro # 只读挂载

网络配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
web:
networks:
- frontend
ports:
- "80:80"

database:
networks:
- backend

networks:
frontend:
backend:
driver: bridge

依赖关系

1
2
3
4
5
6
7
8
9
10
11
12
services:
web:
depends_on:
- database
- redis

database:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3

资源限制

1
2
3
4
5
6
7
8
9
10
services:
web:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M

日志配置

1
2
3
4
5
6
7
services:
web:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

13. Docker Compose 常用命令

基础命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 启动所有服务(后台运行)
docker compose up -d

# 启动并重建镜像
docker compose up -d --build

# 停止所有服务
docker compose down

# 停止并删除卷
docker compose down -v

# 查看运行中的服务
docker compose ps

# 查看所有服务(包括停止的)
docker compose ps -a

服务管理

1
2
3
4
5
6
7
8
9
10
11
# 启动指定服务
docker compose start web

# 停止指定服务
docker compose stop web

# 重启服务
docker compose restart web

# 重建并重启服务
docker compose up -d --force-recreate web

日志和调试

1
2
3
4
5
6
7
8
9
10
11
# 查看所有服务日志
docker compose logs

# 查看指定服务日志
docker compose logs web

# 实时查看日志
docker compose logs -f web

# 查看最近 100 行日志
docker compose logs --tail=100 web

执行命令

1
2
3
4
5
6
7
8
# 在运行中的容器中执行命令
docker compose exec web bash

# 执行数据库命令
docker compose exec database mysql -u root -p

# 运行一次性容器
docker compose run web python manage.py migrate

镜像管理

1
2
3
4
5
6
7
8
9
10
11
# 构建所有镜像
docker compose build

# 构建指定服务镜像
docker compose build web

# 拉取最新镜像
docker compose pull

# 清理未使用的镜像
docker image prune -a

网络和卷管理

1
2
3
4
5
6
7
8
# 查看网络
docker network ls

# 查看卷
docker volume ls

# 清理未使用的卷
docker volume prune

配置验证

1
2
3
4
5
# 验证配置文件
docker compose config

# 查看完整配置
docker compose config --services

14. Docker Compose 实用示例

示例 1:Nginx 反向代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# docker-compose.yml
version: '3.9'

services:
nginx-proxy:
image: nginx:latest
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
- ./html:/usr/share/nginx/html
- ./logs:/var/log/nginx
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- web-network

networks:
web-network:
driver: bridge

示例 2:MySQL 数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# docker-compose.yml
version: '3.9'

services:
mysql:
image: mysql:8.0
container_name: mysql-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: StrongRootPass123
MYSQL_DATABASE: myapp
MYSQL_USER: admin
MYSQL_PASSWORD: StrongPassword123
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./mysql/conf.d:/etc/mysql/conf.d
- ./mysql/initdb:/docker-entrypoint-initdb.d
command: --default-authentication-plugin=mysql_native_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3
networks:
- backend-network

volumes:
mysql-data:

networks:
backend-network:
driver: bridge

示例 3:Redis 缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# docker-compose.yml
version: '3.9'

services:
redis:
image: redis:7-alpine
container_name: redis-cache
restart: always
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes --requirepass StrongRedisPass123
healthcheck:
test: ["CMD", "redis-cli", "-a", "StrongRedisPass123", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
- backend-network

volumes:
redis-data:

networks:
backend-network:
driver: bridge

示例 4:完整的 Web 应用栈

网络拓扑图

graph TB
    subgraph "frontend-network"
        Nginx["Nginx 反向代理<br/>:80/:443"]
        Frontend["前端服务<br/>:3000"]
        Backend["后端服务<br/>:8000"]
    end
    
    subgraph "backend-network"
        Backend2["后端服务"]
        MySQL["MySQL 数据库<br/>:3306"]
        Redis["Redis 缓存<br/>:6379"]
    end
    
    Nginx --> Frontend
    Frontend --> Backend
    Backend2 --> MySQL
    Backend2 --> Redis
    
    style Nginx fill:#4CAF50
    style Frontend fill:#2196F3
    style Backend fill:#FF9800
    style Backend2 fill:#FF9800
    style MySQL fill:#00BCD4
    style Redis fill:#F44336

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
services:
# 前端服务
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: frontend
restart: always
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- API_URL=http://backend:8000
depends_on:
backend:
condition: service_healthy
networks:
- frontend-network

# 后端服务
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: backend
restart: always
ports:
- "8000:8000"
environment:
- DATABASE_URL=mysql://admin:StrongPassword123@database:3306/myapp
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY=your-secret-key-here
depends_on:
database:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend-network
- backend-network

# 数据库
database:
image: mysql:8.0
container_name: mysql-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: StrongRootPass123
MYSQL_DATABASE: myapp
MYSQL_USER: admin
MYSQL_PASSWORD: StrongPassword123
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3
networks:
- backend-network

# 缓存
redis:
image: redis:7-alpine
container_name: redis-cache
restart: always
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
- backend-network

# Nginx 反向代理
nginx:
image: nginx:latest
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- frontend
- backend
networks:
- frontend-network

volumes:
mysql-data:
redis-data:

networks:
frontend-network:
backend-network:

示例 5:Python Django 应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# docker-compose.yml
version: '3.9'

services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- DEBUG=1
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
depends_on:
- db
networks:
- app-network

db:
image: postgres:14
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- app-network

redis:
image: redis:7
ports:
- "6379:6379"
networks:
- app-network

volumes:
postgres-data:

networks:
app-network:

示例 6:微服务架构

网络拓扑图

graph TB
    subgraph "microservices-network"
        Gateway["API 网关 Kong<br/>:8000/:8443/:8001"]
        UserService["用户服务<br/>:3001"]
        OrderService["订单服务<br/>:3002"]
        MongoDB["MongoDB<br/>:27017"]
        PostgreSQL["PostgreSQL<br/>:5432"]
        Redis["Redis<br/>:6379"]
    end
    
    Gateway --> UserService
    Gateway --> OrderService
    UserService --> MongoDB
    OrderService --> PostgreSQL
    OrderService --> Redis
    
    style Gateway fill:#4CAF50
    style UserService fill:#2196F3
    style OrderService fill:#FF9800
    style MongoDB fill:#00BCD4
    style PostgreSQL fill:#3F51B5
    style Redis fill:#F44336

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
services:
# API 网关
api-gateway:
image: kong:latest
container_name: api-gateway
restart: always
ports:
- "8000:8000"
- "8443:8443"
- "8001:8001"
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /etc/kong/kong.yml
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
volumes:
- ./kong:/etc/kong
networks:
- microservices-network

# 用户服务
user-service:
build: ./services/user
container_name: user-service
restart: always
ports:
- "3001:3000"
environment:
- NODE_ENV=production
- DB_HOST=mongodb
- DB_PORT=27017
- DB_NAME=userdb
depends_on:
- mongodb
networks:
- microservices-network

# 订单服务
order-service:
build: ./services/order
container_name: order-service
restart: always
ports:
- "3002:3000"
environment:
- NODE_ENV=production
- DB_HOST=postgresql
- DB_PORT=5432
- DB_NAME=orderdb
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- postgresql
- redis
networks:
- microservices-network

# MongoDB(用户数据)
mongodb:
image: mongo:6
container_name: mongodb
restart: always
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
networks:
- microservices-network

# PostgreSQL(订单数据)
postgresql:
image: postgres:14
container_name: postgresql
restart: always
environment:
POSTGRES_DB: orderdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- microservices-network

# Redis(缓存)
redis:
image: redis:7-alpine
container_name: redis
restart: always
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- microservices-network

volumes:
mongo-data:
postgres-data:
redis-data:

networks:
microservices-network:
driver: bridge

15. Docker Compose 常见问题和坑

问题 1:容器启动顺序问题

现象:应用启动时数据库还没准备好,导致连接失败。

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
services:
web:
depends_on:
database:
condition: service_healthy

database:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 3

问题 2:网络通信问题

现象:容器之间无法通信。

解决方案

1
2
3
4
5
6
7
8
9
10
11
services:
web:
networks:
- app-network
database:
networks:
- app-network

networks:
app-network:
driver: bridge

问题 3:数据持久化问题

现象:容器重启后数据丢失。

graph TB
    subgraph "不使用数据卷(数据丢失)"
        A1["容器停止"]
        A2["数据消失"]
        A3["新容器启动"]
        A4["数据丢失"]
    end
    
    subgraph "使用数据卷(数据持久化)"
        B1["容器停止"]
        B2["数据保存在卷中"]
        B3["新容器启动"]
        B4["数据恢复"]
    end
    
    A1 --> A2 --> A3 --> A4
    B1 --> B2 --> B3 --> B4
    
    style A2 fill:#FFB6C1
    style A4 fill:#FFB6C1
    style B2 fill:#90EE90
    style B4 fill:#90EE90

解决方案

1
2
3
4
5
6
7
services:
database:
volumes:
- mysql-data:/var/lib/mysql

volumes:
mysql-data:

问题 4:端口冲突

现象:端口被占用,容器无法启动。

解决方案

1
2
3
4
5
6
# 查看端口占用
netstat -tulpn | grep :80

# 修改映射端口
ports:
- "8080:80" # 将主机8080映射到容器80

问题 5:环境变量不生效

现象:配置的环境变量没有生效。

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
web:
# 正确方式:使用列表格式
environment:
- NODE_ENV=production
- API_KEY=12345

# 或使用字典格式
environment:
NODE_ENV: production
API_KEY: 12345

# 或使用 env_file
env_file:
- .env

问题 6:构建缓存问题

现象:修改代码后没有生效。

解决方案

1
2
3
4
5
# 无缓存构建
docker compose build --no-cache

# 强制重建容器
docker compose up -d --force-recreate

问题 7:权限问题

现象:容器内文件权限错误。

解决方案

1
2
3
4
5
services:
web:
user: "1000:1000" # 指定用户和组
volumes:
- ./app:/app # 确保本地目录权限正确

问题 8:容器无法访问宿主机服务

现象:容器内无法访问宿主机的数据库等服务。

解决方案

1
2
3
4
services:
web:
extra_hosts:
- "host.docker.internal:host-gateway"

问题 9:日志过大

现象:容器日志占用大量磁盘空间。

解决方案

1
2
3
4
5
6
7
services:
web:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

问题 10:生产环境安全

现象:敏感信息泄露。

解决方案

1
2
3
4
5
6
7
8
9
10
services:
database:
environment:
- MYSQL_ROOT_PASSWORD=/run/secrets/db_root_password
secrets:
- db_root_password

secrets:
db_root_password:
file: ./secrets/db_root_password.txt

问题 11:.env 文件自动读取机制

现象:不清楚 .env 文件如何被自动读取和注入。

详解

当你运行 docker-compose up 时,Compose 会自动查找当前目录下的 .env 文件,并将其中的变量注入到 docker-compose.yml 中。

自动读取规则

graph TB
    subgraph "项目目录结构 myapp/"
        A["docker-compose.yml"]
        B[".env ✅ 会被自动读取"]
        C[".env.prod ❌ 不会被自动读取"]
        subgraph "config/"
            D[".env ❌ 不在当前目录,不会自动读取"]
        end
        E["app/"]
    end
    
    style B fill:#90EE90
    style C fill:#FFB6C1
    style D fill:#FFB6C1

变量优先级(高到低)

1
2
3
4
1. shell 环境变量                    # export APP_PORT=9090(最高优先级)
2. --env-file 指定的文件 # docker-compose --env-file .env.prod up
3. 当前目录的 .env 文件 # 默认自动读取
4. Dockerfile 中的 ENV # 最低优先级

示例

1
2
3
4
# .env 文件
APP_PORT=8080
DB_PASSWORD=MySecretPass123
IMAGE_TAG=v2.0.0
1
2
3
4
5
6
7
8
9
10
# docker-compose.yml
services:
web:
image: myapp:${IMAGE_TAG} # 自动读取 .env 中的 IMAGE_TAG
ports:
- "${APP_PORT}:5000" # 自动读取 APP_PORT
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # 自动读取 DB_PASSWORD

注意.env 是给 docker-compose.yml 本身用的,如果想给容器内部注入环境变量,需要用 env_file

1
2
3
4
5
6
7
services:
web:
image: myapp
env_file: # 这个是把变量注入到容器内部
- ./app.env # 容器内的进程可以读到这些变量
environment:
- EXTRA_VAR=value # 也可以直接写

问题 12:docker-compose up -d 与 up -d –build 的区别

现象:不清楚这两个命令的区别。

详解

命令 行为 适用场景
up -d 如果镜像已存在,直接启动;如果不存在,则构建 日常开发,代码没改
up -d --build 总是重新构建镜像,然后再启动 代码改了,需要重新构建

实际演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 情况1:只用 up -d(不会重新构建)
docker-compose up -d
# 输出:Container myapp-web Already running
# 结果:代码改了,但容器还是旧的

# 情况2:用 up -d --build(强制重新构建)
docker-compose up -d --build
# 输出:
# Building web
# Step 1/5 : FROM python:3.9-slim
# ...
# Successfully built abc123
# Recreating myapp-web ... done
# 结果:代码改了,容器也更新了

三种构建方式的对比

1
2
3
4
5
6
7
8
9
# 方式1:build + up(两步操作)
docker-compose build web # 先构建
docker-compose up -d # 再启动

# 方式2:up --build(一步到位,推荐)
docker-compose up -d --build # 构建并启动

# 方式3:单纯 up(不构建)
docker-compose up -d # 只启动,不构建

日常工作流

1
2
3
4
5
6
7
8
9
10
11
# 早上开始工作时(代码可能有改动)
docker-compose up -d --build

# 开发过程中(只改了配置文件或环境变量)
docker-compose up -d # 不需要重新构建

# 改了代码后
docker-compose up -d --build web # 只重新构建 web 服务

# 确认没问题后,重启所有
docker-compose restart

第三部分:最佳实践

16. Docker 最佳实践

镜像构建最佳实践

  1. 使用多阶段构建:减小镜像体积
  2. 使用 .dockerignore:排除不需要的文件
  3. 合并 RUN 指令:减少镜像层数
  4. 使用官方基础镜像:确保安全性
  5. 设置非 root 用户:提高安全性

容器运行最佳实践

  1. 使用 –rm 参数:自动删除停止的容器
  2. 设置资源限制:避免资源耗尽
  3. 使用健康检查:确保服务可用
  4. 使用命名卷:数据持久化
  5. 使用日志限制:避免磁盘占满

安全最佳实践

  1. 不使用 root 用户:容器内使用非 root 用户
  2. 扫描镜像漏洞:使用 docker scan
  3. 使用 secrets 管理敏感信息:不硬编码密码
  4. 限制网络访问:使用自定义网络
  5. 定期更新镜像:获取安全补丁

17. Docker Compose 最佳实践

配置最佳实践

  1. 使用版本控制:将配置文件纳入 Git
  2. 使用环境变量:敏感信息不硬编码
  3. 使用多文件配置:开发、生产环境分离
  4. 使用健康检查:确保服务依赖正确
  5. 使用资源限制:避免资源竞争

开发环境最佳实践

  1. 使用绑定卷:代码修改实时生效
  2. 使用 watch 模式:自动重建容器
  3. 使用 override 文件:覆盖默认配置
  4. 使用 .env 文件:管理环境变量
  5. 使用 profiles:按需启动服务

生产环境最佳实践

  1. 使用固定版本标签:避免意外更新
  2. 使用资源限制:确保稳定性
  3. 使用日志管理:集中收集日志
  4. 使用 secrets:安全存储密码
  5. 使用 healthcheck:确保服务可用

18. 总结

Docker 核心要点

  1. 镜像:应用的只读模板
  2. 容器:镜像的运行实例
  3. 网络:容器间通信
  4. :数据持久化
  5. Dockerfile:构建镜像的脚本

Docker Compose 核心要点

  1. 服务:容器的配置模板
  2. 网络:服务间通信
  3. :数据持久化
  4. 依赖管理:服务启动顺序
  5. 健康检查:服务状态监控

常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Docker
docker run -d --name my-nginx -p 80:80 nginx
docker ps -a
docker logs -f my-nginx
docker exec -it my-nginx bash
docker stop my-nginx
docker rm my-nginx

# Docker Compose
docker compose up -d
docker compose down
docker compose ps
docker compose logs -f
docker compose exec web bash
docker compose build --no-cache

学习资源

  • Docker 官方文档
  • Docker Compose 官方文档
  • Docker Hub
  • Docker 官方示例

最后更新:2026年8月24日