> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Docker 中级指南
- URL: https://blog.vercanti.com/docker-zhong-ji-zhi-nan/
- Published: 2026-08-28T14:34:21.000Z
- Updated: 2026-08-28T14:56:20.000Z
- Description: 本文面向已掌握 Docker 基础操作的读者，系统讲解 Dockerfile 指令全集、多阶段构建、Docker Compose 完整配置、构建最佳实践、数据管理、网络进阶、日志管理与资源限制。 FROM 是每个 Dockerfile 的起点，声明基础镜像。 语法 参数 scratch 基础镜像 scratch 是 Docker 内置的空镜像，不包含任何文件系统内容。适用于完全静态编译的二进制文件（如 Go 程序）。 ARG 与 FROM 联动 ARG 在 FROM 之前声明时，作用域仅限于 FROM 指令本身，不会传入后续构建阶段。若要在构建阶段内继续
- Author: yellowdog
- Tags: DevOps, Docker

> 官方文档：<https://docs.docker.com/>  
> 适用版本：Docker Engine 24+（2026-05-07 核实）

本文面向已掌握 Docker 基础操作的读者，系统讲解 Dockerfile 指令全集、多阶段构建、Docker Compose 完整配置、构建最佳实践、数据管理、网络进阶、日志管理与资源限制。

---

## 1\. Dockerfile 完整指令参考

### FROM

`FROM` 是每个 Dockerfile 的起点，声明基础镜像。

**语法**

```dockerfile
FROM [--platform=<platform>] <image>[:tag|@digest] [AS name]

```

**参数**

| 参数          | 类型     | 默认值    | 说明                                             |
| ----------- | ------ | ------ | ---------------------------------------------- |
| \--platform | string | 宿主机平台  | 指定目标平台，如 linux/amd64、linux/arm64、windows/amd64 |
| image       | string | 必填     | 基础镜像名，可附带 registry 地址                          |
| :tag        | string | latest | 镜像标签，生产环境应指定确定版本                               |
| @digest     | string | \-     | 用 SHA256 摘要锁定精确版本，比 tag 更可靠                    |
| AS name     | string | \-     | 为该阶段命名，供多阶段构建中 COPY --from 引用                  |

**scratch 基础镜像**

`scratch` 是 Docker 内置的空镜像，不包含任何文件系统内容。适用于完全静态编译的二进制文件（如 Go 程序）。

```dockerfile
FROM scratch
COPY myapp /myapp
ENTRYPOINT ["/myapp"]

```

**ARG 与 FROM 联动**

`ARG` 在 `FROM` 之前声明时，作用域仅限于 `FROM` 指令本身，不会传入后续构建阶段。若要在构建阶段内继续使用该变量，需在 `FROM` 之后重新声明一个同名 `ARG`（可不带默认值）。

```dockerfile
ARG BASE_VERSION=3.11
FROM python:${BASE_VERSION}-slim

# 此处 BASE_VERSION 已失效，需重新声明
ARG BASE_VERSION
RUN echo "Building on Python ${BASE_VERSION}"

```

**示例**

```dockerfile
# 锁定摘要，完全可重现
FROM node:20.11.0-alpine3.19@sha256:abc123... AS builder

# 多平台构建
FROM --platform=linux/arm64 debian:bookworm-slim AS arm-stage

```

---

### RUN

执行命令并创建新镜像层。

**两种形式**

| 形式       | 语法                                   | 说明                                      |
| -------- | ------------------------------------ | --------------------------------------- |
| shell 形式 | RUN command                          | 默认通过 /bin/sh -c 执行，支持 shell 变量展开、管道、通配符 |
| exec 形式  | RUN \["executable", "arg1", "arg2"\] | 直接执行，不启动 shell，无变量展开，JSON 数组格式，需用双引号    |

**\--mount 选项**

`RUN --mount` 在构建时挂载文件系统，不会写入最终镜像层。

```dockerfile
# 语法
RUN --mount=type=<TYPE>[,option=value,...] <command>

```

| 选项       | 类型/可选值                              | 默认值       | 说明                             |
| -------- | ----------------------------------- | --------- | ------------------------------ |
| type     | bind / cache / secret / ssh / tmpfs | bind      | 挂载类型                           |
| target   | string                              | \-        | 挂载到容器内的路径                      |
| source   | string                              | \-        | 宿主机路径（bind）或缓存 ID（cache）       |
| from     | string                              | \-        | 来源构建阶段名称（bind 类型）              |
| readonly | bool                                | false     | 设为 true 则以只读方式挂载               |
| id       | string                              | target 路径 | 缓存键，type=cache 时用于区分不同缓存槽      |
| sharing  | shared / private / locked           | shared    | 并发构建时的缓存共享策略                   |
| uid      | int                                 | 0         | 挂载点的属主用户 ID                    |
| gid      | int                                 | 0         | 挂载点的属主组 ID                     |
| mode     | octal                               | 0755      | 挂载点权限（tmpfs 类型）                |
| size     | bytes                               | \-        | tmpfs 大小限制                     |
| required | bool                                | false     | type=secret 时，若 secret 不存在是否报错 |

各类型典型用法：

```dockerfile
# cache：复用包管理器缓存，加速后续构建
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# cache：pip 缓存
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# secret：注入敏感信息，不留入镜像层
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm install

# bind：从另一构建阶段读取文件
RUN --mount=type=bind,from=builder,source=/app/dist,target=/tmp/dist \
    cp -r /tmp/dist /app/

# ssh：使用宿主机 SSH agent 克隆私有仓库
RUN --mount=type=ssh \
    git clone git@github.com:org/private-repo.git

```

**\--network 选项**

| 值       | 说明                  |
| ------- | ------------------- |
| default | 使用构建时的默认网络（通常有外网访问） |
| none    | 无网络访问，提升安全性与可重现性    |
| host    | 使用宿主机网络命名空间         |

```dockerfile
RUN --network=none pip install --no-index --find-links=/wheels -r requirements.txt

```

**\--security 选项**

| 值        | 说明                                          |
| -------- | ------------------------------------------- |
| sandbox  | 默认，受限沙箱环境                                   |
| insecure | 以提升权限运行（需要 \--allow security.insecure 构建标志） |

**Heredoc 语法**

BuildKit 支持在 `RUN` 中使用 heredoc，适合编写多行脚本而不用大量 `&&`：

```dockerfile
RUN <<EOF
set -e
apt-get update
apt-get install -y curl git
rm -rf /var/lib/apt/lists/*
EOF

```

**最佳实践：合并 RUN 减少层数**

```dockerfile
# 不推荐：每条 RUN 产生一个独立层
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# 推荐：合并为一层，并清理缓存
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        git \
    && rm -rf /var/lib/apt/lists/*

```

---

### COPY

将文件从构建上下文或其他构建阶段复制到镜像中。

**语法**

```dockerfile
COPY [OPTIONS] <src>... <dest>

```

**参数**

| 参数         | 类型     | 默认值   | 说明                               |
| ---------- | ------ | ----- | -------------------------------- |
| \--from    | string | 构建上下文 | 指定来源为其他构建阶段名称或外部镜像名              |
| \--chown   | string | \-    | 设置文件属主，格式 user:group，仅 Linux     |
| \--chmod   | string | \-    | 设置文件权限，如 755、0644                |
| \--link    | bool   | false | 创建独立文件系统层，提升缓存复用率，即使前面的层变化也不会失效  |
| \--parents | bool   | false | 保留源文件的目录结构（从 Dockerfile 1.7+ 支持） |
| \--exclude | string | \-    | 排除匹配 glob 模式的文件，可多次使用            |

```dockerfile
# 从另一阶段复制构建产物
COPY --from=builder /app/dist /app/dist

# 设置属主与权限
COPY --chown=node:node --chmod=644 package*.json ./

# 保留目录结构
COPY --parents src/components/**/*.tsx /app/

# 排除测试文件
COPY --exclude=**/*.test.ts src/ /app/src/

# 使用 --link 提升缓存复用（推荐）
COPY --link package.json package-lock.json ./

```

**路径规则**

- `src` 支持 Go filepath 通配符（`*`、`?`、`**`）
- `dest` 以 `/` 结尾视为目录；不以 `/` 结尾时，若复制单文件则视为目标文件名
- 目标目录不存在会自动创建

---

### ADD

功能是 `COPY` 的超集，支持 URL 下载和自动解压 tar 包。

**语法**

```dockerfile
ADD [OPTIONS] <src>... <dest>
ADD [OPTIONS] <git ref> <dir>

```

**参数**

| 参数              | 类型     | 默认值   | 说明                                   |
| --------------- | ------ | ----- | ------------------------------------ |
| \--chown        | string | \-    | 同 COPY --chown                       |
| \--chmod        | string | \-    | 同 COPY --chmod                       |
| \--link         | bool   | false | 同 COPY --link                        |
| \--checksum     | string | \-    | 校验 URL 下载文件的 SHA256，格式 sha256:abc... |
| \--keep-git-dir | bool   | false | 克隆 git 仓库时保留 .git 目录                 |
| \--exclude      | string | \-    | 排除文件（BuildKit）                       |

**ADD vs COPY 对比**

| 维度       | COPY   | ADD                         |
| -------- | ------ | --------------------------- |
| 本地文件复制   | 支持     | 支持                          |
| URL 下载   | 不支持    | 支持                          |
| tar 自动解压 | 不支持    | 支持（.tar、.tar.gz、.tar.bz2 等） |
| 安全风险     | 低      | URL 来源需谨慎                   |
| 推荐场景     | 绝大多数情况 | 需要解压 tar 或校验 URL 文件时        |

```dockerfile
# 下载并校验文件
ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d \
    https://github.com/moby/moby/releases/download/v27.0.0/moby-27.0.0.tgz /tmp/moby.tgz

# 自动解压本地 tar（ADD 特有）
ADD rootfs.tar.xz /

```

---

### ARG vs ENV

| 维度                | ARG                    | ENV                         |
| ----------------- | ---------------------- | --------------------------- |
| 作用时机              | 仅构建时                   | 构建时 + 运行时                   |
| 是否保存到镜像           | 否（不会出现在运行时环境）          | 是                           |
| docker history 可见 | 是（值可被看到）               | 是                           |
| 运行时覆盖方式           | 不支持                    | \-e KEY=VALUE 或 \--env-file |
| 构建时覆盖方式           | \--build-arg KEY=VALUE | \--build-arg（需 ARG 配合）      |
| 默认值               | 可在 Dockerfile 中设置      | 可在 Dockerfile 中设置           |
| 安全性               | 不适合存放密钥（history 可见）    | 同左，均不安全                     |
| 典型用途              | 版本号、构建开关、平台参数          | 应用运行时配置                     |

```dockerfile
ARG NODE_VERSION=20
ARG BUILD_ENV=production

FROM node:${NODE_VERSION}-alpine

# FROM 之后 ARG 需重新声明才能使用
ARG BUILD_ENV
ENV NODE_ENV=${BUILD_ENV}

```

---

### CMD vs ENTRYPOINT

`ENTRYPOINT` 定义容器的主执行程序，`CMD` 提供默认参数。两者均有 shell 形式和 exec 形式。

**组合效果矩阵**

| ENTRYPOINT             | CMD               | 实际执行                         |
| ---------------------- | ----------------- | ---------------------------- |
| 未设置                    | \["cmd", "arg"\]  | cmd arg                      |
| 未设置                    | cmd arg（shell 形式） | /bin/sh -c cmd arg           |
| \["ep"\]（exec）         | 未设置               | ep                           |
| \["ep"\]（exec）         | \["cmd", "arg"\]  | ep cmd arg                   |
| \["ep"\]（exec）         | cmd arg（shell 形式） | ep /bin/sh -c cmd arg（通常非预期） |
| ep（shell 形式）           | 任意                | /bin/sh -c ep（CMD 被忽略）       |
| \["ep", "arg1"\]（exec） | \["arg2"\]        | ep arg1 arg2                 |

**推荐模式**

`ENTRYPOINT` 使用 exec 形式指定固定的可执行程序，`CMD` 提供可被 `docker run` 覆盖的默认参数：

```dockerfile
ENTRYPOINT ["nginx", "-g", "daemon off;"]
CMD []

# 或
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8000"]

```

`docker run myimage --port 9000` 会用 `--port 9000` 替换 CMD，最终执行 `python app.py --port 9000`。

---

### HEALTHCHECK

声明容器健康检查命令，Docker daemon 会定期执行并更新容器状态（`healthy` / `unhealthy` / `starting`）。

**语法**

```dockerfile
HEALTHCHECK [OPTIONS] CMD <command>
HEALTHCHECK NONE   # 禁用从父镜像继承的健康检查

```

**参数**

| 参数                | 类型       | 默认值 | 说明                      |
| ----------------- | -------- | --- | ----------------------- |
| \--interval       | duration | 30s | 相邻两次检查的间隔时间             |
| \--timeout        | duration | 30s | 单次检查的超时时间，超时视为失败        |
| \--start-period   | duration | 0s  | 容器启动宽限期，期间失败不计入 retries |
| \--start-interval | duration | 5s  | 宽限期内的检查间隔（Docker 25.0+） |
| \--retries        | int      | 3   | 连续失败达到此次数才判定为 unhealthy |

CMD 的退出码含义：`0` \= healthy，`1` \= unhealthy，`2` \= reserved（不使用）。

```dockerfile
# HTTP 接口健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 无 curl 时用 wget
HEALTHCHECK --interval=15s --timeout=5s \
    CMD wget -qO- http://localhost:8080/ping | grep -q '"status":"ok"' || exit 1

# 检查进程是否存在
HEALTHCHECK --interval=10s \
    CMD pgrep nginx || exit 1

```

---

### ONBUILD

`ONBUILD` 声明的指令不在当前镜像构建时执行，而是在以此镜像为 `FROM` 的子镜像构建时触发。

**语法**

```dockerfile
ONBUILD <Dockerfile_INSTRUCTION>

```

**典型场景**：制作团队基础镜像，子项目只需 `FROM` 即可自动完成代码注入和依赖安装。

```dockerfile
# 父镜像 Dockerfile（my-node-base）
FROM node:20-alpine
WORKDIR /app
ONBUILD COPY package*.json ./
ONBUILD RUN npm ci
ONBUILD COPY . .

# 子项目 Dockerfile（只需两行）
FROM my-node-base
CMD ["node", "server.js"]

```

注意：`ONBUILD ONBUILD` 不合法；`ONBUILD FROM` 和 `ONBUILD MAINTAINER` 也不允许。

---

### SHELL

更改后续 shell 形式指令（`RUN`、`CMD`、`ENTRYPOINT`）使用的默认 shell。

```dockerfile
# Linux 切换到 bash（默认 /bin/sh）
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
RUN echo "Now using bash"

# Windows 切换到 PowerShell
SHELL ["powershell", "-Command"]
RUN Write-Host "PowerShell"

```

---

### STOPSIGNAL

设置 `docker stop` 时发送给容器主进程的信号。

```dockerfile
# 默认
STOPSIGNAL SIGTERM

# 让 Nginx 优雅退出（SIGQUIT）
STOPSIGNAL SIGQUIT

```

| 常用信号    | 含义                              |
| ------- | ------------------------------- |
| SIGTERM | 默认，请求程序终止                       |
| SIGQUIT | 带 core dump 的退出（Nginx 用此进行优雅关闭） |
| SIGINT  | 等同 Ctrl+C                       |
| SIGUSR1 | Nginx reload 配置                 |

---

### LABEL

为镜像添加键值对元数据，推荐遵循 OCI 标准标签。

**OCI 标准标签**

| 标签键                                  | 说明             |
| ------------------------------------ | -------------- |
| org.opencontainers.image.title       | 镜像人类可读名称       |
| org.opencontainers.image.description | 简短描述           |
| org.opencontainers.image.version     | 应用版本           |
| org.opencontainers.image.created     | 创建时间（RFC 3339） |
| org.opencontainers.image.authors     | 作者信息           |
| org.opencontainers.image.url         | 项目主页           |
| org.opencontainers.image.source      | 源代码仓库 URL      |
| org.opencontainers.image.licenses    | SPDX 许可证标识     |
| org.opencontainers.image.revision    | Git commit SHA |

```dockerfile
LABEL \
    org.opencontainers.image.title="My App" \
    org.opencontainers.image.description="Production API server" \
    org.opencontainers.image.version="2.1.0" \
    org.opencontainers.image.created="2026-03-08T00:00:00Z" \
    org.opencontainers.image.authors="team@example.com" \
    org.opencontainers.image.source="https://github.com/org/repo" \
    org.opencontainers.image.licenses="MIT"

```

---

## 2\. 多阶段构建（Multi-stage Builds）

### 为什么要用多阶段构建

构建工具链（编译器、构建依赖、测试框架）不应出现在生产镜像中，但传统单阶段 Dockerfile 很难将二者分离。多阶段构建允许在一个 Dockerfile 中定义多个阶段，最终镜像只包含最后阶段的内容。

典型收益：

| 场景         | 单阶段大小    | 多阶段大小    |
| ---------- | -------- | -------- |
| Node.js 应用 | \~1.2 GB | \~150 MB |
| Go 应用      | \~800 MB | \~10 MB  |
| Java/Maven | \~600 MB | \~200 MB |

### 基本语法

```dockerfile
FROM base-image AS stage-name
# ... 构建步骤

FROM another-image AS final
COPY --from=stage-name /path/in/stage /path/in/final

```

### Node.js 应用示例

```dockerfile
# 阶段 1：安装依赖并构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

# 阶段 2：生产运行时
FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]

```

### Go 应用示例（完全静态二进制）

```dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o myapp ./cmd/server

# 使用 scratch 空镜像
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/myapp /myapp
EXPOSE 8080
ENTRYPOINT ["/myapp"]

```

### Java/Maven 应用示例

```dockerfile
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml ./
RUN --mount=type=cache,target=/root/.m2 \
    mvn dependency:go-offline -q
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 \
    mvn package -DskipTests -q

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S spring && adduser -S spring -G spring
COPY --from=builder --chown=spring:spring /build/target/app.jar ./app.jar
USER spring
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

```

### \--target 指定构建到某个阶段

```bash
# 只构建到 builder 阶段，用于调试
docker build --target builder -t myapp:debug .

```

### 从外部镜像 COPY

`--from` 不仅可以引用本 Dockerfile 中的阶段，还可以引用任意外部镜像：

```dockerfile
# 从官方 Nginx 镜像复制默认配置
COPY --from=nginx:1.25-alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf

# 从 distroless 镜像复制证书
COPY --from=gcr.io/distroless/base /etc/ssl/certs /etc/ssl/certs

```

### 并行阶段构建

BuildKit 会自动分析阶段依赖关系，将没有依赖关系的阶段并行构建，无需额外配置：

```dockerfile
FROM node:20 AS frontend-builder
# ... 前端构建（与 backend-builder 并行）

FROM golang:1.22 AS backend-builder
# ... 后端构建（与 frontend-builder 并行）

FROM nginx:alpine AS final
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
COPY --from=backend-builder /build/api /usr/local/bin/api

```

---

## 3\. Docker Compose 完整配置参考

### 顶级键结构

```yaml
# version 字段已废弃，现代 Compose 无需填写
services:      # 服务定义（必填）
networks:      # 网络定义
volumes:       # 卷定义
configs:       # 配置定义（Swarm/Compose）
secrets:       # 密钥定义（Swarm/Compose）

```

### services 完整配置

#### 镜像与构建

| 参数                       | 类型            | 默认值        | 说明                                                  |
| ------------------------ | ------------- | ---------- | --------------------------------------------------- |
| image                    | string        | \-         | 使用的镜像名及标签                                           |
| build                    | string/object | \-         | 构建上下文路径（字符串）或构建配置（对象）                               |
| build.context            | string        | .          | 构建上下文目录                                             |
| build.dockerfile         | string        | Dockerfile | 指定 Dockerfile 文件名或路径                                |
| build.dockerfile\_inline | string        | \-         | 内联 Dockerfile 内容                                    |
| build.args               | map/list      | \-         | 传递给 \--build-arg 的构建参数                              |
| build.target             | string        | \-         | 多阶段构建的目标阶段                                          |
| build.cache\_from        | list          | \-         | 指定缓存来源镜像列表                                          |
| build.cache\_to          | list          | \-         | 指定缓存输出目标                                            |
| build.shm\_size          | string/int    | \-         | 构建容器的共享内存大小                                         |
| build.labels             | map/list      | \-         | 为构建镜像添加元数据                                          |
| build.platforms          | list          | \-         | 目标平台列表（多平台构建）                                       |
| build.network            | string        | \-         | 构建时使用的网络模式                                          |
| build.no\_cache          | bool          | false      | 禁用构建缓存                                              |
| build.pull               | bool          | false      | 强制拉取最新基础镜像                                          |
| pull\_policy             | string        | missing    | always / never / missing / build / if\_not\_present |

```yaml
services:
  app:
    build:
      context: .
      dockerfile: docker/Dockerfile.prod
      args:
        - NODE_VERSION=20
        - BUILD_ENV=production
      target: production
      cache_from:
        - myregistry.io/myapp:cache
      shm_size: 128m
    pull_policy: always

```

#### 容器行为

| 参数                  | 类型          | 默认值     | 说明                                                   |
| ------------------- | ----------- | ------- | ---------------------------------------------------- |
| container\_name     | string      | 自动生成    | 自定义容器名，设置后不能横向扩展                                     |
| command             | string/list | \-      | 覆盖镜像的 CMD                                            |
| entrypoint          | string/list | \-      | 覆盖镜像的 ENTRYPOINT                                     |
| restart             | string      | no      | 重启策略：no / always / on-failure\[:N\] / unless-stopped |
| stop\_signal        | string      | SIGTERM | 停止容器时发送的信号                                           |
| stop\_grace\_period | duration    | 10s     | 发送 SIGKILL 前等待容器优雅退出的时间                              |
| init                | bool        | false   | 使用 init 进程（tini），解决僵尸进程问题                            |
| tty                 | bool        | false   | 分配伪终端（等同 docker run -t）                              |
| stdin\_open         | bool        | false   | 保持 stdin 开启（等同 docker run -i）                        |
| read\_only          | bool        | false   | 以只读文件系统运行容器                                          |
| user                | string      | \-      | 运行用户，格式 user、user:group、uid:gid                      |
| working\_dir        | string      | \-      | 容器内工作目录                                              |
| platform            | string      | \-      | 目标平台，如 linux/amd64                                   |
| privileged          | bool        | false   | 以特权模式运行（拥有宿主机所有能力）                                   |
| cap\_add            | list        | \-      | 添加 Linux capabilities                                |
| cap\_drop           | list        | \-      | 移除 Linux capabilities                                |
| security\_opt       | list        | \-      | 安全选项，如 no-new-privileges:true                        |
| sysctls             | map/list    | \-      | 设置内核参数                                               |
| ulimits             | object      | \-      | 设置资源限制（nofile、nproc 等）                               |
| shm\_size           | string/int  | \-      | /dev/shm 大小                                          |
| ipc                 | string      | \-      | IPC 命名空间：shareable / host / service:\[name\]         |
| pid                 | string      | \-      | PID 命名空间：host                                        |

```yaml
services:
  api:
    command: ["python", "-m", "uvicorn", "main:app", "--reload"]
    restart: unless-stopped
    stop_grace_period: 30s
    init: true
    read_only: true
    user: "1000:1000"
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

```

#### 端口与网络

| 参数            | 类型          | 默认值     | 说明                             |
| ------------- | ----------- | ------- | ------------------------------ |
| ports         | list        | \-      | 端口映射，短语法或长语法                   |
| expose        | list        | \-      | 声明容器监听的端口，不发布到宿主机              |
| networks      | map/list    | default | 连接到的网络列表                       |
| network\_mode | string      | bridge  | host / none / service:\[name\] |
| hostname      | string      | 容器 ID   | 容器主机名                          |
| domainname    | string      | \-      | 容器域名                           |
| dns           | string/list | \-      | 自定义 DNS 服务器                    |
| dns\_search   | string/list | \-      | DNS 搜索域                        |
| dns\_opt      | list        | \-      | DNS 选项                         |
| extra\_hosts  | map/list    | \-      | 添加到 /etc/hosts 的条目             |
| mac\_address  | string      | \-      | 容器 MAC 地址                      |

**ports 长语法**

```yaml
ports:
  - target: 80          # 容器端口
    host_ip: 127.0.0.1  # 绑定的宿主机 IP
    published: "8080"   # 宿主机端口（字符串支持范围如 "8080-8090"）
    protocol: tcp       # tcp 或 udp
    mode: host          # host（单机发布）或 ingress（Swarm 负载均衡）

```

```yaml
services:
  web:
    ports:
      - "8080:80"          # 短语法
      - "127.0.0.1:9000:9000"  # 绑定本地地址
    networks:
      frontend:
        ipv4_address: 172.20.0.10
      backend:
    extra_hosts:
      - "db.internal:192.168.1.100"

```

#### 环境变量

| 参数          | 类型                 | 默认值 | 说明                                      |
| ----------- | ------------------ | --- | --------------------------------------- |
| environment | map/list           | \-  | 设置环境变量，list 格式 KEY=VALUE 或仅 KEY（从宿主机继承） |
| env\_file   | string/list/object | \-  | 从文件加载环境变量                               |

```yaml
services:
  app:
    environment:
      DATABASE_URL: postgres://user:pass@db/mydb
      DEBUG: "false"
      # 从宿主机继承（不写值）
      AWS_ACCESS_KEY_ID:

    # env_file 支持 required/optional（Compose 2.24+）
    env_file:
      - path: .env
        required: true
      - path: .env.local
        required: false

```

`.env` 文件格式：每行 `KEY=VALUE`，`#` 开头为注释，空行忽略。

#### 卷挂载

**短语法**

```yaml
volumes:
  - myvolume:/app/data          # 命名卷
  - ./config:/app/config:ro     # bind mount，只读
  - /tmp                        # 匿名卷（不推荐）

```

**长语法参数**

| 参数                      | 类型         | 默认值      | 说明                                                               |
| ----------------------- | ---------- | -------- | ---------------------------------------------------------------- |
| type                    | string     | volume   | volume / bind / tmpfs / npipe / cluster                          |
| source                  | string     | \-       | 卷名（volume 类型）或宿主机路径（bind 类型）                                     |
| target                  | string     | \-       | 容器内挂载路径                                                          |
| read\_only              | bool       | false    | 只读挂载                                                             |
| bind.propagation        | string     | rprivate | bind 传播模式：shared / slave / private / rshared / rslave / rprivate |
| bind.create\_host\_path | bool       | false    | 若宿主机路径不存在则自动创建                                                   |
| bind.selinux            | string     | \-       | SELinux 标签：z（共享）/ Z（私有）                                          |
| volume.nocopy           | bool       | false    | 不从容器复制初始数据到新卷                                                    |
| volume.subpath          | string     | \-       | 挂载卷内的子路径                                                         |
| tmpfs.size              | string/int | \-       | tmpfs 大小限制，如 64m                                                 |
| tmpfs.mode              | int        | 1777     | tmpfs 权限                                                         |

```yaml
services:
  app:
    volumes:
      - type: volume
        source: app-data
        target: /app/data
        volume:
          nocopy: true
      - type: bind
        source: ./nginx/conf.d
        target: /etc/nginx/conf.d
        read_only: true
        bind:
          selinux: z
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 64m

```

#### 资源限制

| 参数                                   | 类型     | 默认值 | 说明                  |
| ------------------------------------ | ------ | --- | ------------------- |
| deploy.resources.limits.cpus         | string | \-  | CPU 上限，如 "0.5" 表示半核 |
| deploy.resources.limits.memory       | string | \-  | 内存硬限制，如 512m、1g     |
| deploy.resources.limits.pids         | int    | \-  | 进程数上限               |
| deploy.resources.reservations.cpus   | string | \-  | CPU 软预留             |
| deploy.resources.reservations.memory | string | \-  | 内存软预留               |

```yaml
services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512m
        reservations:
          cpus: "0.25"
          memory: 128m

```

注意：`deploy.resources` 在 `docker compose up` 中也生效（不需要 Swarm 模式）。

#### 依赖与健康检查

**depends\_on 参数**

| 参数        | 类型     | 默认值              | 说明              |
| --------- | ------ | ---------------- | --------------- |
| condition | string | service\_started | 依赖条件            |
| restart   | bool   | false            | 依赖服务重启时是否重启当前服务 |
| required  | bool   | true             | 依赖服务不存在时是否报错    |

| condition 值                      | 说明                          |
| -------------------------------- | --------------------------- |
| service\_started                 | 依赖容器已启动（不保证就绪）              |
| service\_healthy                 | 依赖容器 healthcheck 返回 healthy |
| service\_completed\_successfully | 依赖容器正常退出（适用于 init 容器）       |

**healthcheck 参数**

| 参数              | 类型          | 默认值   | 说明                                                       |
| --------------- | ----------- | ----- | -------------------------------------------------------- |
| test            | string/list | \-    | 检查命令，list 形式：\["CMD", "curl", "-f", "http://localhost"\] |
| interval        | duration    | 30s   | 检查间隔                                                     |
| timeout         | duration    | 30s   | 超时时间                                                     |
| retries         | int         | 3     | 失败多少次判定 unhealthy                                        |
| start\_period   | duration    | 0s    | 启动宽限期                                                    |
| start\_interval | duration    | 5s    | 宽限期内检查间隔                                                 |
| disable         | bool        | false | 禁用健康检查                                                   |

```yaml
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  api:
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

```

#### 日志配置

| 参数              | 类型     | 默认值       | 说明      |
| --------------- | ------ | --------- | ------- |
| logging.driver  | string | json-file | 日志驱动名称  |
| logging.options | map    | \-        | 驱动特定的选项 |

json-file 驱动选项：

| 选项              | 类型     | 默认值      | 说明                      |
| --------------- | ------ | -------- | ----------------------- |
| max-size        | string | 无限制      | 单个日志文件最大大小，如 10m        |
| max-file        | string | 1        | 保留的日志文件数量               |
| compress        | string | unset    | 是否压缩轮转的文件               |
| labels          | string | \-       | 收集容器标签（逗号分隔）            |
| env             | string | \-       | 收集环境变量（逗号分隔）            |
| mode            | string | blocking | blocking / non-blocking |
| max-buffer-size | string | 1m       | non-blocking 模式的缓冲大小    |

```yaml
services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"

```

#### profiles（条件启动服务）

`profiles` 允许将服务分组，使用 `--profile` 标志选择性启动：

```yaml
services:
  app:
    image: myapp
    # 不设置 profiles，默认总是启动

  debug-tools:
    image: busybox
    profiles: [debug]    # 仅在 --profile debug 时启动

  docs:
    image: mkdocs
    profiles: [docs]

  loadtest:
    image: k6
    profiles: [test, perf]   # 多个 profile

```

```bash
# 启动默认服务 + debug 组
docker compose --profile debug up

# 启动所有 profile
docker compose --profile debug --profile docs up

```

#### extends（继承配置）

```yaml
# base.yml
services:
  base-service:
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

# docker-compose.yml
services:
  api:
    extends:
      file: base.yml
      service: base-service
    image: myapi:latest
    ports:
      - "8000:8000"

```

---

### networks 配置

| 参数                        | 类型       | 默认值      | 说明                                            |
| ------------------------- | -------- | -------- | --------------------------------------------- |
| driver                    | string   | bridge   | 网络驱动：bridge / overlay / host / none / macvlan |
| driver\_opts              | map      | \-       | 传给驱动的选项                                       |
| external                  | bool     | false    | 使用预先创建的外部网络，不由 Compose 管理                     |
| name                      | string   | 项目名\_网络名 | 自定义网络实际名称，避免项目名前缀                             |
| ipam                      | object   | \-       | IP 地址管理配置                                     |
| ipam.driver               | string   | default  | IPAM 驱动                                       |
| ipam.config               | list     | \-       | 子网配置列表                                        |
| ipam.config\[\].subnet    | string   | \-       | 子网 CIDR，如 172.20.0.0/16                       |
| ipam.config\[\].gateway   | string   | \-       | 网关地址                                          |
| ipam.config\[\].ip\_range | string   | \-       | 可分配 IP 范围                                     |
| internal                  | bool     | false    | 禁止容器访问外部网络                                    |
| attachable                | bool     | false    | Swarm 外的独立容器可加入此网络                            |
| labels                    | map/list | \-       | 网络元数据标签                                       |
| enable\_ipv6              | bool     | false    | 启用 IPv6                                       |

```yaml
networks:
  frontend:
    driver: bridge
    name: myapp_frontend    # 固定网络名，不受项目名影响
    ipam:
      config:
        - subnet: 172.20.0.0/24
          gateway: 172.20.0.1

  backend:
    driver: bridge
    internal: true          # 禁止外部访问

  existing-net:
    external: true          # 引用预存在的网络
    name: production_network

```

---

### volumes 配置

| 参数           | 类型       | 默认值     | 说明                     |
| ------------ | -------- | ------- | ---------------------- |
| driver       | string   | local   | 卷驱动，local 或第三方插件       |
| driver\_opts | map      | \-      | 驱动选项（local 驱动支持 NFS 等） |
| external     | bool     | false   | 引用预存在的卷，不由 Compose 管理  |
| name         | string   | 项目名\_卷名 | 自定义卷实际名称               |
| labels       | map/list | \-      | 卷元数据标签                 |

**local 驱动挂载 NFS：**

| driver\_opts 键 | 说明                      |
| -------------- | ----------------------- |
| type           | 文件系统类型，如 nfs、nfs4、tmpfs |
| o              | 挂载选项，逗号分隔               |
| device         | 设备路径或 NFS 服务器地址         |

```yaml
volumes:
  app-data:
    driver: local
    name: myapp_data    # 不带项目名前缀

  nfs-data:
    driver: local
    driver_opts:
      type: nfs
      o: "addr=192.168.1.100,rw,nfsvers=4"
      device: ":/exports/data"

  external-volume:
    external: true
    name: production_uploads

```

---

### secrets 配置

| 参数       | 类型     | 默认值   | 说明                    |
| -------- | ------ | ----- | --------------------- |
| file     | string | \-    | 从宿主机文件读取 secret 内容    |
| external | bool   | false | 引用 Swarm 中已存在的 secret |
| name     | string | \-    | 自定义 secret 实际名称       |

```yaml
secrets:
  db_password:
    file: ./secrets/db_password.txt

  api_key:
    external: true
    name: production_api_key

services:
  app:
    secrets:
      # 短语法
      - db_password
      # 长语法
      - source: api_key
        target: /run/secrets/api_key
        uid: "1000"
        gid: "1000"
        mode: 0400

```

Secret 默认挂载到容器的 `/run/secrets/<secret_name>`。

---

### configs 配置

`configs` 与 `secrets` 类似，但用于非敏感配置文件。挂载权限默认为 `0444`（可读）。

```yaml
configs:
  nginx_conf:
    file: ./nginx/nginx.conf

services:
  nginx:
    image: nginx:alpine
    configs:
      - source: nginx_conf
        target: /etc/nginx/nginx.conf
        mode: 0444

```

---

## 4\. 镜像构建最佳实践

### 选择合适的基础镜像

| 基础镜像类型                 | 典型大小    | 说明             | 适用场景              |
| ---------------------- | ------- | -------------- | ----------------- |
| ubuntu:22.04           | \~80 MB | 完整 Ubuntu，工具齐全 | 开发、调试             |
| debian:bookworm        | \~50 MB | 标准 Debian      | 通用                |
| debian:bookworm-slim   | \~30 MB | 精简 Debian      | 生产                |
| alpine:3.19            | \~7 MB  | musl libc，极小   | 对体积敏感，注意 musl 兼容性 |
| gcr.io/distroless/base | \~20 MB | 无 shell、无包管理器  | 安全优先的生产环境         |
| scratch                | 0 MB    | 空镜像            | 静态编译的二进制          |

选择建议：

- 优先选择官方镜像的 `-slim` 变体
- Alpine 适合简单应用，但 C 扩展（如某些 Python 包）需要编译，可能踩坑
- distroless 最安全，但调试困难（无 shell）

### 构建缓存优化

Docker 逐层检查，一旦某层缓存失效，之后所有层都重新构建。

**缓存失效规则：**

| 指令         | 缓存失效条件            |
| ---------- | ----------------- |
| FROM       | 基础镜像变化            |
| COPY / ADD | 源文件内容变化（checksum） |
| RUN        | 命令字符串变化，或前面的层失效   |
| ARG        | 传入的值变化            |
| ENV        | 值变化               |

**正确顺序（以 Node.js 为例）：**

```dockerfile
WORKDIR /app

# 先复制依赖声明文件（变化频率低）
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci

# 再复制源码（变化频率高）
COPY . .
RUN npm run build

```

若先 `COPY . .` 再 `npm ci`，每次代码变动都会重新安装依赖。

**使用 RUN --mount=type=cache 跨构建复用缓存：**

```dockerfile
# pip 缓存（跨构建持久化）
RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
    pip install -r requirements.txt

# Go 模块缓存
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o server ./cmd/server

```

### 使用 .dockerignore

`.dockerignore` 排除不需要进入构建上下文的文件，减少发送给 Docker daemon 的数据量，同时防止不必要的缓存失效：

```
# .dockerignore
.git
.gitignore
node_modules
dist
*.log
.env
.env.*
!.env.example
coverage/
.DS_Store
Thumbs.db

```

### 安全最佳实践

**不以 root 运行：**

```dockerfile
# 创建非特权用户
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 --ingroup appgroup --no-create-home appuser

USER appuser

```

**不在镜像中硬编码密钥：**

```dockerfile
# 错误做法
RUN pip install --extra-index-url https://user:TOKEN@private.pypi.io/simple/ mypackage

# 正确做法：使用 secret
RUN --mount=type=secret,id=pypi_token \
    PIP_EXTRA_INDEX_URL=https://$(cat /run/secrets/pypi_token)@private.pypi.io/simple/ \
    pip install mypackage

```

构建时传入：`docker build --secret id=pypi_token,src=./pypi_token.txt .`

**使用固定版本 tag：**

```dockerfile
# 不推荐
FROM node:latest
FROM python:3

# 推荐
FROM node:20.11.0-alpine3.19
FROM python:3.11.8-slim-bookworm

```

---

## 5\. 数据管理进阶

### Volume 详细操作

**创建 NFS 卷：**

```bash
docker volume create \
    --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.1.100,rw,nfsvers=4 \
    --opt device=:/exports/data \
    nfs-data

```

**inspect 查看挂载点：**

```bash
docker volume inspect myvolume
# 关注 Mountpoint 字段，通常为 /var/lib/docker/volumes/<name>/_data

```

**卷备份与恢复：**

```bash
# 备份：运行临时容器，将卷内容打包并输出到标准输出
docker run --rm \
    -v myvolume:/data:ro \
    -v $(pwd):/backup \
    alpine \
    tar czf /backup/myvolume-backup.tar.gz -C /data .

# 恢复：创建新卷并解压
docker volume create myvolume-restored
docker run --rm \
    -v myvolume-restored:/data \
    -v $(pwd):/backup:ro \
    alpine \
    sh -c "cd /data && tar xzf /backup/myvolume-backup.tar.gz"

```

**匿名卷的问题：**

```bash
# 每次 docker run 使用 -v /data 会创建新的匿名卷
docker run -v /data myimage   # 产生随机 ID 的卷

# 使用 docker volume ls 可以看到大量匿名卷
# 清理：docker volume prune（谨慎！）

```

应优先使用命名卷，便于管理和迁移。

### Bind Mount 注意事项

**SELinux/AppArmor 标签（仅 Linux）：**

| 后缀 | 含义                             |
| -- | ------------------------------ |
| :z | 重新标记内容为共享 SELinux 标签，多个容器可同时访问 |
| :Z | 重新标记为私有 SELinux 标签，仅当前容器可访问    |

```bash
docker run -v ./data:/app/data:z myimage

```

**权限对齐：**

若容器以 UID 1000 运行，宿主机目录需有对应权限：

```bash
# 宿主机
chown -R 1000:1000 ./app-data

# 或在 Dockerfile 中指定与宿主机一致的 UID
RUN useradd -u 1000 appuser

```

---

## 6\. 网络进阶

### 自定义 bridge 网络（推荐方式）

默认 `bridge` 网络与自定义 bridge 网络的区别：

| 特性          | 默认 bridge      | 自定义 bridge             |
| ----------- | -------------- | ---------------------- |
| DNS 解析（容器名） | 不支持（需 \--link） | 支持（容器名直接解析）            |
| 网络隔离        | 所有容器共享         | 不同网络相互隔离               |
| 动态连接        | 不支持            | docker network connect |
| 配置灵活性       | 低              | 高（子网、MTU 等）            |

```bash
# 创建自定义 bridge 网络
docker network create \
    --driver bridge \
    --subnet 172.20.0.0/16 \
    --gateway 172.20.0.1 \
    --opt com.docker.network.bridge.name=br-myapp \
    mynet

# 容器加入自定义网络后可直接用容器名通信
docker run --name db --network mynet postgres:16
docker run --name api --network mynet myapi
# api 容器内可直接 ping db、连接 db:5432

```

### 网络类型完整说明

| 类型      | 说明               | 适用场景             | 限制                         |
| ------- | ---------------- | ---------------- | -------------------------- |
| bridge  | 软件定义桥接，Docker 默认 | 单机多容器相互通信        | 仅单机                        |
| host    | 共享宿主机网络命名空间      | 高性能、端口直接监听宿主机    | 仅 Linux，无网络隔离              |
| overlay | 基于 VXLAN 的跨主机网络  | Docker Swarm 多节点 | 需要 Swarm 或 key-value store |
| macvlan | 容器拥有独立 MAC 和 IP  | 直连物理网络、遗留应用      | 需混杂模式，宿主机间通信受限             |
| ipvlan  | 共享 MAC，独立 IP     | 物理接口数量受限时        | 类似 macvlan，更少 MAC 占用       |
| none    | 仅 loopback，无外部连接 | 完全网络隔离的容器        | 无法与外界通信                    |

### 容器间通信

```bash
# 同网络：用容器名直接通信（推荐）
docker exec api curl http://db:5432

# 跨网络连接
docker network connect backend api-container

# --link（已废弃，不推荐使用）
docker run --link db:database myapi

```

---

## 7\. 日志管理

### 日志驱动对比

| 驱动        | 说明                            | 持久化   | 性能 |
| --------- | ----------------------------- | ----- | -- |
| json-file | 默认，JSON 格式本地文件，支持 docker logs | 是     | 中  |
| journald  | 写入 systemd journal            | 是     | 高  |
| syslog    | 通过 syslog 协议发送                | 取决于配置 | 高  |
| fluentd   | 发送到 Fluentd 聚合器               | 是（外部） | 中  |
| awslogs   | 发送到 AWS CloudWatch            | 是     | 中  |
| splunk    | 发送到 Splunk HTTP 事件收集器         | 是     | 中  |
| gelf      | Graylog 扩展日志格式                | 是（外部） | 高  |
| loki      | 发送到 Grafana Loki（插件）          | 是（外部） | 高  |
| none      | 不记录任何日志                       | 否     | 最高 |

**生产环境 json-file 推荐配置（防止磁盘写满）：**

```json
// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "compress": "true"
  }
}

```

或在 `docker run` 中单独指定：

```bash
docker run \
    --log-driver json-file \
    --log-opt max-size=10m \
    --log-opt max-file=3 \
    myimage

```

---

## 8\. 资源限制

### 内存限制

| 参数                    | 说明                           | 示例                         |
| --------------------- | ---------------------------- | -------------------------- |
| \--memory / \-m       | 硬限制，容器使用内存上限                 | \-m 512m                   |
| \--memory-reservation | 软限制，内存紧张时触发                  | \--memory-reservation 256m |
| \--memory-swap        | 内存 + swap 总上限；\-1 表示无限 swap  | \--memory-swap 1g          |
| \--memory-swappiness  | swap 使用倾向（0-100），0 尽量不用 swap | \--memory-swappiness 0     |
| \--oom-kill-disable   | 禁用 OOM Killer（危险，可能导致宿主机不稳定） | \--oom-kill-disable        |
| \--oom-score-adj      | 调整 OOM 优先级（-1000\~1000）      | \--oom-score-adj 500       |

**OOM Killer 说明：**

当容器内存超过 `--memory` 限制时，Linux OOM Killer 会终止容器内的进程。可通过 `docker inspect` 查看 `OOMKilled` 字段判断是否被 OOM 终止。

```bash
docker inspect --format='{{.State.OOMKilled}}' mycontainer

```

### CPU 限制

| 参数             | 说明                        | 示例                           |
| -------------- | ------------------------- | ---------------------------- |
| \--cpus        | CPU 核心数上限（浮点数）            | \--cpus 0.5（半核）              |
| \--cpu-shares  | 相对权重，默认 1024，仅在 CPU 竞争时生效 | \--cpu-shares 512            |
| \--cpuset-cpus | 绑定到指定 CPU 核心              | \--cpuset-cpus "0,2" 或 "0-3" |
| \--cpuset-mems | 绑定到指定 NUMA 内存节点           | \--cpuset-mems "0"           |
| \--cpu-period  | CFS 调度周期（微秒，默认 100000）    | \--cpu-period 100000         |
| \--cpu-quota   | 周期内可用 CPU 时间（微秒）          | \--cpu-quota 50000（= 0.5 核）  |

```bash
# 限制最多使用 1.5 个 CPU 核心，绑定到 0、1 号核
docker run --cpus 1.5 --cpuset-cpus "0,1" myimage

```

### Compose 中的资源限制示例

```yaml
services:
  api:
    image: myapi
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 1g
          pids: 100
        reservations:
          cpus: "0.5"
          memory: 256m
    # 或使用旧式写法（Compose v2 兼容）
    mem_limit: 1g
    cpus: 2.0

```

---

## 9\. 踩坑与注意事项

**depends\_on 只等启动，不等就绪**

`depends_on` 的默认 `condition: service_started` 仅表示容器进程已启动，数据库可能尚未接受连接。必须配合 `healthcheck` 和 `condition: service_healthy` 使用，或在应用层实现重试逻辑。

**Compose 网络名称带项目名前缀**

默认情况下，Compose 创建的网络名为 `<项目目录名>_default`。若多个 Compose 项目需要共享网络，或希望固定网络名，在 `networks` 配置中设置 `name` 字段：

```yaml
networks:
  default:
    name: myapp_network    # 固定名称，不受目录名影响

```

**卷名默认带项目名前缀**

与网络同理，卷实际名称为 `<项目名>_<卷名>`。引用另一 Compose 项目创建的卷时，使用 `external: true` 加 `name`：

```yaml
volumes:
  uploads:
    external: true
    name: production_myapp_uploads

```

**容器时区默认 UTC**

容器内时区默认为 UTC，若应用依赖本地时区：

```dockerfile
# 方法一：Dockerfile 中设置
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 方法二：挂载宿主机时区文件（Linux）
# volumes:
#   - /etc/localtime:/etc/localtime:ro
#   - /etc/timezone:/etc/timezone:ro

```

```yaml
# Compose 中
environment:
  TZ: Asia/Shanghai

```

**Linux bind mount 权限问题**

容器以 root 写入 bind mount 的文件后，宿主机上该文件属主为 root，普通用户无法删除：

```bash
# 在容器内以非 root 运行，或手动修复权限
sudo chown -R $(id -u):$(id -g) ./data

```

**CMD exec 形式无 shell 变量展开**

```dockerfile
# 错误：exec 形式不经过 shell，$PORT 不会被展开
CMD ["node", "server.js", "--port", "$PORT"]

# 正确：使用 shell 形式
CMD node server.js --port $PORT

# 或显式调用 sh -c
CMD ["sh", "-c", "node server.js --port $PORT"]

```

**COPY --chown 被 volume mount 覆盖**

`COPY --chown=appuser:appgroup` 在构建时设置文件属主，但若运行时以 bind mount 覆盖该路径，宿主机文件的属主生效，容器内权限设置失效。需确保宿主机目录有正确权限。

**ENTRYPOINT shell 形式导致信号无法传递**

```dockerfile
# 错误：shell 形式下 PID 1 是 /bin/sh，不是应用进程
# docker stop 发送 SIGTERM 给 sh，应用无法收到信号，导致强制 kill
ENTRYPOINT python app.py

# 正确：exec 形式，应用作为 PID 1 直接接收信号
ENTRYPOINT ["python", "app.py"]

```

**多行 ARG 作用域**

每个 `FROM` 之后，之前的 `ARG` 失效，需要重新声明（无需重复设置默认值）：

```dockerfile
ARG VERSION=1.0
FROM alpine:${VERSION}
# 此处 VERSION 失效
ARG VERSION    # 重新声明，继承之前的默认值
RUN echo "Version: ${VERSION}"

```

---

## 最佳实践

**多阶段构建分离 build 环境和 runtime 环境**：将编译所需的工具链（gcc、npm、Maven）留在 builder 阶段，最终镜像只包含运行时产物，体积可缩小 5-10 倍，攻击面也大幅减小。

**Docker Compose 中用具名 volume，不用匿名 volume**：具名 volume（`db_data:/var/lib/postgresql/data`）可以被 `docker volume ls` 管理，匿名 volume 删除容器时难以追踪，容易造成孤儿 volume 堆积。

**用 `healthcheck` 定义容器健康检查，让 Compose 感知服务真正就绪**：`depends_on` 只等容器启动，不等服务可用（如 PostgreSQL 初始化需要几秒）。配合 `condition: service_healthy` 可避免应用启动时数据库尚未就绪的竞态。

```yaml
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5
  app:
    depends_on:
      db:
        condition: service_healthy

```

**网络隔离：为每个项目用独立的自定义网络**：Compose 默认为项目创建独立网络，但多项目共用时容易混用。显式定义网络名称并用 `internal: true` 隔离不需要对外暴露的后端服务。

---

## 常见陷阱

### 陷阱：多阶段构建后镜像体积没有减小

**现象：** 使用了多阶段构建，但最终镜像大小和单阶段差不多。

**原因：** `COPY --from=builder` 复制了构建产物，但同时也把不必要的依赖或调试工具（如 `node_modules` 的 devDependencies）一并复制进来。

**解决：** 在 builder 阶段只安装生产依赖（`npm ci --omit=dev`），final 阶段只 COPY 最终产物目录，不 COPY `node_modules` 整体。

```dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev   # 只安装生产依赖
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules

```

### 陷阱：容器内时区与宿主机不一致

**现象：** 应用日志时间戳比实际早/晚 N 小时，cron 任务在错误时间触发。

**原因：** Docker 容器默认使用 UTC 时区，宿主机可能是 CST（UTC+8）或其他时区。

**解决：** 在 Dockerfile 中安装 `tzdata` 并设置环境变量，或在 compose.yaml 中挂载宿主机时区文件。

```dockerfile
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata

```

```yaml
# compose.yaml 方案
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /etc/timezone:/etc/timezone:ro

```

### 陷阱：构建缓存失效导致每次都重新安装依赖

**现象：** 修改任意源码文件后，`docker build` 重新执行 `npm install` 或 `pip install`，构建时间很长。

**原因：** `COPY . .` 放在 `RUN npm install` 之前，导致任何文件改动都让 `npm install` 层的缓存失效。

**解决：** 先单独 `COPY package*.json ./`，再 `RUN npm install`，最后才 `COPY . .`。这样只有 `package.json` 变动时才重新安装，源码变动不影响依赖层缓存。

---

## 参见

- [Docker初级指南](https://blog.vercanti.com/docker-chu-ji-zhi-nan/)
- [Docker高级指南](https://blog.vercanti.com/docker-gao-ji-zhi-nan/)
- [Docker Compose完全指南](https://blog.vercanti.com/docker-compose-wan-quan-zhi-nan/)
- [Kubernetes基础](https://blog.vercanti.com/kubernetes-ji-chu/)