Docker完整学习笔记

1. 初识 Docker

核心考点:Docker 解决啥、和虚拟机的区别、架构三件套

  • 解决啥:大型项目组件多(MySQL/Redis/RabbitMQ...),依赖版本冲突 + 操作系统差异 → Docker 把"应用+依赖+运行环境"打包成镜像,一次构建到处跑

  • 和虚拟机区别(面试高频):

    • 虚拟机:Hypervisor + 完整 Guest OS,重、启动慢

    • Docker:共享宿主机内核,只隔离进程,轻量,秒级启动

  • 架构三件套

    • 镜像 Image:只读模板(类比"类")

    • 容器 Container:镜像跑起来的实例(类比"对象")

    • Registry:DockerHub / 阿里云 ACR / 私有 harbor,存镜像的地方

💡 面试被问"Docker 为啥比 VM 轻",答共享内核 + 无 Guest OS​ 就够,别扯 cgroup/namespace 太细,除非对方追问。

2. 镜像加速

镜像加速本质改 /etc/docker/daemon.jsonregistry-mirrors

2026还能正常访问的

XML 复制代码
​
{

"registry-mirrors": [

"https://docker.1ms.run",

"https://docker.xuanyuan.me",

"https://docker.m.daocloud.io"

]

}

​

3. Docker 基本操作

常用命令
bash 复制代码
# ========== 镜像管理 ==========
# 列出本地镜像
docker images
# 拉取镜像
docker pull nginx:1.20
# 删除镜像
docker rmi nginx:1.20
# 导出镜像为tar文件
docker save -o nginx.tar nginx:1.20
# 从tar文件导入镜像
docker load -i nginx.tar
# 查看镜像历史分层
docker history nginx:1.20
# 标记镜像(打标签)
docker tag nginx:1.20 myrepo/nginx:1.20
# 推送镜像到仓库
docker push myrepo/nginx:1.20

# ========== 容器生命周期 ==========
# 创建并启动容器(前台)
docker run -it --name ubuntu ubuntu:22.04 /bin/bash
# 创建并启动容器(后台)
docker run -d --name nginx -p 80:80 nginx:1.20
# 启动已有容器
docker start nginx
# 停止容器
docker stop nginx
# 重启容器
docker restart nginx
# 强制停止容器
docker kill nginx
# 暂停容器
docker pause nginx
# 恢复暂停的容器
docker unpause nginx
# 删除容器(运行中需加-f)
docker rm nginx
# 强制删除运行中的容器
docker rm -f nginx
# 删除所有停止的容器
docker container prune

# ========== 容器操作 ==========
# 查看运行中的容器
docker ps
# 查看所有容器(含停止)
docker ps -a
# 查看容器详细信息(JSON)
docker inspect nginx
# 查看容器日志(实时跟踪)
docker logs -f nginx
# 进入容器执行命令(交互式)
docker exec -it nginx /bin/bash
# 在容器内执行单条命令
docker exec nginx ls /etc
# 从宿主机复制文件到容器
docker cp /path/host.txt nginx:/container/path
# 从容器复制文件到宿主机
docker cp nginx:/container/path /path/host.txt
# 查看容器内进程
docker top nginx
# 查看容器资源占用
docker stats nginx
# 查看容器端口映射
docker port nginx
# 重命名容器
docker rename old-name new-name

# ========== 数据卷 ==========
# 创建命名卷
docker volume create my-volume
# 列出所有卷
docker volume ls
# 查看卷详情
docker volume inspect my-volume
# 删除卷
docker volume rm my-volume
# 删除未使用的卷
docker volume prune
# 挂载命名卷运行容器
docker run -d --name mysql -v my-volume:/var/lib/mysql mysql:8.0
# 挂载宿主机目录(bind mount)
docker run -d --name nginx -v /host/html:/usr/share/nginx/html nginx:1.20

# ========== 网络 ==========
# 列出网络
docker network ls
# 创建桥接网络
docker network create my-net
# 查看网络详情
docker network inspect my-net
# 连接容器到网络
docker network connect my-net nginx
# 断开容器与网络
docker network disconnect my-net nginx
# 删除网络
docker network rm my-net
# 删除未使用的网络
docker network prune
# 指定网络运行容器
docker run -d --name app --network my-net my-app

# ========== Docker Compose ==========
# 启动服务(后台)
docker-compose up -d
# 启动服务并重新构建镜像
docker-compose up -d --build
# 停止服务
docker-compose stop
# 停止并删除容器、网络(默认不删卷)
docker-compose down
# 停止并删除容器、网络、卷(危险)
docker-compose down -v
# 查看服务状态
docker-compose ps
# 查看服务日志
docker-compose logs -f app
# 重启服务
docker-compose restart
# 拉取最新镜像
docker-compose pull
# 执行服务中的命令
docker-compose exec app /bin/bash

# ========== 清理与系统信息 ==========
# 查看Docker版本
docker version
# 查看Docker系统信息
docker info
# 查看磁盘使用情况
docker system df
# 清理所有未使用的资源(容器、镜像、网络、卷)
docker system prune -a --volumes
# 清理未使用的镜像
docker image prune -a
# 清理未使用的容器
docker container prune
# 清理未使用的网络
docker network prune
# 清理未使用的卷
docker volume prune
# 查看事件流(实时监控)
docker events
# 查看历史命令
docker history nginx:1.20
容器备份恢复

将一台机器上的容器备份恢复到另一台机器

bash 复制代码
# ========== 场景A:无状态容器(如 Spring Boot 镜像)→ save/load ==========

# 【原机器】1. 提交容器为镜像(如果当初是 docker run 起的、没 save 过镜像)
docker commit sb-container sb-app:1.0

# 【原机器】2. 导出镜像为 tar
docker save -o sb-app.tar sb-app:1.0

# 【原机器】3. 连同 docker-compose.yml 一起拷到新机器
scp sb-app.tar user@new-host:/opt/
scp docker-compose.yml user@new-host:/opt/

# 【新机器】4. 导入镜像
docker load -i /opt/sb-app.tar

# 【新机器】5. 启动(前提:MySQL 等其他依赖已就位)
docker-compose up -d


# ========== 场景B:有状态容器(MySQL 数据卷)→ alpine tar 法 ==========

# 【原机器】1. 停 MySQL 保一致性
docker stop mysql

# 【原机器】2. alpine 临时容器把命名卷打成 tar
docker run --rm \
  -v mysql-data:/volume \
  -v /tmp:/backup \
  alpine \
  tar -czf /backup/mysql-data.tar.gz -C /volume .

# 【原机器】3. 拷到新机器
scp /tmp/mysql-data.tar.gz user@new-host:/tmp/

# 【新机器】4. 用同名 compose 先把卷创建出来(只起 mysql 让它初始化一次然后停)
docker-compose up -d mysql
docker-compose stop mysql

# 【新机器】5. alpine 临时容器恢复卷数据
docker run --rm \
  -v myapp_mysql-data:/volume \
  -v /tmp:/backup \
  alpine \
  sh -c "rm -rf /volume/* && tar -xzf /backup/mysql-data.tar.gz -C /volume"

# 【新机器】6. 起全套
docker-compose up -d


# ========== 场景C:MySQL 跨版本/最稳 → mysqldump SQL 法 ==========

# 【原机器】1. dump(--single-transaction 保证 InnoDB 一致性)
docker exec mysql mysqldump -uroot -p${MYSQL_ROOT_PASSWORD} \
  --single-transaction --routines --triggers --all-databases > /tmp/backup.sql

# 【原机器】2. 拷 SQL + 镜像 + compose
scp /tmp/backup.sql user@new-host:/tmp/
scp sb-app.tar user@new-host:/opt/

# 【新机器】3. load 镜像 + 起空 MySQL
docker load -i /opt/sb-app.tar
docker-compose up -d mysql   # 此时库是空的

# 【新机器】4. 导入 SQL
docker cp /tmp/backup.sql mysql:/tmp/
docker exec -i mysql mysql -uroot -p${MYSQL_ROOT_PASSWORD} < /tmp/backup.sql

# 【新机器】5. 起应用
docker-compose up -d app

# 【新机器】6. 验证
docker-compose ps
curl http://localhost:8080/actuator/health
DockerFile和docker-compose.yml的用法案例

现在有个前后端分离项目:app.jar、dist(前端代码文件夹)、还用到了mysql、redis、nginx;

项目拆一下:5 样东西 ≠ 1 个 Dockerfile,Dockerfile 一次只构建一个镜像。正确拆法是:

  • app.jar​ → 自己写 Dockerfile 打 Spring Boot 镜像

  • dist + nginx​ → 写一个 Dockerfile(把 dist 拷进 nginx)

  • mysql / redis​ → 直接用官方镜像,不用自己写 Dockerfile,compose 里配就行

一、后端 Dockerfile(app.jar)

如果用jdk8,将FROM eclipse-temurin:21-jre-alpine改为FROM eclipse-temurin:8-jre-alpine

复制代码
# ========== 多阶段构建 ==========
# 阶段1:构建(如果你的 CI 已经 mvn package 了,这段可以省,直接用阶段2)
FROM maven:3.9-eclipse-temurin:21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

# 阶段2:运行
FROM eclipse-temurin:21-jre-alpine
LABEL maintainer="you@example.com"

# 时区(面试常问:为啥容器里时间不对)
ENV TZ=Asia/Shanghai

WORKDIR /app

# 从构建阶段拷 jar(CI 已经打好就用下面这句替代上面阶段1)
# COPY target/app.jar /app.jar
COPY --from=build /app/target/*.jar app.jar

# JVM 调优(8年经验简历能写"JVM 参数调优")
# -Xms -Xmx 按容器内存来,比如容器 1G 就设 512m
# UseContainerSupport 让 JVM 识别容器内存上限(JDK 8u191+ 默认开,21 肯定开)
ENTRYPOINT ["java", \
    "-Xms256m", "-Xmx512m", \
    "-XX:+UseG1GC", \
    "-Duser.timezone=Asia/Shanghai", \
    "-jar", "/app.jar"]

💡 如果公司 CI 已经 mvn package了,阶段1删掉,只留阶段2 + COPY target/app.jar /app.jar,镜像从 700MB 降到 80MB 级(alpine jre)。

二、 前端 Dockerfile(dist + nginx)

项目里 dist/是 Vue npm run build出来的,跟 nginx 放一起:

复制代码
# ========== 多阶段构建 ==========
# 阶段1:Node 编 dist(如果 dist 已经本地打好了,这段也可以省)
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --registry=https://registry.npmmirror.com
COPY . .
RUN npm run build

# 阶段2:Nginx 跑 dist
FROM nginx:1.26-alpine

# 删掉默认配置
RUN rm /etc/nginx/conf.d/default.conf

# 拷自定义 nginx.conf(反向代理后端 api 用,下面给)
COPY nginx.conf /etc/nginx/conf.d/

# 拷 dist(本地已有 dist 就 COPY dist /usr/share/nginx/html,不用阶段1)
COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80

配套 nginx.conf(解决前端路由 history 模式 + 反向代理 /api到 Spring Boot):

复制代码
server {
    listen 80;
    server_name localhost;

    # 前端静态资源
    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;   # Vue Router history 模式必配
    }

    # 反向代理后端接口
    location /api/ {
        proxy_pass http://app:8080/api/;
        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_pass http://app:8080里的 app是 compose service 名,靠自定义网络 DNS 解析,不是 localhost。

三、docker-compose.yml 把 5 样串起来(重点交付物)
复制代码
version: '3.8'

services:
  # ===== 后端 =====
  app:
    build:
      context: ./backend          # 下放 app.jar + Dockerfile
      dockerfile: Dockerfile
    container_name: app
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: prod
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/appdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: root123
      SPRING_DATA_REDIS_HOST: redis
      SPRING_DATA_REDIS_PORT: 6379
    volumes:
      - ./logs:/app/logs         # SB 日志挂出来
    depends_on:
      - mysql
      - redis
    restart: always
    networks:
      - app-net

  # ===== 前端 + nginx =====
  nginx:
    build:
      context: ./frontend        # 下放 dist + nginx.conf + Dockerfile
      dockerfile: Dockerfile
    container_name: nginx
    ports:
      - "80:80"
    depends_on:
      - app
    restart: always
    networks:
      - app-net

  # ===== MySQL =====
  mysql:
    image: mysql:8.0
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: appdb
      TZ: Asia/Shanghai
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      # 首次启动自动执行建表/初始化SQL(可选)
      # - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    command:
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --default-authentication-plugin=mysql_native_password
    restart: always
    networks:
      - app-net

  # ===== Redis =====
  redis:
    image: redis:7-alpine
    container_name: redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
      # 自定义配置(可选,关保护模式/设密码)
      # - ./redis.conf:/etc/redis/redis.conf
    command: redis-server --appendonly yes
    restart: always
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

volumes:
  mysql-data:
  redis-data:
四、目录结构参考(照着摆)
复制代码
project/
├── backend/
│   ├── Dockerfile        # 后端的
│   ├── app.jar           # mvn package 出来的
│   └── logs/             # 运行时挂出来
├── frontend/
│   ├── Dockerfile        # 前端的
│   ├── dist/             # npm run build 出来的
│   └── nginx.conf
├── docker-compose.yml
└── init.sql              # (可选) MySQL 初始化
五、启动 & 验证
复制代码
# 构建 + 启动(--build 强制重打镜像,改了 Dockerfile 必加)
docker-compose up -d --build

# 看状态
docker-compose ps

# 看后端日志
docker-compose logs -f app

# 浏览器 http://服务器IP ,nginx 80 端口进,/api 反向代理到 app:8080
  1. 多阶段构建压镜像体积:Maven 3.9 + JDK21 编,Alpine JRE21 跑,最终镜像 80MB 级

  2. 镜像分层优化 :pom.xml 单独 COPY 先 dependency:go-offline,依赖层缓存

  3. nginx 反向代理 解决跨域 + Vue history 模式 try_files

  4. compose 自定义网络​ → 服务间用容器名互访,不用写 IP

  5. 有状态服务挂卷 (mysql-data / redis-data),restart: always

⚠️ 一个坑:生产别把 mysql/redis 的 ports暴露成 "3306:3306",云服务器公网 3306 被扫很正常,改成 "127.0.0.1:3306:3306"让 nginx→app→mysql 走内网就行。

4. Docker 核心概念

4.1 镜像 Image

镜像是容器运行的模板,里面包含应用程序、依赖库、配置文件、运行命令等。

特点:

  1. 镜像是只读的。
  2. 镜像是分层的。
  3. 镜像可以基于另一个镜像构建。
  4. 镜像可以上传到仓库,也可以从仓库拉取。

示例:

复制代码
docker pull nginx:latest
docker images

4.2 容器 Container

容器是镜像运行起来之后的实例。

特点:

  1. 一个镜像可以启动多个容器。
  2. 容器之间默认相互隔离。
  3. 容器删除后,容器可写层中的数据默认会丢失。
  4. 持久化数据应使用 volume 或 bind mount。

示例:

复制代码
docker run -d --name my-nginx -p 8080:80 nginx
docker ps
docker stop my-nginx
docker rm my-nginx

4.3 仓库 Registry

镜像仓库用于保存和分发镜像。

常见仓库:

  1. Docker Hub。
  2. 阿里云容器镜像服务。
  3. 腾讯云镜像仓库。
  4. Harbor 私有仓库。

常用命令:

复制代码
docker login
docker tag myapp:1.0 username/myapp:1.0
docker push username/myapp:1.0
docker pull username/myapp:1.0

AI写代码bash

  • 1
  • 2
  • 3
  • 4

5. Docker 安装

以下以 CentOS Stream 8 为例。

5.1 准备环境

实验环境:

  1. 容器管理工具:Docker Engine。
  2. 容器运行时:containerd、runc。
  3. 操作系统:CentOS Stream 8。
5.2 配置 Docker 软件源
复制代码
yum install -y yum-utils device-mapper-persistent-data lvm2

yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

5.3 安装 Docker

复制代码
yum install -y docker-ce docker-ce-cli containerd.io
5.4 启动 Docker
复制代码
systemctl enable docker.service --now

docker --version
systemctl status docker
5.5 配置镜像加速
复制代码
mkdir -p /etc/docker
vim /etc/docker/daemon.json

示例配置:

复制代码
{
  "registry-mirrors": [
    "https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com"
  ]
}

重启 Docker:

复制代码
systemctl daemon-reload
systemctl restart docker
docker info

6. Docker 常用命令

6.1 镜像命令
复制代码
# 查看本地镜像
docker images

# 搜索镜像
docker search nginx

# 拉取镜像
docker pull nginx:latest

# 删除镜像
docker rmi nginx:latest

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

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

6.2 容器命令

复制代码
# 创建并启动容器
docker run -d --name web -p 8080:80 nginx

# 查看正在运行的容器
docker ps

# 查看所有容器
docker ps -a

# 停止容器
docker stop web

# 启动已停止容器
docker start web

# 重启容器
docker restart web

# 删除容器
docker rm web

# 强制删除运行中的容器
docker rm -f web
6.3 进入容器
复制代码
docker exec -it web bash

如果容器中没有 bash,可以使用 sh:

复制代码
docker exec -it web sh
6.4 查看日志
复制代码
docker logs web
docker logs -f web
docker logs --tail 100 web
6.5 文件复制
复制代码
# 从宿主机复制文件到容器
docker cp ./index.html web:/usr/share/nginx/html/index.html

# 从容器复制文件到宿主机
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
6.6 查看资源占用
复制代码
docker stats
6.7 清理资源
复制代码
# 删除停止的容器
docker container prune

# 删除无用镜像
docker image prune

# 删除无用网络
docker network prune

# 删除无用 volume
docker volume prune

# 清理所有无用资源
docker system prune

注意:docker system prune -a 会删除所有未被容器使用的镜像,生产环境慎用。

7. docker run 常用参数

复制代码
docker run [OPTIONS] IMAGE [COMMAND]

常用参数:

参数 作用
-d 后台运行
-it 交互式终端
--name 指定容器名称
-p 端口映射
-v 挂载目录或数据卷
-e 设置环境变量
--network 指定网络
--restart 设置重启策略
--rm 容器退出后自动删除
--privileged 开启特权模式
--memory 限制内存
--cpus 限制 CPU

示例:

复制代码
docker run -d \
  --name mysql8 \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql-data:/var/lib/mysql \
  --restart=always \
  mysql:8.0

8. Docker 网络

8.1 网络模式

Docker 常见网络模式:

网络模式 说明
bridge 默认模式,容器通过 docker0 网桥通信
host 容器直接使用宿主机网络
none 容器没有网络
container 与另一个容器共享网络命名空间
自定义 bridge 推荐用于多个容器互相通信
8.2 常用网络命令
复制代码
# 查看网络
docker network ls

# 查看网络详情
docker network inspect bridge

# 创建自定义网络
docker network create app-net

# 使用指定网络启动容器
docker run -d --name nginx --network app-net nginx

# 将容器连接到网络
docker network connect app-net nginx

# 将容器移出网络
docker network disconnect app-net nginx
8.3 容器之间通信

推荐使用自定义网络。处于同一个自定义网络中的容器,可以通过容器名互相访问。

示例:

复制代码
docker network create app-net

docker run -d --name redis --network app-net redis:7

docker run -it --rm --network app-net redis:7 redis-cli -h redis

9. Docker 存储

9.1 容器数据为什么会丢失

容器运行后会在镜像只读层上增加一层可写层。如果删除容器,这一层也会被删除。因此数据库、上传文件、日志等需要持久化的数据不能只放在容器内部。

9.2 volume 数据卷

volume 是 Docker 管理的数据卷,适合保存数据库数据。

复制代码
# 创建数据卷
docker volume create mysql-data

# 查看数据卷
docker volume ls

# 查看数据卷详情
docker volume inspect mysql-data

# 使用数据卷
docker run -d \
  --name mysql8 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0
9.3 bind mount 目录挂载

bind mount 是把宿主机目录挂载到容器中,适合挂载配置文件、项目代码。

复制代码
docker run -d \
  --name web \
  -p 8080:80 \
  -v /opt/nginx/html:/usr/share/nginx/html \
  nginx
9.4 volume 与 bind mount 对比
对比项 volume bind mount
管理方 Docker 管理 用户自己管理
路径 Docker 默认路径 用户指定路径
适用场景 数据库、持久化数据 配置文件、代码目录
可移植性 较好 依赖宿主机目录结构

10. Dockerfile

Dockerfile 是用来构建镜像的文本文件,里面定义了基础镜像、依赖安装、文件复制、启动命令等步骤。

10.1 常用指令
指令 作用
FROM 指定基础镜像
LABEL 添加镜像元数据
WORKDIR 指定工作目录
COPY 复制文件到镜像
ADD 复制文件,支持自动解压和 URL
RUN 构建镜像时执行命令
CMD 容器启动时默认命令
ENTRYPOINT 容器启动入口
ENV 设置环境变量
ARG 构建参数
EXPOSE 声明容器端口
VOLUME 声明挂载点
USER 指定运行用户
10.2 Dockerfile 示例:Nginx 静态站点
复制代码
FROM nginx:1.25-alpine

COPY ./dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

构建和运行:

复制代码
docker build -t my-nginx-site:1.0 .
docker run -d --name site -p 8080:80 my-nginx-site:1.0
10.3 Dockerfile 示例:Java 应用
复制代码
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

COPY target/app.jar /app/app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "/app/app.jar"]
10.4 CMD 与 ENTRYPOINT 区别

CMD 提供默认启动命令,容易被 docker run 后面的命令覆盖。

ENTRYPOINT 定义容器固定入口,更适合作为主程序入口。

示例:

复制代码
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
CMD ["--spring.profiles.active=prod"]

运行时可以覆盖 CMD 参数:

复制代码
docker run myapp:1.0 --spring.profiles.active=test
10.5 镜像构建优化
  1. 选择更小的基础镜像,例如 alpine、slim。
  2. 合并相关 RUN 指令,减少镜像层数。
  3. 使用 .dockerignore 排除无关文件。
  4. 利用构建缓存,把不常变化的步骤放前面。
  5. 使用多阶段构建减少最终镜像体积。
  6. 不把密码、密钥写入镜像。
  7. 尽量使用非 root 用户运行应用。
10.6 多阶段构建示例
复制代码
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:17-jre-alpine

WORKDIR /app
COPY --from=builder /build/target/app.jar /app/app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
  1. Docker Compose

Docker Compose 用于定义和运行多个容器。适合本地开发、测试环境和小规模部署。

11.1 Compose 常用命令
复制代码
# 启动服务
docker compose up -d

# 查看服务
docker compose ps

# 查看日志
docker compose logs -f

# 停止并删除服务
docker compose down

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

# 停止服务但不删除容器
docker compose stop

# 启动已停止服务
docker compose start

11.2 Compose 示例:Web + MySQL + Redis

复制代码
services:
  app:
    build: .
    container_name: demo-app
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: 123456
      SPRING_REDIS_HOST: redis
    depends_on:
      - mysql
      - redis
    networks:
      - app-net

  mysql:
    image: mysql:8.0
    container_name: demo-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: demo
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - app-net

  redis:
    image: redis:7
    container_name: demo-redis
    ports:
      - "6379:6379"
    networks:
      - app-net

networks:
  app-net:

volumes:
  mysql-data:

启动:

复制代码
docker compose up -d

12. Docker 日志与排错

12.1 常见排错命令
复制代码
# 查看容器状态
docker ps -a

# 查看容器日志
docker logs 容器名

# 查看容器详细信息
docker inspect 容器名

# 进入容器检查
docker exec -it 容器名 sh

# 查看容器资源占用
docker stats

# 查看端口映射
docker port 容器名

# 查看 Docker 服务日志
journalctl -u docker -f
12.2 容器启动失败排查思路
  1. docker ps -a 查看容器是否退出。
  2. docker logs 查看应用错误日志。
  3. 检查端口是否被占用。
  4. 检查环境变量是否配置正确。
  5. 检查挂载路径是否存在、权限是否正确。
  6. 检查镜像架构是否与服务器架构匹配。
  7. 检查容器启动命令是否正确。
12.3 端口访问不了排查思路
  1. 容器是否正在运行。
  2. 是否配置了 -p 宿主机端口:容器端口
  3. 应用是否监听 0.0.0.0,而不是只监听 127.0.0.1
  4. 宿主机防火墙或安全组是否放行。
  5. 容器内部服务是否正常。

13. Docker 安全建议

  1. 不在镜像中写入密码、Token、私钥。
  2. 尽量不要使用 --privileged
  3. 生产环境尽量使用固定版本标签,不使用 latest
  4. 使用非 root 用户运行应用。
  5. 只暴露必要端口。
  6. 定期更新基础镜像。
  7. 对镜像进行漏洞扫描。
  8. 限制容器 CPU 和内存资源。
  9. 对重要数据使用 volume 持久化,并定期备份。

14. Docker 实践项目

项目 1:使用 Docker 部署 Nginx 静态网站

目标:

  1. 使用 Nginx 容器运行一个静态页面。
  2. 将宿主机目录挂载到容器。
  3. 通过浏览器访问页面。

步骤:

复制代码
mkdir -p /opt/docker-demo/html
echo "Hello Docker" > /opt/docker-demo/html/index.html

docker run -d \
  --name nginx-demo \
  -p 8080:80 \
  -v /opt/docker-demo/html:/usr/share/nginx/html \
  nginx:1.25

访问:

复制代码
http://服务器IP:8080
项目 2:使用 Docker 部署 MySQL

目标:

  1. 启动 MySQL 8 容器。
  2. 使用 volume 持久化数据。
  3. 使用客户端连接数据库。

步骤:

复制代码
docker volume create mysql-data

docker run -d \
  --name mysql8 \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -e MYSQL_DATABASE=demo \
  -v mysql-data:/var/lib/mysql \
  --restart=always \
  mysql:8.0

进入 MySQL:

复制代码
docker exec -it mysql8 mysql -uroot -p123456
项目 3:使用 Dockerfile 打包 Spring Boot 项目

目标:

  1. 将 Spring Boot 项目打包成 jar。
  2. 编写 Dockerfile。
  3. 构建镜像并运行容器。

Dockerfile:

复制代码
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app
COPY target/*.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

构建运行:

复制代码
mvn clean package -DskipTests

docker build -t springboot-demo:1.0 .

docker run -d \
  --name springboot-demo \
  -p 8080:8080 \
  springboot-demo:1.0
项目 4:使用 Docker Compose 部署完整后端环境

目标:

  1. 使用 Compose 同时启动应用、MySQL、Redis。
  2. 应用通过服务名访问 MySQL 和 Redis。
  3. 使用 volume 保存 MySQL 数据。

文件:docker-compose.yml

复制代码
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: demo
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - app-net

  redis:
    image: redis:7
    networks:
      - app-net

  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      MYSQL_HOST: mysql
      REDIS_HOST: redis
    depends_on:
      - mysql
      - redis
    networks:
      - app-net

networks:
  app-net:

volumes:
  mysql-data:

启动:

复制代码
docker compose up -d --build

停止:

复制代码
docker compose down

15. Docker 常见面试题

1. Docker 和虚拟机有什么区别?

Docker 是进程级隔离,共享宿主机内核,启动快、资源占用少;虚拟机是硬件级虚拟化,每台虚拟机都有完整操作系统,隔离性更强但资源占用更大。

2. Docker 镜像和容器有什么区别?

镜像是只读模板,容器是镜像运行后的实例。一个镜像可以创建多个容器,容器有自己的可写层。

3. Docker 为什么启动快?

容器不需要启动完整操作系统,而是共享宿主机内核,本质上是启动一个隔离的进程,所以启动速度很快。

4. Docker 底层隔离原理是什么?

主要依赖 Linux Namespace 和 Cgroups。Namespace 负责隔离进程、网络、文件系统等资源;Cgroups 负责限制和统计 CPU、内存、I/O 等资源。

5. Docker 镜像为什么是分层的?

镜像分层可以复用已有层,减少存储空间和网络传输成本。构建镜像时,如果某一层没有变化,可以直接使用 缓存 。

6. Dockerfile 中 CMD 和 ENTRYPOINT 有什么区别?

CMD 是默认命令,容易被 docker run 后面的参数覆盖;ENTRYPOINT 是容器启动入口,通常用于固定启动主程序。二者可以配合使用,ENTRYPOINT 定义程序,CMD 提供默认参数。

7. COPY 和 ADD 有什么区别?

COPY 只负责复制文件或目录,语义更清晰,推荐优先使用。ADD 除了复制,还支持自动解压 tar 文件和从 URL 添加文件。

8. volume 和 bind mount 有什么区别?

volume 由 Docker 管理,适合数据库等持久化数据;bind mount 使用宿主机指定路径,适合挂载配置文件或代码目录。

9. 容器之间如何通信?

推荐创建自定义 bridge 网络,然后让容器加入同一个网络。这样容器之间可以通过容器名或服务名访问。

10. Docker Compose 是什么?

Docker Compose 是用于定义和管理多容器应用的工具,通过 docker-compose.yml 描述服务、网络、数据卷等配置,再用一条命令启动整个应用。

11. 如何减少 Docker 镜像体积?

可以选择更小的基础镜像,使用多阶段构建,合并 RUN 指令,清理安装缓存,使用 .dockerignore 排除无关文件,并避免把源码、测试文件、构建工具放入最终镜像。

12. 容器退出后数据会不会丢失?

如果数据只保存在容器可写层,删除容器后会丢失。需要持久化的数据应保存到 volume 或 bind mount 中。

13. 如何查看容器日志?
复制代码
docker logs 容器名
docker logs -f 容器名
docker logs --tail 100 容器名
14. 如何进入正在运行的容器?
复制代码
docker exec -it 容器名 bash

如果没有 bash:

复制代码
docker exec -it 容器名 sh
15. Docker 容器访问不了外部端口怎么办?

排查方向:

  1. 容器是否运行。
  2. 是否正确配置 -p 端口映射。
  3. 应用是否监听 0.0.0.0
  4. 防火墙或云服务器安全组是否放行。
  5. 容器日志是否报错。
16. Docker 中 latest 标签有什么问题?

latest 不代表最新稳定版本,只是一个普通标签。生产环境使用 latest 可能导致版本不可控,建议使用明确版本号。

17. 什么是 Dockerfile 构建缓存?

Docker 构建镜像时会按指令逐层执行。如果某一层指令和上下文没有变化,Docker 会复用之前的缓存,从而加快构建速度。

18. 如何限制容器资源?
复制代码
docker run -d \
  --name app \
  --memory=512m \
  --cpus=1 \
  nginx
19. 容器内应用为什么要前台运行?

容器的生命周期由主进程决定。如果主进程退出,容器也会退出。因此容器中的主应用一般要以前台方式运行。

20. Docker 适合运行有状态服务吗?

可以运行,但要正确处理数据持久化、备份、恢复、网络和资源限制。数据库这类有状态服务在生产环境中要更加谨慎,通常需要结合专业运维方案。

16. 学习路线建议

  1. 先掌握镜像、容器、仓库三个核心概念。
  2. 熟练使用 docker rundocker psdocker logsdocker exec
  3. 学会网络和数据卷,理解容器间通信和数据持久化。
  4. 学会编写 Dockerfile,把自己的应用打包成镜像。
  5. 学会 Docker Compose,能启动一套完整开发环境。
  6. 最后再学习 Kubernetes、CI/CD、镜像仓库和生产环境部署。