Docker 学习篇(四)| Docker的 核心概念
2026/5/6 22:52:29 网站建设 项目流程

Docker 学习篇(四)| Docker的 核心概念

    • 第一部分:基础三要素
      • 1. 镜像(Image)
      • 2. 容器(Container)
      • 3. Dockerfile
    • 第二部分:启动容器的四件套
      • 4. 端口映射(-p)
      • 5. 数据挂载(-v)
      • 6. 环境变量(-e)
      • 7. 资源限制
    • 第三部分:容器管理
      • 8. 生命周期
      • 9. 进入容器(exec)
      • 10. 查看日志(logs)
      • 11. 清理命令
    • 第四部分:编排与组合
      • 12. 容器间网络
      • 13. 重启策略
      • 14. 健康检查(Health Check)
      • 15. docker-compose
        • `build:` 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 引用(FROMdocker 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 --from=builder target/*.jar app.jar # EXPOSE = 声明端口 EXPOSE 8080 # CMD = 启动命令 CMD ["java", "-jar", "app.jar"]

编译也进了 Docker,你不需要手动mvn package

流程:docker build → 镜像(一条命令,编译+打包全自动)

最终镜像只有 JRE + jar,约 250MB。比方式一多了AS builder+COPY --from=builder两行,省了你手动打包步骤。

为什么方式二比方式一小?省的不是 jar,是底座。

两个方式最终都只拿了一个 jar:

方式一 底座:eclipse-temurin:21-jdk-slim ← JDK ≈ 350MB 方式二 底座:eclipse-temurin:21-jre-alpine ← JRE ≈ 180MB

JDK vs JRE:

JDK(Java Development Kit) ├── JRE(Java Runtime Environment)← 运行时,180MB ├── javac 编译器 ← 源码 → .class ├── jdb 调试器 ├── javadoc 文档生成器 └── 其他开发工具... 总约 350MB
  • 你在 IDEA 里写代码、改代码、编译、调试 → 必须用 JDK
  • jar 部署到服务器上,只跑不编译 → JRE 就够了

方式一不分阶段,只能用 JDK 当底座(约 350MB);方式二把编译交给阶段1(Maven 镜像),阶段2 换 JRE 底座(约 180MB)。jar 没变,底座瘦了 170MB。


方式三:Buildpacks(零配置)

不需要 Dockerfile,一行命令:

mvn spring-boot:build-image

全自动黑盒生成镜像,约 500MB。上手最快,但命名不灵活、构建过程黑盒、出问题难排查。


三种方式对比:

方式一 基础方式二 多阶段方式三 Buildpacks
需要 Dockerfile需要需要不需要
需要手动 mvn package需要不需要不需要
最终镜像体积~400MB~250MB~500MB
相当于传统方式吗✅ 流程一样传统方式的自动化版全自动黑盒
推荐度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Dockerfile 命名规则:

  • 文件名固定叫Dockerfile(D 大写,无后缀)
  • 放在项目根目录
  • docker build默认在当前目录找这个文件
  • 非要用别的名字也行:docker build -f 其他文件名 .,但没人这么做

构建时永远加-t起名字,否则 Docker 分配随机 ID:

dockerbuild-tblog-server:latest.# ↑ ↑# 镜像名:标签 构建上下文(Dockerfile 所在目录)

标签不写默认为latest,但它只是个标签名,不代表"最新版本"——Docker 不会自动帮你更新它。


第二部分:启动容器的四件套

4. 端口映射(-p)

dockerrun-p3306:3306 mysql:8.0# ↑ ↑# 宿主机 容器内部

默认:不加-p,外面访问不到容器。同一个 Docker 网络下的容器之间不受影响——它们走容器名就能互访,不需要端口映射。

一句话判断:

需要从"宿主机或外部"访问容器 → 加 -p 只是"容器和容器之间"通信 → 不加
场景要不要映射例子
浏览器访问前端页面✅ 必须-p 80:80
IDEA 连 MySQL 调试✅ 开发时需要-p 3306:3306
blog-server 连 MySQL❌ 不用容器名mysql:3306直接通
blog-server 连 Redis❌ 不用容器名redis:6379直接通

两个实用技巧:

  • 端口冲突了改左边:-p 3307:3306localhost:3307
  • 只让本机访问:-p 127.0.0.1:3306:3306

开发 vs 部署:

开发环境:想连什么映射什么(方便调试) MySQL ✓ Redis ✓ blog-server ✓ blog-ui ✓ 部署上线:只映射前端,其余全关 MySQL ✗ Redis ✗ blog-server ✗ blog-ui ✓ ↑ 多映射一个端口就多一扇攻击面

记住一条:容器间互访走容器名,不经过端口映射。只有外面要进来才需要开映射。


5. 数据挂载(-v)

dockerrun-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. 环境变量(-e)

dockerrun-eMYSQL_ROOT_PASSWORD=root-eMYSQL_DATABASE=blog mysql:8.0

默认行为:不加-e,镜像使用内置默认值。不同镜像差异很大:

  • MySQL:没有默认密码,不设直接启动失败
  • Redis:能启动,但无密码保护(安全风险)
  • Nginx:不需要任何变量就能跑

什么时候不用加:镜像默认值刚好满足需求。比如 Nginx 的默认配置就能提供静态文件。

什么时候必须加:默认值不满足需求时。最常见的就是数据库密码、连接地址、服务端口这些。

怎么加更好:

  • 敏感信息(密码、密钥)用env_file单独存放,不要直接写在docker-compose.yml
  • 环境变量只在首次启动时生效。数据已初始化后再改不会自动生效
  • 不确定有哪些可用变量?去 Docker Hub 搜镜像名,页面上的 Environment Variables 章节全列出来了

推荐:数据库等必须设的镜像必加,简单服务(Nginx 等)不加也能跑就不加。


7. 资源限制

dockerrun--memory="512m"--cpus="2"...

默认行为:不加限制,容器可以无上限使用宿主机所有 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 终端,可以lscatmysql -uroot -p。用完exit退出。

注意:Alpine 系镜像(标签带alpine)不带bash,只有sh。进去的命令是:

dockerexec-itredissh# alpine 镜像用 sh 而不是 bash

不确定镜像有没有 bash?先用sh试试,大多数镜像都有。


10. 查看日志(logs)

容器出错起不来,第一件事看日志:

dockerlogs mysql# 看全部dockerlogs-fmysql# 实时滚动,Ctrl+C 退出dockerlogs--tail50mysql# 只看最近 50 行

默认行为:日志用json-file驱动,输出到 Docker 内部文件。没有大小限制,没有自动轮转——一个容器长期运行日志能撑爆磁盘。

生产环境建议配日志上限:

dockerrun --log-opt max-size=10m --log-opt max-file=3...

单文件最大 10MB,最多保留 3 个文件,超出自动滚动删除。

推荐:本地开发不管,生产环境必须配日志轮转。


11. 清理命令

Docker 不会自动清垃圾,用久了能吃掉几十 GB:

dockersystem 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 --interval=30s --timeout=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1

默认行为:不加 HEALTHCHECK,Docker 只看主进程 PID 是否存活——MySQL 进程跑起来了但初始化还没完成,Docker 就认为"正常"。这就是为什么容器状态是running但你连不上。

什么时候不用加:服务启动即就绪(比如 Nginx 几乎没有初始化延迟),或者不依赖健康状态来做启动顺序控制。

什么时候必须加:

  • 数据库等初始化较慢的服务(MySQL 首次启动要几十秒)
  • 需要控制启动顺序(blog-server 必须等 MySQL 就绪才能启动)

怎么加更好:

  • interval别太短(至少 10s),否则健康检查本身消耗资源
  • timeout别太短(至少 5s),数据库响应慢一点就被误判为 unhealthy
  • docker-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 默认帮你做了三件事:

  1. 自动建网络:名叫<目录名>_default,service 名 = DNS 域名。DB_HOST: mysql能工作就是因为它
  2. 自动命名容器<目录名>-<service名>-1,不用手动--name
  3. 自动管理启动顺序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 --builddocker build,再docker compose up -d
需要源码
适合场景频繁改代码的本地开发服务器部署、CI/CD、镜像仓库拉取

build:的价值:开发体验

一天改几十次代码的时候,docker compose up -d --build一条命令重建+重启,少打一次docker build,迭代快。这就是它存在的理由——语法糖,给本地开发省一个命令

build:的坑:一个 compose 文件,本地和服务器不通用

compose 文件里有build:,传到服务器上跑docker compose up -d,Docker 找不到源码和 Dockerfile,直接报错。所以用build:的话要维护两套 compose(本地版和服务器版),或者每次部署前手动改。

大团队怎么做:分文件

docker-compose.yml # 共享配置(端口、网络、环境变量) docker-compose.override.yml # 本地覆盖(加 build:),git ignore docker-compose.prod.yml # 生产覆盖(加 image:)

docker compose up -d自动合并docker-compose.yml+docker-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 ← 层1:Maven + 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 --from=builder target/*.jar app.jar CMD ["java", "-jar", "app.jar"]

编译工具只留在阶段1,最终镜像只有 JRE + jar,体积砍到 200MB。

什么时候不用:项目不需要自己编译(比如直接运行别人给的 jar 包),一个FROM+COPY+CMD就完事。

什么时候必须用:任何需要编译步骤的项目(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必须加,否则随机 ID
4端口映射容器完全隔离,外面访问不到localhost:端口可访问-p 宿主机:容器
5数据挂载删容器数据跟着销毁数据持久化到宿主机-v 宿主机:容器
6环境变量MySQL 等镜像会启动失败按变量初始化服务-e KEY=VALUE
7资源限制无上限,能吃满宿主机限制 CPU/内存--memory--cpus
8生命周期startstoprm
9进入容器进去排查内部状态alpine 镜像用sh不是bash
10日志无大小限制,可能撑爆磁盘可配轮转上限--log-opt max-size=
11清理垃圾永远堆积,吃掉几十 GB手动prune回收system prune -a
12容器间网络默认 bridge 只能用 IP;容器间互访需自定义网络容器名即 DNS 域名--network;宿主机连容器用localhost:端口,容器互访用容器名
13重启策略不自动重启,Docker 重启后全停开机自启、挂了自愈--restart unless-stopped
14健康检查只看进程死活,不关心服务是否就绪真实验证可用状态condition: service_healthy
15compose手动敲 N 条docker run一键全家桶启动compose up -d
16分层缓存未改层自动复用,秒级构建--no-cache可强制全量重建
17多阶段构建编译工具和源码全打进镜像,体积臃肿最终镜像只含运行环境AS builderCOPY --from=
18dockerignore整个项目目录发给构建引擎,又慢又臃肿排除无用文件,构建飞快.dockerignore

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询