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

相关推荐
功德+n5 小时前
Linux下安装与配置Docker完整详细步骤
linux·运维·服务器·开发语言·docker·centos
小敬爱吃饭6 小时前
Ragflow Docker部署及问题解决方案(界面为Welcome to nginx,ragflow上传文件失败,Docker中的ragflow-cpu-1一直重启)
人工智能·python·nginx·docker·语言模型·容器·数据挖掘
木子欢儿6 小时前
Docker Hub 镜像发布指南
java·spring cloud·docker·容器·eureka
coppher7 小时前
Ubuntu 22.04 amd64 离线安装 Docker 完整教程
linux·docker
虚伪的空想家9 小时前
k8s集群configmap和secrets备份脚本
linux·容器·kubernetes
SXJR9 小时前
k8s中的Pod
云原生·容器·kubernetes
文静小土豆9 小时前
K8s 滚动更新在 Java 应用中的实践与优化
java·容器·kubernetes
w6100104669 小时前
CKA-2026-Ingress
云原生·容器·kubernetes·cka
bloglin9999910 小时前
docker logs 如何一直监听日志输出
运维·docker·容器
说实话起个名字真难啊11 小时前
Docker 入门之网络基础
网络·docker·php