CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
DevOps 入门实战/课时/Docker
RawBlame
课时目录12 节

DevOps 入门实战

  1. 01DevOps 入门
  2. 02操作系统
  3. 03Git 与 GitHub
  4. 04脚本语言
  5. 05网络协议
  6. 06Docker
  7. 07AWS
  8. 08Terraform
  9. 09Ansible
  10. 10GitHub Actions
  11. 11Nginx
  12. 12综合练习

Docker

容器入门。

30 分钟

Docker

这一课你将能做到

  1. 说清楚镜像、容器、仓库三者关系,理解容器和虚拟机的本质区别
  2. 为一个 Spring Boot 应用写出多阶段构建的 Dockerfile,构建出 <200MB 的生产镜像
  3. 用 Docker Compose 一键拉起「Spring Boot + MySQL + Redis + Nginx」完整环境

为什么要学

你肯定听过这句话:「我本地跑得好好的,到你机器上就不行」。原因是环境差异——JDK 版本、系统库、配置文件、环境变量,任何一项不同都可能导致行为不一致。Docker 的解决思路极其暴力:把应用和它需要的整个运行环境(操作系统层以上的所有东西)打包成一个镜像,这个镜像在哪里跑都一模一样。

人话:Docker 之于应用,就像集装箱之于货物。以前码头工人要懂每种货物怎么搬(每种应用怎么装环境),现在全部装进标准集装箱(镜像),码头(服务器)只需要会操作集装箱(跑容器)就行。这也是为什么 Docker 的 logo 是一头驮着集装箱的鲸鱼。

对本站技术栈而言:以前部署要在服务器上装 JDK、MySQL、Redis、Nginx,配版本、调依赖,一台新机器要折腾半天;用 Docker 后,docker compose up -d 一条命令,3 分钟完整环境就绪。

先建立图像

容器 vs 虚拟机:

虚拟机(VM)                     容器(Container)
┌──────────────┐                ┌──────────────┐
│   应用 A      │                │   应用 A      │
├──────────────┤                ├──────────────┤
│  完整客户机OS │  重(GB级)     │  应用+依赖    │  轻(MB级)
├──────────────┤  启动分钟级     ├──────────────┤  启动秒级
│ Hypervisor   │                │ Docker Engine │
├──────────────┤                ├──────────────┤
│    宿主机 OS   │                │   宿主机 OS    │  共享内核
└──────────────┘                └──────────────┘

镜像 → 容器 → 仓库的关系:

Dockerfile(配方)──build──▶ 镜像 Image(模具)──run──▶ 容器 Container(实例)
                                │
                            push │ pull
                                ▼
                          仓库 Registry(Docker Hub / 阿里云 ACR)

跟着做

步骤 1:安装 Docker 并跑通 hello world

# Ubuntu 安装(一行官方脚本)
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker

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

# 验证
docker run hello-world
docker version
docker info

步骤 2:核心命令肌肉记忆

# 镜像操作
docker pull nginx:1.25          # 拉镜像
docker images                   # 看本地镜像
docker rmi nginx:1.25           # 删镜像

# 容器生命周期
docker run -d --name web -p 80:80 nginx:1.25   # 后台跑,端口映射
docker ps                       # 运行中的容器
docker ps -a                    # 所有容器(含已停止)
docker logs -f web              # 实时日志
docker exec -it web bash        # 进入容器
docker stop web && docker rm web

# 排查三件套
docker inspect web              # 容器全部配置
docker stats                    # 资源占用实时面板
docker system df                # 磁盘占用

步骤 3:为 Spring Boot 写多阶段 Dockerfile

在 Spring Boot 项目根目录创建 Dockerfile:

# 阶段 1:构建(用大镜像,编译完就扔)
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /build
COPY pom.xml .
# 单独拷贝 pom 先下依赖,利用 Docker 层缓存:pom 没变就不用重下
RUN mvn dependency:go-offline -q
COPY src ./src
RUN mvn package -DskipTests -q

# 阶段 2:运行(用最小 JRE 镜像)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

# 创建非 root 用户(安全基线)
RUN addgroup -S app && adduser -S app -G app
USER app

COPY --from=build /build/target/*.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]

构建并运行:

docker build -t myapp:1.0.0 .
docker run -d --name myapp -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  myapp:1.0.0
curl http://localhost:8080/actuator/health

检查镜像大小:docker images myapp —— 多阶段 + alpine 应该在 150~200MB,直接用 maven 镜像跑会超过 600MB。

步骤 4:Docker Compose 编排完整技术栈

创建 docker-compose.yml:

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mydb?useSSL=false
      SPRING_DATASOURCE_USERNAME: myuser
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}   # 从 .env 文件读,不硬编码
      SPRING_DATA_REDIS_HOST: redis
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: mydb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
    volumes:
      - mysql-data:/var/lib/mysql     # 数据持久化,容器删了数据还在
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p$MYSQL_PASSWORD"]
      interval: 5s
      retries: 10
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis-data:/data
    restart: unless-stopped

  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    restart: unless-stopped

volumes:
  mysql-data:
  redis-data:

配套 .env(记得加进 .gitignore!):

DB_PASSWORD=Str0ng!Pass
REDIS_PASSWORD=An0ther!Pass

启动整个栈:

docker compose up -d          # 一键拉起全部
docker compose ps             # 看各服务状态
docker compose logs -f app    # 看应用日志
docker compose down           # 全部停止并删除(数据卷保留)

步骤 5:理解容器网络

Compose 自动创建一个网络,服务之间用服务名互相访问:Spring Boot 连数据库写 mysql:3306 而不是 IP。这是容器编排的核心便利之一。

docker network ls
docker compose exec app ping mysql    # 在 app 容器里能 ping 通 mysql 服务名

讲清楚要点

  1. 镜像分层与缓存:Dockerfile 每条指令生成一层,层有缓存——内容没变的层直接复用。所以把变化少的指令(pom.xml、依赖下载)放前面,代码 COPY 放最后,构建速度差 10 倍。
  2. 容器是进程不是虚拟机:容器里跑的 java 进程直接出现在宿主机 ps aux 里(PID 不同而已)。容器删了里面数据就没,持久数据必须挂卷(volume)。
  3. 容器内必须有 1 号前台进程:容器的主进程退出 = 容器退出。CMD ["java", "-jar", ...] 保持前台运行;不要用 CMD java -jar app.jar & 这种把主进程放后台的写法。
  4. 数据卷 vs 绑定挂载:volume(mysql-data:/var/lib/mysql)由 Docker 管理,生产推荐;bind mount(./nginx.conf:/etc/nginx/nginx.conf)直接映射宿主机文件,适合配置文件。
  5. 多阶段构建是 Java 镜像标配:编译需要 Maven + JDK(600MB+),运行只需要 JRE(~80MB)。分两个阶段,最终镜像只含运行时,又小又安全。

你可能会懵的地方

  • 容器和镜像到底什么关系? 类与对象的关系:镜像是类(只读模板),容器是对象(运行实例)。一个镜像可以跑 10 个容器,就像一个人设图纸可以盖 10 栋楼。

  • 容器删了数据是不是就没了? 容器内的可写层会随容器删除而消失。但挂载的 volume 独立于容器生命周期,docker compose down 默认不删 volume,除非你加 -v。所以 MySQL 数据放 volume 里是安全的。

  • 为什么我的 Java 容器被 OOM Kill 了? 老版本 JVM 不认识容器内存限制,按宿主机内存算堆大小。JDK 17 默认开启容器感知,-XX:MaxRAMPercentage=75 表示堆最大用容器内存的 75%(留 25% 给堆外)。Docker 侧用 --memory 1g 限制容器。

  • 镜像 tag 不写就是 latest,有什么坑? latest 只是默认 tag 名,不代表「最新版本」,更不自动更新。生产环境必须用明确版本号(myapp:1.2.3),否则你无法确认线上跑的到底是哪个版本,回滚也无从谈起。

  • docker-compose 和 Docker Compose 有什么区别? docker-compose(带横杠)是老的 Python 独立工具,docker compose(空格)是 Docker 官方内置插件(Go 重写)。新版本都用后者,命令基本兼容。

错法对照

错法问题正解
Dockerfile 用 latest 基础镜像基础镜像更新导致构建行为突变锁定版本:mysql:8.0、nginx:1.25-alpine
容器内用 root 跑应用容器逃逸风险被放大Dockerfile 里 USER app
密码写死在 compose 文件进 Git 泄露.env 文件 + .gitignore
MySQL 不挂 volume容器重建数据全丢volumes: - mysql-data:/var/lib/mysql
一个容器里塞 Nginx+Java+MySQL违背单一职责,无法独立伸缩一个容器一个进程,用 Compose 编排

卡点急救

  1. 容器启动立刻退出:docker logs <容器> 看报错。最常见原因:主进程启动失败(端口冲突、配置缺失、依赖服务没就绪)。Compose 里用 depends_on + healthcheck 解决启动顺序。
  2. 镜像越来越大,磁盘告警:docker system df 看占用 → docker system prune -a 清理无用镜像/停止的容器/悬空层(注意会删未使用镜像)。日常养成 docker compose down 而不是只 stop 的习惯。
  3. 容器内连不上 MySQL:确认走的是 Compose 网络(服务名解析)而不是 localhost——容器内的 localhost 是容器自己,不是宿主机。Java 配置里写 jdbc:mysql://mysql:3306(服务名),不是 localhost:3306。

面试常问

  • Docker 和虚拟机的区别? 虚拟机通过 Hypervisor 虚拟硬件,跑完整客户机 OS,隔离强但重(GB 级、分钟级启动);容器共享宿主机内核,通过 namespace 做隔离(进程/网络/文件系统各有独立视图)、cgroup 做资源限制,轻量(MB 级、秒级启动)。隔离性 VM 更强,密度和速度容器完胜。K8s 时代也有两者结合的方案(Kata Containers)。

  • Dockerfile 里 COPY 和 ADD、CMD 和 ENTRYPOINT 的区别? COPY 就是拷贝文件,ADD 额外支持 URL 下载和 tar 自动解压(但官方建议优先 COPY,行为更可预测)。CMD 提供默认命令可被 docker run 后的参数覆盖,ENTRYPOINT 是固定入口,CMD 内容变成 ENTRYPOINT 的参数。常见模式:ENTRYPOINT ["java", "-jar", "app.jar"] + CMD ["--spring.profiles.active=prod"]。

  • 如何排查容器内的网络问题? 思路:宿主机视角 docker inspect 看网络配置和端口映射 → docker exec -it 容器 sh 进容器 → 容器内用 ss/ping/curl 测连通性 → 跨容器通信用服务名。容器镜像太精简没工具时,可以用 nicolaka/netshoot 这种排障专用镜像共享网络命名空间诊断。

自测三问

  1. 为什么 Dockerfile 要把 COPY pom.xml 和依赖下载放在 COPY src 之前?
  2. docker compose down 之后 MySQL 数据还在吗?为什么?
  3. 容器之间互相访问应该用什么地址?为什么在容器里访问 localhost:3306 连不上宿主机 MySQL?

小结一句话

Docker 把「应用 + 环境」封装成标准镜像,让「一次构建,到处运行」成为现实,是现代 DevOps 的基石。

延伸

  • Docker 官方文档
  • 下一课:AWS——把容器跑在云上,认识云厂商的基础服务。
加载本节练习…
← 上一课网络协议下一课 →AWS