Docker 学习篇四| Docker的 核心概念第一部分基础三要素1. 镜像Image2. 容器Container3. Dockerfile第二部分启动容器的四件套4. 端口映射-p5. 数据挂载-v6. 环境变量-e7. 资源限制第三部分容器管理8. 生命周期9. 进入容器exec10. 查看日志logs11. 清理命令第四部分编排与组合12. 容器间网络13. 重启策略14. 健康检查Health Check15. docker-composebuild: vs image: —— 什么时候用哪个第五部分构建优化16. 分层缓存17. 多阶段构建18. .dockerignore速查总表第一部分基础三要素1. 镜像Image镜像 程序 运行环境打包成一个只读文件。MySQL 镜像里包含MySQL 程序 微型 Linux MySQL 需要的所有依赖。这个镜像扔到任何装了 Docker 的机器上都能还原出一模一样的运行效果。镜像和容器的关系镜像 : 容器 ≈ Word 模板 : 打开的编辑窗口。模板只有一个用它创建的窗口可以有无数个。镜像是分层存储的。每条 Dockerfile 指令生成一层┌─────────────────────┐ │ COPY demo.jar │ ← 层3你的 jar ├─────────────────────┤ │ JDK 21 JRE │ ← 层2运行时 ├─────────────────────┤ │ Alpine Linux │ ← 层1操作系统 └─────────────────────┘ 最终镜像 层1~3 叠加CMD、EXPOSE、ENV 不产生新层只有 RUN、COPY、ADD 才会。五个项目都用同一个 JRE 层 → 这一层只存一份各镜像共享引用。这就是为什么 Docker 比虚拟机省磁盘。镜像按用途分两类基础镜像JDK中间件镜像MySQL/Redis用途作为项目镜像的底座提供独立网络服务存在意义被 Dockerfile 引用FROM被docker run启动成容器不启动容器能用吗✅ 能已打进项目镜像❌ 不能不启动则服务不可用记法基础镜像 底座用了就行。中间件镜像 下载了安装包但没安装必须docker run才干活。2. 容器Container容器是用镜像启动出来的运行实例。它是真正在干活的东西——MySQL 容器在存数据Redis 容器在缓存你的 blog-server 容器在处理请求。容器之间文件系统、进程、网络完全隔离。删容器就像删一个 Word 窗口模板镜像不受影响。3. Dockerfile你自己的项目没有现成镜像需要写一份打包说明书告诉 Docker 怎么构建。这个说明书就是 Dockerfile。先回顾没有 Docker 时你怎么部署 Java 项目的mvn package → blog.jar → 传到 Linux → java -jar blog.jar有了 Docker 以后Spring Boot 项目有三种构建方式本质区别只有一个编译mvn package谁来跑。方式一基础 Dockerfile相当于传统方式# FROM 基于哪个基础镜像这里用 JDK 21 精简版 FROM eclipse-temurin:21-jdk-slim # COPY 把宿主机文件复制到镜像里 # 左边你电脑上的 jar 右边镜像里的路径 COPY target/*.jar app.jar # EXPOSE 声明容器监听哪个端口文档性质不实际映射 EXPOSE 8080 # CMD 容器启动时执行的命令 CMD [java, -jar, app.jar]先手动mvn package再把打好的 jar COPY 进镜像。流程你电脑 mvn package → docker build → 镜像最终镜像包含完整 JDK jar约 400MB。和传统部署一模一样只是目的地从 Linux 服务器换成了 Docker 镜像。方式二多阶段构建推荐# 阶段1编译只用来构建不进入最终镜像 # FROM 基础镜像AS builder 给这个阶段起个名字 FROM maven:3.9-eclipse-temurin-21 AS builder # COPY . . 把当前目录所有文件复制到镜像工作目录 COPY . . # RUN 在构建过程中执行命令这里跑 Maven 编译 RUN mvn package -DskipTests # 阶段2运行最终镜像只包含这个阶段 # FROM 换个轻量基础镜像只有 JRE 没有 Maven FROM eclipse-temurin:21-jre-alpine # COPY --from 从指定阶段复制文件只取 jar不要源码和 Maven # 单模块项目target/*.jar多模块项目blog-bootstrap/target/*.jar COPY --frombuilder target/*.jar app.jar # EXPOSE 声明端口 EXPOSE 8080 # CMD 启动命令 CMD [java, -jar, app.jar]编译也进了 Docker你不需要手动mvn package。流程docker build → 镜像一条命令编译打包全自动最终镜像只有 JRE jar约 250MB。比方式一多了AS builderCOPY --frombuilder两行省了你手动打包步骤。为什么方式二比方式一小省的不是 jar是底座。两个方式最终都只拿了一个 jar方式一 底座eclipse-temurin:21-jdk-slim ← JDK ≈ 350MB 方式二 底座eclipse-temurin:21-jre-alpine ← JRE ≈ 180MBJDK vs JREJDKJava Development Kit ├── JREJava Runtime Environment← 运行时180MB ├── javac 编译器 ← 源码 → .class ├── jdb 调试器 ├── javadoc 文档生成器 └── 其他开发工具... 总约 350MB你在 IDEA 里写代码、改代码、编译、调试 → 必须用 JDKjar 部署到服务器上只跑不编译 → JRE 就够了方式一不分阶段只能用 JDK 当底座约 350MB方式二把编译交给阶段1Maven 镜像阶段2 换 JRE 底座约 180MB。jar 没变底座瘦了 170MB。方式三Buildpacks零配置不需要 Dockerfile一行命令mvn spring-boot:build-image全自动黑盒生成镜像约 500MB。上手最快但命名不灵活、构建过程黑盒、出问题难排查。三种方式对比方式一 基础方式二 多阶段方式三 Buildpacks需要 Dockerfile需要需要不需要需要手动 mvn package需要不需要不需要最终镜像体积~400MB~250MB~500MB相当于传统方式吗✅ 流程一样传统方式的自动化版全自动黑盒推荐度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐Dockerfile 命名规则文件名固定叫DockerfileD 大写无后缀放在项目根目录docker build默认在当前目录找这个文件非要用别的名字也行docker build -f 其他文件名 .但没人这么做构建时永远加-t起名字否则 Docker 分配随机 IDdockerbuild-tblog-server:latest.# ↑ ↑# 镜像名:标签 构建上下文Dockerfile 所在目录标签不写默认为latest但它只是个标签名不代表最新版本——Docker 不会自动帮你更新它。第二部分启动容器的四件套4. 端口映射-pdockerrun-p3306:3306 mysql:8.0# ↑ ↑# 宿主机 容器内部默认不加-p外面访问不到容器。同一个 Docker 网络下的容器之间不受影响——它们走容器名就能互访不需要端口映射。一句话判断需要从宿主机或外部访问容器 → 加 -p 只是容器和容器之间通信 → 不加场景要不要映射例子浏览器访问前端页面✅ 必须-p 80:80IDEA 连 MySQL 调试✅ 开发时需要-p 3306:3306blog-server 连 MySQL❌ 不用容器名mysql:3306直接通blog-server 连 Redis❌ 不用容器名redis:6379直接通两个实用技巧端口冲突了改左边-p 3307:3306→localhost:3307连只让本机访问-p 127.0.0.1:3306:3306开发 vs 部署开发环境想连什么映射什么方便调试 MySQL ✓ Redis ✓ blog-server ✓ blog-ui ✓ 部署上线只映射前端其余全关 MySQL ✗ Redis ✗ blog-server ✗ blog-ui ✓ ↑ 多映射一个端口就多一扇攻击面记住一条容器间互访走容器名不经过端口映射。只有外面要进来才需要开映射。5. 数据挂载-vdockerrun-vD:/data/mysql:/var/lib/mysql mysql:8.0# ↑ ↑# 宿主机目录 容器内 MySQL 存数据的地方默认行为不加-v数据存在容器的可写层里。容器在数据在容器删数据跟着消失没有任何恢复手段。什么时候不用加无状态服务或临时测试。比如你跑个hello-world、临时拉个镜像试一下——用完就删不需要持久化。什么时候必须加数据库MySQL、PostgreSQL 等文件存储上传的图片、附件等任何删容器后还想保留的数据三种挂载方式怎么选方式写法适用场景绑定挂载-v D:/data/mysql:/var/lib/mysql开发用你知道路径在哪命名卷-v mysql-data:/var/lib/mysql生产推荐Docker 管理存储位置匿名卷-v /var/lib/mysql不推荐删容器后找不到数据宿主机路径必须是绝对路径不能用相对路径。推荐有状态服务必须加数据库、文件存储无状态服务不用加。6. 环境变量-edockerrun-eMYSQL_ROOT_PASSWORDroot-eMYSQL_DATABASEblog mysql:8.0默认行为不加-e镜像使用内置默认值。不同镜像差异很大MySQL没有默认密码不设直接启动失败Redis能启动但无密码保护安全风险Nginx不需要任何变量就能跑什么时候不用加镜像默认值刚好满足需求。比如 Nginx 的默认配置就能提供静态文件。什么时候必须加默认值不满足需求时。最常见的就是数据库密码、连接地址、服务端口这些。怎么加更好敏感信息密码、密钥用env_file单独存放不要直接写在docker-compose.yml里环境变量只在首次启动时生效。数据已初始化后再改不会自动生效不确定有哪些可用变量去 Docker Hub 搜镜像名页面上的 Environment Variables 章节全列出来了推荐数据库等必须设的镜像必加简单服务Nginx 等不加也能跑就不加。7. 资源限制dockerrun--memory512m--cpus2...默认行为不加限制容器可以无上限使用宿主机所有 CPU 和内存。一个容器写了个死循环整台机器卡死。什么时候不用加本地开发调试。你一个人用不会跑死循环多给点资源跑得快。什么时候必须加生产环境。多个容器共享一台机器不设限等于坐等事故——一个服务有问题就能拖垮所有其他服务。怎么加更好总内存给宿主机留 20%其余按服务重要程度分配数据库多给点内存前端静态服务给 128MB 就够--memory-swap控制 swap 使用量生产环境一般设为 memory 的 1.5 倍推荐本地开发不用加上了生产/服务器必须加。第三部分容器管理8. 生命周期docker start mysql 启动已存在的容器 docker stop mysql 停止 docker restart mysql 重启 docker rm mysql 删除容器必须先 stop docker ps 看正在跑的容器 docker ps -a 看所有容器包括停掉的停止不是删除。停了还能再启删了就真没了。9. 进入容器exec容器跑起来了想进去看看内部dockerexec-itmysqlbash进去后就是一个微型 Linux 终端可以ls、cat、mysql -uroot -p。用完exit退出。注意Alpine 系镜像标签带alpine不带bash只有sh。进去的命令是dockerexec-itredissh# alpine 镜像用 sh 而不是 bash不确定镜像有没有 bash先用sh试试大多数镜像都有。10. 查看日志logs容器出错起不来第一件事看日志dockerlogs mysql# 看全部dockerlogs-fmysql# 实时滚动CtrlC 退出dockerlogs--tail50mysql# 只看最近 50 行默认行为日志用json-file驱动输出到 Docker 内部文件。没有大小限制没有自动轮转——一个容器长期运行日志能撑爆磁盘。生产环境建议配日志上限dockerrun --log-opt max-size10m --log-opt max-file3...单文件最大 10MB最多保留 3 个文件超出自动滚动删除。推荐本地开发不管生产环境必须配日志轮转。11. 清理命令Docker 不会自动清垃圾用久了能吃掉几十 GBdockersystem prune# 清理停止的容器、未用的网络、悬空镜像dockervolume prune# 清理未挂载的数据卷dockerbuilder prune# 清理构建缓存默认行为停止的容器、没打标签的镜像、孤儿数据卷、构建缓存——全都不自动清理永远堆积。定期手动prune是唯一回收空间的办法。docker system prune -a可以一次性清理所有未用资源包括有标签但没被容器使用的镜像。推荐每个月跑一次docker system prune磁盘告急时跑-a。第四部分编排与组合12. 容器间网络dockernetwork create blog-netdockerrun--networkblog-net--namemysql...dockerrun--networkblog-net--nameblog-server...默认行为不加--network容器被挂到 Docker 默认的bridge网络。这个默认网络不支持 DNS 解析——容器之间只能通过 IP 地址通信不能用容器名。也就是说你得先docker inspect mysql | grep IPAddress查出容器的 IP再拿这个 IP 去连接。容器重启后 IP 会变所以基本上没法用。什么时候默认够用只跑单个容器不需要容器间通信。什么时候必须加自定义网络两个及以上容器需要互访。怎么加更好用docker-compose——网络自动创建详见第 15 节手动docker run的话docker network create blog-net然后容器加--network blog-net限制不同网络之间默认隔离。一个容器可以加入多个网络docker network connect但通常不需要。本地开发场景区分关键看谁连谁——是宿主机上的 IDEA 连容器还是容器之间互访场景需要自定义网络为什么IDEA → MySQL 容器❌ 不需要localhost:端口走端口映射blog-server 容器 → MySQL 容器✅ 必须容器间互访需要容器名 DNS浏览器 → blog-ui 容器❌ 不需要localhost:80走端口映射判断方法连接方在宿主机上IDEA、浏览器→ 用localhost:端口默认 bridge 即可。连接方在容器里blog-server→ 必须自定义网络。推荐用 docker-compose网络自动创建什么都不用管。手动 docker run 且容器需要互访时必须建自定义网络。13. 重启策略默认行为不加即--restart no。容器挂了就挂了Docker 不会自动重启它。Docker Desktop 重启后所有容器全部停掉需要手动docker start。什么时候不用加本地开发调试。容器挂了你要看错误信息、改配置自动重启反而干扰排查。什么时候必须加生产环境。服务器半夜自动更新重启不加的话第二天早上所有服务全停。四个值怎么选值行为适合no默认不自动重启本地开发、一次性任务always挂了自启Docker 启动自启核心服务nginx 等unless-stopped和 always 一样但手动 stop 后不自动启动数据库等需要手动停机维护的服务on-failure:5异常退出才重启最多 5 次任务型容器正常退出不应该重启推荐本地开发不加生产环境统一用unless-stopped。14. 健康检查Health Check# 注意Alpine 镜像不带 curl需先 RUN apk add --no-cache curl HEALTHCHECK --interval30s --timeout5s --retries3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1默认行为不加 HEALTHCHECKDocker 只看主进程 PID 是否存活——MySQL 进程跑起来了但初始化还没完成Docker 就认为正常。这就是为什么容器状态是running但你连不上。什么时候不用加服务启动即就绪比如 Nginx 几乎没有初始化延迟或者不依赖健康状态来做启动顺序控制。什么时候必须加数据库等初始化较慢的服务MySQL 首次启动要几十秒需要控制启动顺序blog-server 必须等 MySQL 就绪才能启动怎么加更好interval别太短至少 10s否则健康检查本身消耗资源timeout别太短至少 5s数据库响应慢一点就被误判为 unhealthydocker-compose 里的关键用法depends_on:mysql:condition:service_healthy# 等健康检查通过才启当前服务推荐数据库和初始化慢的服务必须加简单服务Nginx 等可不加。15. docker-compose把所有docker run的参数端口、挂载、变量、网络声明式写成一个文件# 注意端口和路径是示例实际项目按需改如 3307:3306 避免冲突services:mysql:image:mysql:8.0ports:[3306:3306]volumes:[D:/data/mysql:/var/lib/mysql]environment:MYSQL_ROOT_PASSWORD:rootMYSQL_DATABASE:blogredis:image:redis:7-alpineports:[6379:6379]docker compose up -d所有服务全起。什么时候不用 compose只跑一个容器一条docker run就够。什么时候必须用 compose一个项目有 2 个及以上的服务。你的 blog 项目有 MySQL Redis blog-server blog-ui 4 个服务。Compose 默认帮你做了三件事自动建网络名叫目录名_defaultservice 名 DNS 域名。DB_HOST: mysql能工作就是因为它自动命名容器目录名-service名-1不用手动--name自动管理启动顺序depends_on保证依赖先启动常用命令dockercompose up-d# 启动所有服务dockercompose down# 停止并删除所有容器网络dockercompose stop / restart / start# 停止/重启/恢复dockercomposeps# 只看当前项目容器dockercompose logs-f# 所有容器日志一起看dockercompose-f指定文件.yml up-d# 用指定 yml 文件启动dockercompose up-d--build# 重新构建镜像再启动dockercompose up-d--force-recreate--build# 强制重建所有容器改了大配置用这个改 yml 后重跑up -d没变的服务不动变了的重建。不变的内容跑第二遍不会重启容器。build:vsimage:—— 什么时候用哪个compose 里定义服务来源有两种写法# 方式一build —— 从 Dockerfile 现场编译blog-server:build:./blog-server# 告诉 compose去这个目录找 Dockerfile 编译# 方式二image —— 用现成的镜像blog-server:image:blog-server:latest# 告诉 compose镜像已经存在本地了直接用两者本质区别build:image:镜像从哪来每次从源码编译本地已有或从仓库拉取改代码后docker compose up -d --build先docker build再docker compose up -d需要源码是否适合场景频繁改代码的本地开发服务器部署、CI/CD、镜像仓库拉取build:的价值开发体验一天改几十次代码的时候docker compose up -d --build一条命令重建重启少打一次docker build迭代快。这就是它存在的理由——语法糖给本地开发省一个命令。build:的坑一个 compose 文件本地和服务器不通用compose 文件里有build:传到服务器上跑docker compose up -dDocker 找不到源码和 Dockerfile直接报错。所以用build:的话要维护两套 compose本地版和服务器版或者每次部署前手动改。大团队怎么做分文件docker-compose.yml # 共享配置端口、网络、环境变量 docker-compose.override.yml # 本地覆盖加 build:git ignore docker-compose.prod.yml # 生产覆盖加 image:docker compose up -d自动合并docker-compose.ymldocker-compose.override.yml本地有build:服务器上没有 override 只有image:各取所需。个人项目建议统一用image:个人项目不值得搞三份 compose 文件。统一用image:改代码时多打一行docker build换来的是本地和服务器同一份 compose、不用记规则、不会部署翻车。推荐单容器不用 compose两个及以上服务必用 compose。个人项目统一用image:不纠结build:。第五部分构建优化16. 分层缓存镜像是一层层叠上去的每条 Dockerfile 指令生成一层FROM maven:3.9-eclipse-temurin-21 ← 层1Maven JDK COPY pom.xml . ← 层2依赖描述 RUN mvn dependency:resolve ← 层3下载依赖 COPY src . ← 层4源码 RUN mvn package ← 层5编译没变的层会复用缓存下次构建只重建变化的层。你改了一行源码只重跑层4和5秒级完成。如果缓存导致问题比如依赖更新了但缓存没刷新加--no-cache强制全量重建dockerbuild --no-cache-tblog-server:latest.17. 多阶段构建单阶段构建会把 JDK Maven 源码全打进最终镜像体积 800MB 起。多阶段把编译和运行分开# 阶段1编译只用来构建不进入最终镜像 FROM maven:3.9-eclipse-temurin-21 AS builder COPY . . RUN mvn package -DskipTests # 阶段2运行最终镜像只包含这个阶段 FROM eclipse-temurin:21-jre-alpine # 单模块项目target/*.jar多模块项目启动模块/target/*.jar COPY --frombuilder target/*.jar app.jar CMD [java, -jar, app.jar]编译工具只留在阶段1最终镜像只有 JRE jar体积砍到 200MB。什么时候不用项目不需要自己编译比如直接运行别人给的 jar 包一个FROMCOPYCMD就完事。什么时候必须用任何需要编译步骤的项目Java/Maven、前端/Node 等。推荐只要 Dockerfile 里有编译步骤就用多阶段构建。18. .dockerignore和.gitignore同理打包镜像时排除不要的文件node_modules .git target *.log默认行为不加.dockerignore项目目录下所有文件包括node_modules几万个文件、target几百 MB 构建产物、.git目录全部发给 Docker 构建引擎。构建又慢又臃肿。什么时候可以不加项目极小只有一个 jar 文件和一个 Dockerfile。其他情况一律加。什么时候必须加任何前端项目node_modules巨大、任何 Java 项目target巨大。推荐每个项目都加养成习惯。速查总表#概念如果不配/不加配了以后关键参数1镜像———2容器———3Dockerfile没有镜像可用构建出自定义镜像-t必须加否则随机 ID4端口映射容器完全隔离外面访问不到localhost:端口可访问-p 宿主机:容器5数据挂载删容器数据跟着销毁数据持久化到宿主机-v 宿主机:容器6环境变量MySQL 等镜像会启动失败按变量初始化服务-e KEYVALUE7资源限制无上限能吃满宿主机限制 CPU/内存--memory--cpus8生命周期——startstoprm9进入容器—进去排查内部状态alpine 镜像用sh不是bash10日志无大小限制可能撑爆磁盘可配轮转上限--log-opt max-size11清理垃圾永远堆积吃掉几十 GB手动prune回收system prune -a12容器间网络默认 bridge 只能用 IP容器间互访需自定义网络容器名即 DNS 域名--network宿主机连容器用localhost:端口容器互访用容器名13重启策略不自动重启Docker 重启后全停开机自启、挂了自愈--restart unless-stopped14健康检查只看进程死活不关心服务是否就绪真实验证可用状态condition: service_healthy15compose手动敲 N 条docker run一键全家桶启动compose up -d16分层缓存—未改层自动复用秒级构建--no-cache可强制全量重建17多阶段构建编译工具和源码全打进镜像体积臃肿最终镜像只含运行环境AS builderCOPY --from18dockerignore整个项目目录发给构建引擎又慢又臃肿排除无用文件构建飞快.dockerignore