跳转到内容

Docker 容器化全面学习手册

Docker 容器化全面学习手册

Docker 是云计算时代必备技能。 解决"我电脑上能跑,服务器上不行"的终极方案。本文从底层原理→安装→命令→实战部署(MySQL/Redis/Nginx)→Dockerfile→Docker Compose,从 0 到 1 掌握容器化。


一、Docker 解决了什么问题

没有 Docker 时: 开发说"我电脑上能跑啊"→运维部署发现环境不一致(JDK 版本/系统库/配置文件)→排查半天→"你这环境有问题"。 有了 Docker: 把代码+依赖+环境配置全部打包成镜像,一次构建,到处运行。

1.1 Docker vs 虚拟机

对比维度

虚拟机 (VM)

Docker 容器

启动速度

分钟级

秒级(甚至毫秒)

资源占用

每个 VM 有独立 OS 内核,GB 级内存

共享宿主机内核,MB 级内存

镜像大小

几 GB

几十 MB

隔离性

完全隔离(Hypervisor 层)

进程级隔离(Namespace + Cgroups)

密度

一台物理机跑几个 VM

一台物理机跑几十上百容器

迁移

需导出完整 VM 文件

镜像 push/pull 即可

性能损耗

较大(虚拟化开销)

几乎无(直接使用宿主机内核)

为什么 Docker 更轻量?

传统虚拟机架构:                    Docker 架构:
┌──────────────────┐               ┌──────────────────┐
│   App A  │ App B │               │   App A  │ App B │
├──────────┴───────┤               ├──────────┴───────┤
│  Guest OS  Guest OS│              │  Docker Engine    │
├──────────────────┤               ├───────────────────┤
│   Hypervisor      │               │   Host OS (Linux)  │
├──────────────────┤               ├───────────────────┤
│   Host OS         │               │   Infrastructure   │
├──────────────────┤               └───────────────────┘
│   Infrastructure  │
└──────────────────┘

核心差异:虚拟机虚拟硬件和 OS 内核,Docker 只虚拟应用层和依赖。Docker 容器直接使用宿主机内核,没有 Guest OS 这一层。


1.2 核心概念

三大核心对象

概念

类比

说明

镜像(Image)

类(Class)

容器的模板,包含完整的运行环境(OS + 依赖 + 代码)。镜像是只读的,一层一层叠加(Union FS)。

容器(Container)

实例(Instance)

镜像的运行实例。在镜像之上添加一个可写层,容器停止/删除后该层丢失。

仓库(Registry)

代码仓库(GitHub)

存放镜像的地方。Docker Hub 是官方公共仓库,也可以搭建私有仓库(Harbor)。

Docker 架构图

┌────────────────────────────────────────────┐
│                 Docker Client               │
│              (docker build/run/pull)         │
└─────────────────┬──────────────────────────┘
                  │ REST API (Unix Socket / TCP)
┌─────────────────▼──────────────────────────┐
│               Docker Daemon (dockerd)        │
│  ┌──────────────────────────────────────┐   │
│  │         Container Runtime (containerd)│   │
│  │  ┌────────┐ ┌────────┐ ┌────────┐   │   │
│  │  │  容器A  │ │  容器B  │ │  容器C  │   │   │
│  │  └────────┘ └────────┘ └────────┘   │   │
│  └──────────────────────────────────────┘   │
│  ┌──────────┐ ┌──────────────┐              │
│  │  镜像管理 │ │  网络/存储管理 │              │
│  └──────────┘ └──────────────┘              │
└────────────────────────────────────────────┘

底层技术支撑

技术

作用

Namespace(命名空间)

实现隔离。每种 Namespace 隔离一种资源(PID/Network/Mount/User/UTS/IPC)

Cgroups(控制组)

实现资源限制。限制 CPU、内存、磁盘 IO、网络带宽

UnionFS(联合文件系统)

实现镜像分层。overlay2 是目前推荐使用的存储驱动

Docker 分层镜像原理

┌──────────────────┐
│   可写容器层      │ ← 容器运行时的修改
├──────────────────┤
│   第3层: app.jar │ ← COPY (你的应用)
├──────────────────┤
│   第2层: JDK 17  │ ← RUN apt install openjdk-17
├──────────────────┤
│   第1层: alpine  │ ← FROM alpine:3.19
└──────────────────┘

每层只读、共享复用——100 个 Java 容器共享同一个 JDK 层,磁盘只占一份!


二、Docker 安装与基础命令

2.1 安装 Docker

Linux(推荐,生产环境用)

# Ubuntu/Debian
curl -fsSL https://get.docker.com | bash
# 或手动安装
sudo apt update
sudo apt install docker.io -y
sudo systemctl enable docker --now

# 将当前用户加入 docker 组(免 sudo)
sudo usermod -aG docker $USER
# 重新登录后生效

# CentOS/RHEL
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce docker-ce-cli containerd.io -y
sudo systemctl enable docker --now

macOS

# 推荐用 OrbStack(轻量、快)或 Docker Desktop
brew install orbstack   # 或: brew install --cask docker

Windows

# 推荐 Docker Desktop,或 WSL2 中安装 Linux 版 Docker
winget install Docker.DockerDesktop

验证安装

docker version           # 查看客户端和服务端版本
docker run hello-world   # 跑一个测试容器
docker info              # 查看 Docker 系统信息(存储驱动、Cgroup 版本等)

镜像加速(国内)

# 编辑 /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://docker.m.daocloud.io"
  ]
}
sudo systemctl restart docker

2.2 镜像管理

核心命令

# 搜索镜像
docker search nginx                   # Docker Hub 搜索
docker search nginx --limit 10        # 限制返回数量

# 拉取镜像
docker pull nginx                     # 拉取最新版(latest)
docker pull nginx:1.25                # 拉取指定版本
docker pull nginx:1.25-alpine         # 拉取 alpine 精简版(体积小)

# 查看本地镜像
docker images                         # 列出所有镜像
docker images -a                      # 包含中间层
docker images nginx                   # 筛选指定镜像
docker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# 查看镜像详情
docker inspect nginx:latest           # 镜像完整 JSON 信息
docker history nginx:latest           # 镜像构建历史(每层大小)

# 删除镜像
docker rmi <镜像ID>                   # 删除镜像
docker rmi nginx:latest               # 删除指定标签
docker rmi $(docker images -q)        # 删除所有镜像(危险!)
docker image prune -a                 # 删除所有未被使用的镜像

# 导出/导入镜像
docker save -o nginx.tar nginx:latest           # 导出为 tar 包
docker save nginx:latest | gzip > nginx.tar.gz  # 压缩导出
docker load -i nginx.tar                        # 从 tar 载入
docker load < nginx.tar.gz                      # 从压缩包载入

# 镜像标签
docker tag nginx:latest myregistry.com/nginx:v1  # 打标签(用于推送到私有仓库)

# 推送镜像
docker push myregistry.com/nginx:v1              # 推送到仓库
docker login myregistry.com                      # 先登录

2.3 容器管理

生命周期命令

# === 创建与启动 ===

# docker run = 创建 + 启动(最常用)
docker run -d --name my-nginx -p 8080:80 nginx:latest

# 常用参数详解:
docker run \
  -d \                    # 后台运行(detached mode)
  --name my-nginx \       # 容器命名(不指定则随机生成)
  -p 8080:80 \            # 端口映射 宿主机:容器
  -p 8080:80/udp \        # 指定 UDP 端口
  -v /data:/app/data \    # 挂载数据卷 宿主机目录:容器目录
  -v myvolume:/data \     # 挂载命名卷
  -e MYSQL_ROOT_PASSWORD=123456 \  # 设置环境变量
  --env-file .env \       # 从文件加载环境变量
  --restart always \      # 重启策略(no/always/on-failure/unless-stopped)
  -m 512m \               # 内存限制
  --cpus 2 \              # CPU 核数限制
  --network mynet \       # 指定网络
  --rm \                  # 容器停止后自动删除(调试用)
  nginx:latest

# 交互式运行(调试/开发)
docker run -it ubuntu:22.04 /bin/bash   # -i 交互模式, -t 分配伪终端

# === 查看容器 ===

docker ps                        # 查看运行中的容器
docker ps -a                     # 查看所有容器(含已停止)
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
docker ps -q                     # 只输出容器 ID
docker ps --filter "status=exited"  # 过滤已退出的容器

# === 容器操作 ===

docker start my-nginx            # 启动已停止的容器
docker stop my-nginx             # 停止容器(发送 SIGTERM,10s 后 SIGKILL)
docker stop -t 30 my-nginx       # 等待 30 秒后再强制停止
docker restart my-nginx          # 重启容器
docker pause my-nginx            # 暂停容器进程
docker unpause my-nginx          # 恢复容器进程
docker kill my-nginx             # 强制停止(直接 SIGKILL)

# === 进入容器 ===

docker exec -it my-nginx /bin/bash        # 进入容器开新 shell(推荐)
docker exec my-nginx cat /etc/nginx/nginx.conf  # 在容器中执行命令
docker exec -it my-nginx mysql -uroot -p  # 进入 MySQL 容器

docker attach my-nginx           # 连接到容器的主进程(不推荐,exit 会停止容器)

# === 日志与信息 ===

docker logs my-nginx             # 查看日志
docker logs -f my-nginx          # 实时跟踪日志(tail -f)
docker logs --tail 50 my-nginx   # 查看最后 50 行
docker logs --since 30m my-nginx # 最近 30 分钟的日志
docker logs -f --tail 100 my-nginx | grep ERROR

docker top my-nginx              # 查看容器内进程
docker stats                     # 实时查看所有容器资源使用(CPU/内存/网络/磁盘)
docker stats my-nginx            # 查看指定容器
docker port my-nginx             # 查看容器端口映射
docker inspect my-nginx          # 容器完整 JSON 信息
docker inspect -f '{{.NetworkSettings.IPAddress}}' my-nginx  # 提取特定字段

# === 文件操作 ===

docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf  # 容器 → 宿主机
docker cp ./index.html my-nginx:/usr/share/nginx/html/  # 宿主机 → 容器

# === 删除容器 ===

docker rm my-nginx               # 删除已停止的容器
docker rm -f my-nginx            # 强制删除运行中的容器
docker rm $(docker ps -aq)       # 删除所有容器(危险!)
docker container prune           # 删除所有已停止的容器

# === 提交容器为镜像 ===

docker commit my-nginx my-nginx-custom:v1  # 将容器当前状态保存为镜像
docker commit -m "安装了自定义模块" my-nginx my-nginx:v2
# 注意:commit 不利于复现,生产环境请用 Dockerfile

容器状态流转

                    docker create
                         │
                         ▼
  docker pull ──→  Created ──→  Running ──→  Paused
                         │          │             │
                         │    docker stop     docker unpause
                         │    docker kill          │
                         ▼          ▼             │
                       Exited ←──────              │
                         │                    Paused
                         │ docker start
                         ▼
                       Running
                         │
                         │ docker rm
                         ▼
                      Deleted

三、实战部署常用软件

3.1 部署 MySQL

# 基础部署
docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql_data:/var/lib/mysql \
  -v ./mysql/conf:/etc/mysql/conf.d \
  --restart always \
  mysql:8.0

# 完整参数版(推荐)
docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=MyStr0ngP@ss \
  -e MYSQL_DATABASE=myapp \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -e TZ=Asia/Shanghai \
  -v mysql_data:/var/lib/mysql \
  -v ./mysql/conf:/etc/mysql/conf.d \
  -v ./mysql/init:/docker-entrypoint-initdb.d \  # 初始化 SQL 脚本
  --restart always \
  mysql:8.0 \
  --character-set-server=utf8mb4 \
  --collation-server=utf8mb4_unicode_ci \
  --default-authentication-plugin=mysql_native_password

# 连接测试
docker exec -it mysql mysql -uroot -p123456 -e "SHOW DATABASES;"

3.2 部署 Redis

# 基础部署
docker run -d \
  --name redis \
  -p 6379:6379 \
  -v redis_data:/data \
  --restart always \
  redis:7-alpine

# 带密码 + 持久化 + 配置文件
docker run -d \
  --name redis \
  -p 6379:6379 \
  -v redis_data:/data \
  -v ./redis/redis.conf:/usr/local/etc/redis/redis.conf \
  --restart always \
  redis:7-alpine \
  redis-server /usr/local/etc/redis/redis.conf \
  --requirepass "MyStr0ngP@ss" \
  --appendonly yes

# 连接测试
docker exec -it redis redis-cli
> AUTH MyStr0ngP@ss
> PING
> SET test hello
> GET test
# 或一条命令
docker exec -it redis redis-cli -a MyStr0ngP@ss PING

3.3 部署 Nginx

# 基础部署
docker run -d \
  --name nginx \
  -p 80:80 \
  -p 443:443 \
  -v ./nginx/html:/usr/share/nginx/html \
  -v ./nginx/nginx.conf:/etc/nginx/nginx.conf \
  -v ./nginx/conf.d:/etc/nginx/conf.d \
  -v ./nginx/logs:/var/log/nginx \
  --restart always \
  nginx:alpine

# 典型 nginx.conf 反向代理配置
# ./nginx/conf.d/default.conf:
server {
    listen 80;
    server_name api.example.com;
    client_max_body_size 100M;

    location /api/ {
        proxy_pass http://host.docker.internal:8080/;  # 访问宿主机的 Spring Boot
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;   # SPA 前端路由
    }
}

# 测试配置
docker exec nginx nginx -t
# 重载配置(不停机)
docker exec nginx nginx -s reload

其他常用中间件快速部署

# PostgreSQL
docker run -d --name postgres -p 5432:5432 \
  -e POSTGRES_PASSWORD=123456 \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16-alpine

# MongoDB
docker run -d --name mongo -p 27017:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD=123456 \
  -v mongodata:/data/db \
  mongo:7

# RabbitMQ(带管理界面)
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 \
  -e RABBITMQ_DEFAULT_USER=admin \
  -e RABBITMQ_DEFAULT_PASS=123456 \
  rabbitmq:3-management-alpine

# ElasticSearch(单节点)
docker run -d --name es -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  -v esdata:/usr/share/elasticsearch/data \
  elasticsearch:8.12.0

# MinIO(对象存储,S3 兼容)
docker run -d --name minio -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=admin \
  -e MINIO_ROOT_PASSWORD=12345678 \
  -v miniodata:/data \
  minio/minio server /data --console-address ":9001"

# Portainer(Docker 可视化管理)
docker run -d --name portainer -p 9000:9000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  --restart always \
  portainer/portainer-ce

四、Dockerfile:构建自己的镜像

4.1 Dockerfile 核心指令

指令

作用

示例

FROM

指定基础镜像

FROM openjdk:17-jdk-alpine

WORKDIR

设置工作目录(后续命令在此目录执行)

WORKDIR /app

COPY

复制本地文件到镜像

COPY target/*.jar app.jar

ADD

复制 + 支持 URL 下载、自动解压 tar

ADD https://xxx/file.tar.gz /app/

RUN

构建时执行命令

RUN apt-get update && apt-get install -y curl

EXPOSE

声明容器监听端口(文档作用)

EXPOSE 8080

CMD

容器启动时默认命令

CMD ["java", "-jar", "app.jar"]

ENTRYPOINT

容器入口点(比 CMD 更难被覆盖)

ENTRYPOINT ["java"]

ENV

设置环境变量

ENV JAVA_OPTS="-Xmx512m"

ARG

构建参数(--build-arg 传入)

ARG VERSION=1.0

USER

切换运行用户(安全:不推荐 root)

USER appuser

VOLUME

声明匿名卷(数据持久化提示)

VOLUME ["/data"]

HEALTHCHECK

健康检查(判断容器是否正常)

HEALTHCHECK CMD curl -f http://localhost:8080/actuator/health

LABEL

添加元数据

LABEL version="1.0" maintainer="dev@example.com"

CMD vs ENTRYPOINT 区别

# CMD:可被 docker run 后面的命令覆盖
CMD ["java", "-jar", "app.jar"]
# docker run my-image java -jar other.jar  ← 可以覆盖

# ENTRYPOINT:不可被覆盖,docker run 后面的参数作为追加参数
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar"]
# docker run my-image -jar other.jar  ← 追加到 ENTRYPOINT 后面

# 联合使用(推荐模式):
ENTRYPOINT ["java", "-jar", "app.jar"]   # 固定入口
CMD ["--spring.profiles.active=dev"]     # 可被覆盖的默认参数
# docker run my-image --spring.profiles.active=prod   ← 覆盖 CMD

最佳实践

# 1. 使用 .dockerignore 排除无需的文件
# .dockerignore
node_modules/
.git/
*.md
target/  (Maven 构建产物不用 COPY,在容器内构建)

# 2. 减少层数 —— 合并 RUN 命令
# ❌ 坏写法,每行一个层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# ✅ 好写法,一层搞定
RUN apt-get update && \
    apt-get install -y curl vim && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# 3. 充分利用缓存 —— 把不变层放前面
# ✅ 先 COPY pom.xml,利用缓存避免每次改代码都重新下载依赖
COPY pom.xml .
RUN mvn dependency:go-offline  # 下载依赖(这层会被缓存)
COPY src ./src                 # 代码经常变,放后面
RUN mvn package -DskipTests

4.2 Spring Boot 应用 Dockerfile

基础版(最简单)

FROM openjdk:17-jdk-alpine
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

优化版(多阶段构建 + 安全加固)

# ============ 第一阶段:构建 ============
FROM maven:3.9-eclipse-temurin-17-alpine AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B    # 缓存依赖(代码不改时复用)
COPY src ./src
RUN mvn package -DskipTests -B      # 编译打包

# ============ 第二阶段:运行 ============
FROM eclipse-temurin:17-jre-alpine  # JRE 就够了,不用 JDK!(体积小一半 ~100MB)
WORKDIR /app

# 安全:创建非 root 用户运行应用
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 从构建阶段复制 jar
COPY --from=builder /build/target/*.jar app.jar

# 复制启动脚本(可选)
COPY docker-entrypoint.sh .
RUN chmod +x docker-entrypoint.sh

EXPOSE 8080

# 切换到非 root 用户
USER appuser

# JVM 参数通过环境变量配置,生产环境可调
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom"

# 健康检查(Spring Boot Actuator)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget -qO- http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

docker-entrypoint.sh(可选,更灵活的启动配置):

#!/bin/sh
# 等待依赖服务就绪(如数据库)
# while ! nc -z mysql 3306; do sleep 2; done

# 设置 JVM 参数
JAVA_OPTS="${JAVA_OPTS} -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

exec java ${JAVA_OPTS} -jar app.jar

对比:优化版带来的提升

版本

镜像大小

构建时间(代码改动后)

安全性

基础版

~350MB (JDK)

完整构建

root 运行

优化版

~180MB (JRE)

mvn 依赖缓存,只编译

非 root 运行

native 版

~80MB

Spring Native / GraalVM

非 root 运行


五、Docker Compose:多容器编排

当项目需要 MySQL + Redis + Nginx + App 多个容器时,手动一个一个 docker run 太麻烦。Docker Compose 用一个 YAML 文件定义所有服务,一条命令启动全部。

5.1 docker-compose.yml 完整示例

Spring Boot + MySQL + Redis + Nginx 全套

# docker-compose.yml
version: '3.8'

services:
  # ========== 1. MySQL ==========
  mysql:
    image: mysql:8.0
    container_name: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-app123}
      TZ: Asia/Shanghai
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/conf:/etc/mysql/conf.d          # 自定义配置
      - ./mysql/init:/docker-entrypoint-initdb.d # 初始化 SQL
    networks:
      - app-network
    healthcheck:                                 # 健康检查
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-root123}"]
      interval: 10s
      timeout: 5s
      retries: 5
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci

  # ========== 2. Redis ==========
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes

  # ========== 3. Spring Boot 应用 ==========
  app:
    build: .                              # 用当前目录的 Dockerfile 构建
    # image: myregistry.com/myapp:latest  # 或直接用镜像
    container_name: myapp
    restart: always
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/myapp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      SPRING_DATASOURCE_USERNAME: appuser
      SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD:-app123}
      SPRING_REDIS_HOST: redis
      SPRING_REDIS_PORT: 6379
      TZ: Asia/Shanghai
      JAVA_OPTS: "-Xms256m -Xmx512m -XX:+UseG1GC"
    volumes:
      - ./logs:/app/logs                   # 日志持久化
      - ./upload:/app/upload               # 上传文件持久化
    networks:
      - app-network
    depends_on:                            # 启动顺序(不保证就绪!)
      mysql:
        condition: service_healthy         # 等 MySQL 健康通过
      redis:
        condition: service_healthy         # 等 Redis 健康通过

  # ========== 4. Nginx ==========
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/logs:/var/log/nginx
    networks:
      - app-network
    depends_on:
      - app

# ========== 网络 ==========
networks:
  app-network:
    driver: bridge
    # 同一网络的容器可以通过服务名互相访问(如 mysql、redis、app)

# ========== 数据卷 ==========
volumes:
  mysql_data:
    driver: local
  redis_data:
    driver: local

Docker Compose 常用命令

# 启动所有服务(后台)
docker compose up -d

# 启动指定服务
docker compose up -d mysql redis

# 构建并启动(代码有改动时)
docker compose up -d --build

# 查看服务状态
docker compose ps
docker compose ps -a

# 查看日志
docker compose logs              # 所有服务
docker compose logs -f app       # 指定服务实时日志
docker compose logs --tail 100 app

# 进入服务容器
docker compose exec app /bin/sh
docker compose exec mysql mysql -uappuser -p

# 停止服务
docker compose stop              # 停止不删除
docker compose down              # 停止并删除容器和网络
docker compose down -v           # 停止并删除容器、网络、数据卷!(谨慎)
docker compose down --rmi all    # 附加删除构建的镜像

# 重启服务
docker compose restart app

# 扩容
docker compose up -d --scale app=3   # 启动 3 个 app 实例(需处理端口冲突)

# 查看资源占用
docker compose stats

# 拉取最新镜像
docker compose pull

.env 文件(环境变量管理)

# .env(和 docker-compose.yml 在同级目录)
MYSQL_ROOT_PASSWORD=ProdStr0ngP@ss2024
MYSQL_PASSWORD=appprodpass
REDIS_PASSWORD=RedisStr0ngP@ss
SPRING_PROFILES_ACTIVE=prod

六、数据卷与网络

6.1 数据卷(Volume)详解

三种挂载方式

方式

语法

宿主机路径

适用场景

Volume(命名卷)

-v volname:/data

Docker 管理的路径(/var/lib/docker/volumes/

推荐!生产环境数据持久化

Bind Mount(绑定挂载)

-v /host/path:/data

宿主机绝对路径

开发环境热更新、配置文件注入

tmpfs

--tmpfs /data

内存

临时数据、不需要持久化的缓存

Volume 命令

# 创建卷
docker volume create my_volume

# 查看卷
docker volume ls                    # 列出所有卷
docker volume inspect my_volume     # 查看卷详情(挂载点等)
docker volume inspect my_volume -f '{{.Mountpoint}}'

# 删除卷
docker volume rm my_volume          # 删除指定卷
docker volume prune                 # 删除所有未被使用的卷

# 备份卷数据
docker run --rm -v my_volume:/data -v $(pwd):/backup alpine \
  tar czf /backup/backup.tar.gz -C /data .

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

Bind Mount 实战场景

# 开发环境热更新(改代码容器立即生效)
docker run -d -p 8080:8080 \
  -v $(pwd)/target:/app \
  -v $(pwd)/config:/app/config \
  myapp:dev

# Nginx 配置实时生效
docker run -d -p 80:80 \
  -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \    # :ro = 只读
  nginx:alpine

Volume 数据共享模式

# 多个容器共享同一个卷
docker run -d --name app1 -v shared_data:/data app:v1
docker run -d --name app2 -v shared_data:/data app:v1

# 只读挂载(防止数据被修改)
docker run -d -v shared_data:/data:ro app:v1

# 数据卷容器模式(已过时但仍常见)
docker create --name data-container -v /data busybox   # 创建纯数据容器
docker run -d --name app --volumes-from data-container app:v1

6.2 Docker 网络

网络模式

模式

说明

使用场景

bridge(默认)

创建虚拟网桥 docker0,容器通过 NAT 通信

单机多容器通信

host

容器直接使用宿主机网络栈(无隔离)

高性能网络场景

none

无网络,完全隔离

安全敏感或不需要网络的容器

overlay

跨主机容器通信(Swarm 模式)

多主机集群

ipvlan/macvlan

容器拥有独立 MAC/IP(物理网络可见)

需要直连物理网络的场景

自定义网络(推荐)

# 创建网络
docker network create mynet
docker network create --subnet 172.20.0.0/16 --gateway 172.20.0.1 mynet  # 指定子网

# 查看网络
docker network ls                   # 列出所有网络
docker network inspect mynet        # 查看网络详情(容器 IP、子网等)

# 容器连接网络
docker run -d --name app --network mynet app:v1
# 同一自定义网络中的容器可以**用容器名直接互相访问**!
docker exec app ping mysql           # 直接用容器名 ping 通

# 容器连接多个网络
docker network connect mynet2 app
docker network disconnect mynet2 app

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

自定义网络 vs 默认 bridge

特性

默认 bridge

自定义 bridge

DNS 解析

只能用 IP

自动 DNS,容器名互访

隔离性

需手动管理

天然隔离,不同网络的容器互不可见

配置灵活性

有限

可自定义子网、网关等


七、Docker 进阶专题

7.1 镜像优化技巧

# 1. 选择最小基础镜像
FROM alpine:3.19                                    # ~7MB
FROM eclipse-temurin:17-jre-alpine                   # ~180MB(含 JRE)
FROM gcr.io/distroless/java17-debian12               # Google 的"无发行版"镜像(更安全)

# 2. 多阶段构建(减小最终镜像体积)
FROM node:20-alpine AS build-stage
WORKDIR /app
COPY package*.json .
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM nginx:alpine AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 最终镜像只有 nginx:alpine + dist 文件,没有 node_modules!

# 3. 善用 .dockerignore
# .dockerignore
node_modules
.git
*.md
.env
.DS_Store
dist
coverage

Alpine vs Debian vs Distroless

基础镜像

大小

glibc

包管理器

适用场景

alpine

~7MB

musl

apk

通用,最轻量

debian:slim

~80MB

glibc

apt

需要 glibc 的应用

ubuntu

~77MB

glibc

apt

广泛支持

distroless

~20MB

glibc

安全敏感,无 shell


7.2 日志管理

# 限制容器日志大小(防止撑爆磁盘)
docker run -d \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp

# docker-compose.yml 中配置日志
services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

# 使用其他日志驱动
# docker run --log-driver=syslog ...
# docker run --log-driver=fluentd ...

# 查看 Docker 日志占用
docker system df
du -sh /var/lib/docker/containers/*/

7.3 资源限制

# CPU 限制
docker run --cpus 2 \              # 最多用 2 个 CPU 核心
           --cpu-shares 512 \       # CPU 权重(相对值,默认 1024)
           app:latest

# 内存限制
docker run -m 512m \               # 最大 512MB
           --memory-swap 1g \       # 内存+Swap 上限
           --memory-reservation 256m \  # 软限制(预留内存)
           app:latest

# 在 docker-compose.yml 中
services:
  app:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

7.4 常用调试命令集合

# 查看 Docker 磁盘占用
docker system df              # 概要
docker system df -v           # 详细(每个组件占用)

# 一键清理(谨慎!)
docker system prune           # 清理停止容器、未使用网络、悬挂镜像
docker system prune -a        # 附加清理所有未使用镜像
docker system prune -a -f --volumes  # 包括未使用的卷

# 构建历史查看
docker history myapp:latest

# 查看容器文件变化
docker diff my-nginx          # 显示容器相对于镜像的文件变更

# 查看容器内运行的进程
docker top my-nginx

# 查看容器 IP 等详细信息
docker inspect my-nginx | jq '.[0].NetworkSettings.Networks'

# 测试 DNS 解析
docker exec my-nginx nslookup mysql

# 复制粘贴(理解 --rm 的用法)
docker run --rm -v $(pwd):/data alpine tar czf /data/backup.tar.gz -C /app data

7.5 安全最佳实践

# 1. 不要用 root 运行应用
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# 2. 不要写敏感信息在镜像中
# ❌ ENV DB_PASSWORD=plaintext
# ✅ 运行时传入
# docker run -e DB_PASSWORD=$(cat /run/secrets/db_password)

# 3. 使用具体版本标签,不用 latest
# ❌ FROM node:latest
# ✅ FROM node:20.11.0-alpine3.19

# 4. 定期扫描镜像漏洞
# docker scan myapp:latest

# 5. 设置只读文件系统
# docker run --read-only myapp
# 配合 tmpfs 给需要写入的目录
# docker run --read-only --tmpfs /tmp --tmpfs /var/run myapp

# 6. 限制容器能力
# docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

八、常见问题排查

Q1:容器启动了但立刻退出

# 原因:主进程执行完就退出
docker run -d alpine     # alpine 没有前台进程,立即退出

# 排查
docker logs <container>
docker ps -a | grep Exited

# 解决:确保有前台进程运行
docker run -d alpine tail -f /dev/null    # 临时保持运行
docker run -d -it alpine /bin/sh          # 交互式

Q2:端口冲突

# 报错:Bind for 0.0.0.0:8080 failed: port is already allocated
# 查看谁占用了端口
sudo lsof -i :8080
sudo netstat -tulnp | grep 8080
# 或用其他端口
docker run -p 8081:8080 myapp

Q3:容器之间无法通信

# 1. 确认在同一网络
docker network inspect mynet
# 2. 测试 DNS
docker exec app ping mysql      # 容器名能不能解析
docker exec app nslookup mysql
# 3. 确认防火墙未拦截

Q4:镜像构建太慢

# 1. 利用 Docker BuildKit 缓存
DOCKER_BUILDKIT=1 docker build -t myapp .
# 2. 把不常变的层放前面
# 3. 使用 .dockerignore 减少 context
# 4. 使用 --cache-from 复用远程缓存
docker build --cache-from myregistry.com/myapp:latest -t myapp .

附录:学习路线

阶段一:理解概念(半天)
  ├── 什么是容器?和虚拟机的区别
  ├── 镜像/容器/仓库三大概念
  └── 分层文件系统原理

阶段二:核心命令(1-2天)
  ├── docker pull/images/rmi(镜像管理)
  ├── docker run/ps/stop/rm/logs/exec(容器生命周期)
  └── docker cp/inspect/stats/top(调试查看)

阶段三:实战部署(1-2天)
  ├── docker run 部署 MySQL/Redis/Nginx
  ├── 数据卷挂载(-v)
  └── 端口映射(-p)、环境变量(-e)

阶段四:构建镜像(1-2天)
  ├── Dockerfile 编写(FROM/COPY/RUN/CMD/EXPOSE)
  ├── 多阶段构建
  └── 镜像优化(alpine / 合并层 / .dockerignore)

阶段五:多容器编排(2-3天)
  ├── docker-compose.yml 编写
  ├── depends_on + healthcheck
  └── 网络管理

阶段六:生产化(持续)
  ├── 日志管理、资源限制
  ├── CI/CD 集成
  └── K8s/监控/编排

核心原则:一定要动手!每学一个概念就在本地 Docker 上敲一遍。从 docker run nginx 开始,到写出完整 docker-compose.yml 部署 Spring Boot + MySQL + Redis + Nginx 全家桶。每天在测试服务器上操作,一周掌握。