Docker 深入学习指南

目录

  1. [Dockerfile 深入](#Dockerfile 深入)
  2. 镜像原理与分层结构
  3. 容器运行机制
  4. 网络深入
  5. 存储与数据管理
  6. [Docker Compose 进阶](#Docker Compose 进阶)
  7. 安全最佳实践
  8. [CI/CD 生产部署流程](#CI/CD 生产部署流程)

一、Dockerfile 深入

1.1 完整指令参考

dockerfile 复制代码
# ── 基础 ──────────────────────────────────────────────
FROM node:18-alpine          # 基础镜像,推荐用 alpine 变种(体积小)
ARG NODE_ENV=production      # 构建时参数,docker build --build-arg 传入
ENV APP_PORT=3000            # 运行时环境变量,容器内可读取

# ── 文件系统 ──────────────────────────────────────────
WORKDIR /app                 # 设置工作目录(不存在会自动创建)
COPY package*.json ./        # 复制文件(推荐 COPY,不要用 ADD)
ADD archive.tar.gz /data/    # ADD 额外支持自动解压 tar 和远程 URL
RUN npm ci --only=production # 执行命令(每条 RUN 生成一个新层)

# ── 元数据 ────────────────────────────────────────────
LABEL maintainer="you@example.com"
EXPOSE 3000                  # 声明端口(文档作用,不会真正开放)
VOLUME ["/app/data"]         # 声明挂载点

# ── 用户 ──────────────────────────────────────────────
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser                 # 切换到非 root 用户运行(安全实践)

# ── 启动 ──────────────────────────────────────────────
ENTRYPOINT ["node"]          # 固定入口,不能被 docker run 末尾参数覆盖
CMD ["server.js"]            # 默认参数,可被 docker run 末尾参数覆盖

ENTRYPOINT vs CMD 对比:

ENTRYPOINT CMD
作用 固定入口命令 默认参数
能否覆盖 需要 --entrypoint 才能覆盖 docker run 末尾参数直接覆盖
组合使用 ENTRYPOINT ["node"] + CMD ["app.js"] → 执行 node app.js ---
推荐场景 容器作为可执行程序时 提供灵活默认参数时

1.2 多阶段构建(Multi-stage Build)

架构说明:

多阶段构建将构建环境和运行环境完全分离。整个过程分两个阶段:

复制代码
阶段 1(Builder)                   阶段 2(Runner)
─────────────────                   ────────────────
FROM node:18(~1GB)                FROM node:18-alpine(~50MB)
  ↓                                   ↓
npm install                         COPY --from=builder /app/dist
  ↓                                   ↓
npm run build                       仅保留运行产物
  ↓                                 最终镜像 ~150MB(减少 85%)
生成 dist/ 产物
(编译器、源码、dev依赖留在这里,
 不会出现在最终镜像中)

不用多阶段构建(臃肿):

dockerfile 复制代码
FROM node:18                 # 完整镜像,~1GB
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["node", "dist/server.js"]
# 最终镜像包含所有构建工具,非常臃肿

使用多阶段构建(精简):

dockerfile 复制代码
# ── 阶段 1:构建 ──────────────────────────────────────
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ── 阶段 2:运行 ──────────────────────────────────────
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Go 语言极致优化(最终镜像几乎为零):

dockerfile 复制代码
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .

FROM scratch                 # 空镜像!
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
# 最终镜像只有几 MB

1.3 .dockerignore

.dockerignore 告诉 Docker 哪些文件不要打包进镜像,类似 .gitignore

复制代码
# .dockerignore
node_modules/        # 不复制本地 node_modules,镜像内会重新安装
.git/                # git 历史记录
*.log                # 日志文件
.env                 # 敏感配置,绝对不能打包进镜像!
dist/                # 多阶段构建时本地 dist 不需要
coverage/
.DS_Store

为什么重要:

  • 减少构建上下文大小,加快构建速度
  • 避免 .env 等敏感文件意外打包进镜像

1.4 构建缓存优化

Docker 按层缓存,层不变则复用,不重新执行。关键原则:把变化少的放前面,变化多的放后面。

dockerfile 复制代码
# ❌ 低效:代码改动 → npm install 重新执行
COPY . .
RUN npm install

# ✅ 高效:利用层缓存
COPY package*.json ./     # ① 先只复制依赖描述文件(改动少)
RUN npm ci                # ② 安装依赖(package.json 不变则命中缓存)
COPY . .                  # ③ 再复制源码(源码改动不影响上面两层)

缓存失效规则:

  • 某一层发生变化,其后所有层缓存全部失效
  • COPY / ADD 对比文件内容,文件变了才失效
  • RUN 只对比指令字符串,指令不变则命中缓存

二、镜像原理与分层结构

2.1 联合文件系统(UnionFS)------ 架构说明

分层结构示意:

复制代码
┌────────────────────────────────────────┐
│  可写层(Container Layer)              │  ← 每个容器独享,停止后丢失
├────────────────────────────────────────┤  ← 分界线(以下全部只读)
│  Layer 4: COPY . .(应用代码)          │
├────────────────────────────────────────┤
│  Layer 3: RUN npm install(依赖)       │
├────────────────────────────────────────┤
│  Layer 2: WORKDIR /app                 │
├────────────────────────────────────────┤
│  Layer 1: FROM node:18-alpine(基础层) │  ← ~50MB,可被多个镜像共享
└────────────────────────────────────────┘
         叠加方向 ↓

工作原理:

  1. 镜像由多个只读层叠加,每层记录相对上一层的变更(增删改)
  2. 容器启动时,Docker 在镜像层顶部加一个可写层
  3. 读文件:从上往下找,找到为止(上层覆盖下层)
  4. 写文件:写到可写层,不修改下面的只读层(Copy-on-Write)
  5. 删文件:在可写层写入"删除标记",原文件仍在下层,只是被遮蔽

层共享的价值:

  • 两个镜像都基于 node:18-alpine,这层在磁盘只存一份
  • docker pull 时已有的层直接跳过,只下载新增的层

2.2 镜像体积优化技巧

dockerfile 复制代码
# 技巧 1:选用 alpine / slim 基础镜像
FROM node:18-alpine     # ~50MB,而非 node:18 的 ~1GB

# 技巧 2:合并 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 \
    && rm -rf /var/lib/apt/lists/*

# 技巧 3:在同一层内完成安装和清理
# ❌ 错误:Layer N 删除的文件在 Layer N-1 仍然存在,镜像体积不变
RUN apt-get install -y build-essential   # Layer 2
RUN rm -rf /usr/share/doc               # Layer 3,无效

# ✅ 正确:同一层内完成
RUN apt-get install -y build-essential \
    && rm -rf /usr/share/doc /var/lib/apt/lists/*

# 技巧 4:只安装生产依赖
RUN npm ci --only=production

# 技巧 5:使用多阶段构建(见 1.2 节)

2.3 常用镜像管理命令

bash 复制代码
# 查看镜像层历史(每层大小、命令)
docker history myapp:v1

# 查看镜像详细元数据
docker inspect myapp:v1

# 打新标签
docker tag myapp:v1 myuser/myapp:v1
docker tag myapp:v1 myapp:latest

# 离线传输:导出 / 导入
docker save myapp:v1 | gzip > myapp.tar.gz
docker load < myapp.tar.gz

# 清理
docker image prune        # 清理悬空镜像(无标签的旧层)
docker image prune -a     # 清理所有未被容器使用的镜像

三、容器运行机制

3.1 容器生命周期------ 状态机说明

完整状态转换:

复制代码
                  ┌─────────────┐
                  │   Created   │  docker create / docker run
                  └──────┬──────┘
                         │ docker start / docker run
                         ↓
              ┌──── Running ────┐
docker pause  │                 │  docker stop(SIGTERM → SIGKILL)
              ↓                 │  docker kill(直接 SIGKILL)
           Paused               ↓
              │              Stopped ──→ Removed(docker rm)
docker unpause│                 │
              └──→ Running ←────┘  docker start / docker restart
                  (继续)

关键区别:

  • docker stop:发送 SIGTERM,等待应用优雅退出(默认 10 秒),超时再 SIGKILL
  • docker kill:直接发送 SIGKILL,立即强制终止,可能丢失数据
  • docker pause:冻结容器进程(SIGSTOP),不释放资源,恢复后继续执行

3.2 容器资源限制

不限制资源的容器可能耗尽宿主机所有 CPU/内存,生产环境必须设置:

bash 复制代码
# 内存限制(超出会被 OOM Kill)
docker run -m 512m myapp

# CPU 限制(最多使用 1 个核心)
docker run --cpus="1.0" myapp

# 综合生产配置
docker run -d \
  --name web \
  -m 512m \
  --memory-swap 512m \          # 与内存相同 = 禁用 swap
  --cpus="0.5" \
  --restart unless-stopped \
  -p 8080:3000 \
  myapp:v1

3.3 容器日志管理

bash 复制代码
docker logs web                 # 全部日志
docker logs -f web              # 实时跟踪(tail -f)
docker logs --tail 100 web      # 最后 100 行
docker logs -t web              # 带时间戳
docker logs --since 2h web      # 最近 2 小时
docker logs --since "2026-03-01" web

生产环境限制日志大小(防止撑满磁盘):

json 复制代码
// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

3.4 进入容器调试

bash 复制代码
# 进入容器 shell(最常用)
docker exec -it web bash          # bash(ubuntu/debian)
docker exec -it web sh            # sh(alpine)

# 以 root 身份进入(容器内用 root 排查问题)
docker exec -it -u root web bash

# 执行单条命令不进入
docker exec web cat /app/config.json
docker exec web ps aux

# 用 netshoot 排查网络问题(共享目标容器网络命名空间)
docker run -it --network container:web nicolaka/netshoot

四、网络深入

4.1 网络驱动类型

驱动 说明 适用场景
bridge(默认) 虚拟网桥,容器通过 NAT 访问外网 单机多容器
host 共享宿主机网络,无隔离 高性能场景(慎用)
overlay 跨主机容器通信 Docker Swarm / 多机集群
macvlan 容器直接获得 MAC 地址 需要接入物理网络时
none 无网络,完全隔离 离线计算任务

4.2 自定义 Bridge 网络------ 架构说明

默认 bridge vs 自定义 bridge 的核心差别:

复制代码
默认 bridge 网络:
  容器 A(172.17.0.2)──→ 只能用 IP 访问容器 B(172.17.0.3)
  IP 会变,不可靠

自定义 bridge 网络(推荐):
  ┌─── app-net(内置 DNS 服务器)───────────────┐
  │                                              │
  │  web ──→ db:3306    (用容器名,稳定可靠)   │
  │  web ──→ cache:6379                          │
  │                                              │
  └──────────────────────────────────────────────┘
  宿主机端口映射:-p 8080:3000(仅 web 对外暴露)
  db / cache 不暴露端口,外部无法直接访问(安全)
bash 复制代码
# 创建自定义网络
docker network create --driver bridge app-net

# 启动容器加入网络
docker run -d --name db    --network app-net mysql:8
docker run -d --name cache --network app-net redis:7-alpine
docker run -d --name web   --network app-net \
  -e DB_HOST=db -e REDIS_HOST=cache \
  -p 8080:3000 myapp:v1

# 同一网络内可用容器名直接访问,无需 IP

# 查看网络信息
docker network ls
docker network inspect app-net

# 将已运行容器加入/离开网络
docker network connect    app-net existing-container
docker network disconnect app-net existing-container

4.3 端口映射详解

bash 复制代码
docker run -p 8080:80 nginx         # 宿主机 8080 → 容器 80
docker run -p 127.0.0.1:8080:80 nginx  # 只绑本地,不对外暴露
docker run -p 80:80 -p 443:443 nginx   # 映射多个端口

docker port web                     # 查看端口映射

生产最佳实践: Web 服务绑定 127.0.0.1,由 nginx 做反向代理对外暴露,数据库等服务完全不映射端口。


五、存储与数据管理

5.1 三种挂载方式------ 架构说明

复制代码
         Container(容器)
              │
     ┌────────┼────────┐
     ↓        ↓        ↓
  Named     Bind      tmpfs
  Volume    Mount     Mount
     │        │        │
  Docker    宿主机    宿主机
  管理目录  任意目录   内存
     │        │        │
  持久化    开发同步   临时/敏感
  数据库    源码热更新  不写磁盘
类型 存储位置 容器删除后数据 推荐用途
Named Volume /var/lib/docker/volumes/ 保留 数据库、持久化应用数据
Bind Mount 宿主机任意目录 保留(宿主机上) 开发时挂载源码
tmpfs 宿主机内存 消失 敏感临时数据(不落盘)

5.2 Named Volume(命名卷)

bash 复制代码
docker volume create mysql-data
docker volume ls
docker volume inspect mysql-data    # 查看实际存储路径

docker run -d \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8

docker volume rm mysql-data         # 删除(数据丢失!)
docker volume prune                 # 清理未使用的 volume

5.3 Bind Mount

bash 复制代码
# 开发时挂载本地源码(容器内改动实时同步)
docker run -v $(pwd):/app myapp

# 只读挂载(容器不能修改配置文件)
docker run -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro nginx

5.4 数据备份与迁移

bash 复制代码
# 备份 volume 数据
docker run --rm \
  -v mysql-data:/data \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/mysql-backup.tar.gz -C /data .

# 恢复备份
docker run --rm \
  -v mysql-data:/data \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/mysql-backup.tar.gz -C /data

六、Docker Compose 进阶

6.1 服务依赖与拓扑------ 架构说明

典型多服务依赖关系:

复制代码
外部流量
    │ :80/:443
    ↓
  nginx(反向代理 / SSL 终止)
    │ :3000
    ↓
  web(node.js 应用)
    │ DB_HOST=db         │ REDIS_HOST=cache
    ↓                    ↓
  db(mysql)         cache(redis)
    │
  Volume: db-data(持久化)

depends_on 顺序:db 和 cache 先启动,web 再启动,nginx 最后
healthcheck:等待 db 真正就绪(能响应连接),而非仅仅进程存在

6.2 完整 compose 文件结构

yaml 复制代码
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile.prod
      args:
        NODE_ENV: production
    ports:
      - "8080:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
    env_file:
      - .env                     # 从文件加载环境变量(不提交到 git)
    depends_on:
      db:
        condition: service_healthy   # 等待 db 健康检查通过再启动
    volumes:
      - ./logs:/app/logs
    networks:
      - app-net
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M

  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}  # 从 .env 读取
      MYSQL_DATABASE: myapp
    volumes:
      - db-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-net
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

volumes:
  db-data:
    driver: local

6.3 多环境配置

bash 复制代码
# 文件结构
docker-compose.yml          # 基础配置(所有环境共用)
docker-compose.dev.yml      # 开发环境覆盖
docker-compose.prod.yml     # 生产环境覆盖
yaml 复制代码
# docker-compose.dev.yml
services:
  web:
    build:
      target: builder        # 使用含调试工具的构建阶段
    volumes:
      - .:/app               # 挂载源码,热更新
    environment:
      - NODE_ENV=development
    command: npm run dev
  db:
    ports:
      - "3306:3306"          # 开发时暴露数据库端口,方便本地连接
bash 复制代码
# 开发
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# 生产
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

6.4 常用 Compose 命令

bash 复制代码
docker compose up -d --build          # 后台启动,强制重新构建
docker compose up -d --scale web=3    # 扩容到 3 个 web 实例
docker compose up -d --no-deps --build web  # 只重建并更新 web 服务
docker compose restart web            # 重启某服务
docker compose logs -f web            # 实时日志
docker compose exec web bash          # 进入容器
docker compose exec db mysql -u root -p
docker compose ps                     # 查看状态
docker compose down                   # 停止并删除容器和网络(保留 volume)
docker compose down -v                # 同上,同时删除 volume(数据丢失!)

七、安全最佳实践

7.1 不以 root 运行容器

dockerfile 复制代码
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --chown=appuser:appgroup . .
USER appuser

7.2 只读文件系统 + 最小权限

bash 复制代码
docker run \
  --read-only \                  # 文件系统只读
  --tmpfs /tmp \                 # /tmp 允许写(内存)
  -v app-data:/app/data \        # 数据目录允许写
  --cap-drop ALL \               # 删除所有 Linux capabilities
  --cap-add NET_BIND_SERVICE \   # 只允许绑定低位端口
  --security-opt no-new-privileges \  # 禁止容器内提权
  myapp

7.3 镜像安全扫描

bash 复制代码
# Trivy(开源,推荐)
trivy image myapp:v1
trivy image --severity HIGH,CRITICAL myapp:v1

# Docker 内置
docker scout cves myapp:v1

7.4 敏感信息管理

绝对不能做:

dockerfile 复制代码
# ❌ 密钥写进 Dockerfile
ENV DB_PASSWORD=mysecret

# ❌ .env 文件打包进镜像
COPY .env .

# ❌ 私钥放进镜像
COPY private.key /app/certs/

推荐做法:

bash 复制代码
# 运行时注入环境变量(比构建时写入安全)
docker run -e DB_PASSWORD="$SECRET" myapp

# 生产推荐:使用外部密钥管理系统
# AWS Secrets Manager / HashiCorp Vault / Kubernetes Secrets
# 容器启动时从外部拉取,不落进镜像

八、CI/CD 生产部署流程

8.1 完整流程说明

CI 阶段(代码提交自动触发):

复制代码
git push
   ↓
CI 系统触发(GitHub Actions / GitLab CI)
   ↓
运行测试 + lint 检查
   ↓
docker build(利用层缓存加速)
   ↓
镜像安全扫描(Trivy)
   ↓
docker push → Registry(tag 用 git commit hash)

CD 阶段(手动或自动触发):

复制代码
从 Registry 拉取新镜像
   ↓
停止旧容器(docker stop,给 30s 优雅退出)
   ↓
启动新容器(带资源限制 + 健康检查)
   ↓
健康检查通过 → 流量切入
   ↓
(失败 → 自动回滚到上一个 tag)

8.2 GitHub Actions 完整示例

yaml 复制代码
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            myuser/myapp:latest
            myuser/myapp:${{ github.sha }}
          cache-from: type=gha       # 利用 GitHub Actions 缓存加速
          cache-to: type=gha,mode=max

8.3 生产容器启动模板

bash 复制代码
docker run -d \
  --name web \
  --restart unless-stopped \        # 自动重启
  -m 512m \                         # 内存上限
  --memory-swap 512m \              # 禁用 swap
  --cpus="1.0" \                    # CPU 上限
  --cap-drop ALL \                  # 最小权限
  --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --read-only \                     # 只读文件系统
  --tmpfs /tmp:rw,size=64m \
  --log-opt max-size=10m \          # 日志大小限制
  --log-opt max-file=3 \
  -p 127.0.0.1:8080:3000 \         # 只绑本地,由 nginx 反代
  -v app-data:/app/data \
  --health-cmd "curl -f http://localhost:3000/health || exit 1" \
  --health-interval 30s \
  --health-retries 3 \
  myapp:abc1234                     # 使用 commit hash,不用 latest

8.4 镜像版本管理策略

bash 复制代码
# ❌ 生产不用 latest(无法追溯)
docker pull myapp:latest

# ✅ 语义化版本
docker tag myapp:1.2.3 myapp:1.2
docker tag myapp:1.2.3 myapp:1
docker tag myapp:1.2.3 myapp:latest

# ✅ 用 git commit hash(可精确回滚)
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t myapp:${GIT_SHA} .

附录:常见问题排查

bash 复制代码
# 容器启动失败
docker logs web

# 容器反复重启,查看退出码
docker inspect web --format='{{.State.ExitCode}} {{.State.Error}}'

# 查看磁盘占用
docker system df

# 清理(保留运行中容器和有名字的 volume)
docker system prune

# 网络排查
docker network inspect app-net
docker exec web ping db
docker exec web nslookup db

# 实时监控事件流
docker events

# 查看容器资源占用
docker stats

相关推荐
馨谙4 小时前
Kubernetes 核心技术之 Namespace:资源隔离与环境管理全解析
容器·kubernetes
道清茗4 小时前
【Kubernetes知识点问答题】Pod
云原生·容器·kubernetes
专家大圣4 小时前
告别智能家居品牌壁垒✨ Home Assistant+cpolar 让远程控家更省心
网络·docker·智能家居·内网穿透·cpolar
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ5 小时前
Docker的介绍及使用
docker
ai产品老杨5 小时前
终结协议孤岛:基于GB28181/RTSP融合网关的多品牌设备统一接入与边缘推流方案
人工智能·docker·架构·kubernetes·音视频
浊酒入清梦6 小时前
Gradle多模块项目构建docker镜像脚本
运维·docker·容器
西柚小萌新7 小时前
【docker】--4.Docker Compose
docker·容器·eureka
Scabbards_7 小时前
基于docker的LLM服务部署
运维·docker·容器
于眠牧北7 小时前
ubuntu22.04在docker中安装redis6.2.x并配置远程连接
运维·redis·docker·容器