Docker - 核心概念速通,镜像、容器、仓库一次弄懂

👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Docker这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!
文章目录
- Docker - 核心概念速通,镜像、容器、仓库一次弄懂 🐳
Docker - 核心概念速通,镜像、容器、仓库一次弄懂 🐳
“Docker 不是虚拟机,但它让部署像搭积木一样简单。”
—— 这句话背后,藏着三个看似简单却常被混淆的核心概念:镜像(Image)、容器(Container) 和 仓库(Registry)。
本文不堆砌术语,不罗列命令,而是用 Java 开发者最熟悉的视角——从mvn clean package到java -jar app.jar,再到docker run myapp:1.2.0——层层剥开 Docker 的本质逻辑。全程配有可运行的 Java 示例、清晰的 Mermaid 流程图、真实可用的外链资源,以及关键原理的直觉化类比。读完,你将真正理解:
✅ 为什么docker build不等于“编译”,而更像“制作标准化安装包”?
✅ 为什么docker run启动的是“进程快照”,而非“操作系统副本”?
✅ 为什么docker push推送的不是代码,而是可复现的执行环境契约?
一、先问一个灵魂问题:没有 Docker,Java 应用部署有多“脆弱”? 💥
假设你正在维护一个 Spring Boot 微服务:user-service。它依赖:
- Java 17(OpenJDK)
- Maven 3.8+
- MySQL 8.0.33(含特定字符集配置)
- Redis 7.0(启用 TLS)
- 环境变量
SPRING_PROFILES_ACTIVE=prod、DB_URL=jdbc:mysql://db:3306/userdb
你本地开发一切正常 ✅
测试环境上线后报错 ❌
Caused by: java.sql.SQLException: Unknown system variable 'default_authentication_plugin'
排查发现:测试环境 MySQL 是 5.7,而你的 application.yml 中用了 8.0+ 特性。
运维同事说:“换 MySQL 版本太重,我们加个兼容层吧。”
你回复:“那我本地也得降级?CI/CD 流水线怎么改?”
……最终,大家在 Slack 里发了 47 条消息,耗时 3 小时,临时打了补丁。
⚠️ 这就是典型的 环境不一致(Environment Drift):
开发 → 测试 → 预发 → 生产,每个环节的 OS、库版本、配置路径、甚至时区都可能不同。
Java 的 “Write Once, Run Anywhere” 在 JVM 层成立,但在 JVM 之下的操作系统层,它失效了。
Docker 要解决的,正是这个“JVM 之下”的不确定性。它不替代 Java,而是为 Java 提供一个可移植、可声明、可验证的运行基座。
二、镜像(Image):一份“只读的、自包含的、可复现的执行环境说明书” 📜
2.1 镜像是什么?—— 类比 Java 的 .jar 文件 🔍
想象你写了一个极简的 Spring Boot Web 应用:
// src/main/java/com/example/hello/HelloController.java
package com.example.hello;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/api/hello")
public String hello() {
return "Hello from Dockerized Spring Boot! 🌟";
}
}
mvn clean package 后生成 target/hello-spring-boot-0.0.1-SNAPSHOT.jar。
这个 JAR 文件包含了:
- 编译好的
.class字节码 META-INF/MANIFEST.MF(声明主类、依赖等)BOOT-INF/lib/下所有依赖 JARapplication.properties配置(打包进去了)
✅ 它是一个自包含(self-contained) 的交付物:只要有 java -version >= 17,就能运行。
Docker 镜像,就是这个理念在操作系统层的延伸。
它不是一个“操作系统 ISO”,而是一份分层的、只读的、声明式的环境说明书,描述了:
- 基础操作系统(如
openjdk:17-jre-slim) - 必需的二进制依赖(如
curl,jq) - 应用文件(你的
hello-spring-boot-*.jar) - 启动命令(
ENTRYPOINT ["java", "-jar", "/app.jar"]) - 环境变量、端口暴露、工作目录等元数据
它和 JAR 一样:
🔹 不可变(Immutable):构建后内容固定,无法修改(只能 docker build 新镜像)
🔹 可传输(Transferable):可 push 到远程仓库,pull 到任意机器
🔹 可验证(Verifiable):通过 sha256 摘要唯一标识,确保“所见即所得”
✅ 关键认知:镜像 ≠ 虚拟机镜像。VM 镜像包含完整内核 + 用户空间;Docker 镜像只包含用户空间(rootfs),共享宿主机内核。轻量百倍,启动毫秒级。
2.2 构建一个 Java 镜像:Dockerfile 实战 🛠️
创建项目根目录下的 Dockerfile:
# 第一行:指定基础镜像(官方 OpenJDK 17 运行时,Debian Slim 版)
FROM openjdk:17-jre-slim
# 维护者信息(非必需,但推荐)
LABEL maintainer="dev@mycompany.com"
# 创建非 root 用户提升安全性(重要!)
RUN groupadd -g 1001 -f appgroup && useradd -s /bin/bash -u 1001 -g appgroup appuser
# 设置工作目录
WORKDIR /app
# 复制本地 JAR 到镜像中(注意:使用相对路径,避免 COPY 整个 target 目录)
COPY target/hello-spring-boot-0.0.1-SNAPSHOT.jar app.jar
# 暴露应用端口(仅文档作用,实际防火墙需另行配置)
EXPOSE 8080
# 设置运行用户(安全最佳实践)
USER appuser
# 定义启动命令(ENTRYPOINT 更适合封装,CMD 可被覆盖)
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
💡 解析关键指令:
| 指令 | 作用 | 类比 Java |
|---|---|---|
FROM |
指定父镜像,构成镜像的第一层 | extends 父类(但这里是组合,非继承) |
COPY |
将本地文件复制进镜像文件系统 | maven-assembly-plugin 打包资源 |
EXPOSE |
声明容器监听端口(不自动映射) | @SpringBootApplication 上的 @EnableAutoConfiguration(声明能力,非强制生效) |
ENTRYPOINT |
容器启动时执行的默认命令(不易被覆盖) | public static void main(String[]) —— 程序入口点 |
⚠️ 注意:不要写
RUN java -jar app.jar!那是运行时行为,镜像构建阶段应只做“准备”,不“执行”。
构建镜像(确保已 mvn clean package):
docker build -t hello-spring-boot:1.0.0 .
输出类似:
[+] Building 12.5s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 366B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/openjdk:17-jre-slim 1.2s
=> [1/5] FROM docker.io/library/openjdk:17-jre-slim@sha256:... 10.1s
=> => resolve docker.io/library/openjdk:17-jre-slim@sha256:... 0.0s
=> [2/5] RUN groupadd -g 1001 -f appgroup && useradd ... 0.5s
=> [3/5] WORKDIR /app 0.0s
=> [4/5] COPY target/hello-spring-boot-0.0.1-SNAPSHOT.jar app.jar 0.1s
=> [5/5] EXPOSE 8080 0.0s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1 0.0s
=> => naming to docker.io/library/hello-spring-boot:1.0.0 0.0s
✅ 镜像构建完成!现在它是一个静态的、可分发的实体。
2.3 镜像的分层存储机制:为什么 docker build 很快? 🧱
Docker 镜像由**只读层(Read-Only Layers)**叠加而成。每条 Dockerfile 指令(FROM, RUN, COPY, EXPOSE)都会生成一层。这些层被缓存并复用。
Mermaid 图表直观展示分层结构:
✅ 分层优势:
- 高效复用:若只改
app.jar(COPY指令),只有 Layer 4 及之后层重建,前面 3 层直接复用缓存。 - 节省空间:多个镜像共享相同基础层(如
openjdk:17-jre-slim),物理磁盘只存一份。 - 安全审计:可逐层检查
RUN apt-get update是否引入风险包。
🔗 想深入理解分层?访问 Docker 官方文档:Images and layers(权威、实时更新、无登录墙)
三、容器(Container):镜像的“运行时实例” —— 一个受控的进程沙盒 🧪
3.1 容器是什么?—— 类比 java -jar app.jar 的进程 🔗
当你执行:
java -jar hello-spring-boot-0.0.1-SNAPSHOT.jar
发生了什么?
- JVM 启动一个
java进程(PID=1234) - 加载
app.jar和所有依赖到内存 - 绑定
localhost:8080(或失败,因端口被占) - 进程生命周期:从
main()开始,到System.exit()或异常终止
Docker 容器,就是这个过程的增强版:
它把 java -jar app.jar 这个命令,放到一个隔离的、资源受限的、网络独立的 Linux 命名空间(Namespace)和控制组(Cgroup) 中运行。
✅ 容器 = 镜像 + 可写层(Container Layer) + 隔离运行时环境(Namespaces + Cgroups)
- 可写层(Top Layer):容器启动时,在镜像最上层叠加一个可读写层。所有运行时改动(如
touch /tmp/log.txt,echo "ok" > status)都发生在此层。容器停止后,此层默认丢弃(除非用-v挂载卷)。 - 命名空间(Namespaces):提供视图隔离
pid:进程 PID 隔离 → 容器内ps aux只看到自己的进程net:网络栈隔离 → 容器有自己lo,eth0,IP 独立mnt:文件系统挂载点隔离 →df -h只显示容器视角的磁盘
- 控制组(Cgroups):提供资源限制
--memory=512m:限制最大内存使用--cpus=1.5:限制最多使用 1.5 个 CPU 核心--pids-limit=100:限制最多 100 个进程
3.2 启动一个容器:docker run 的全貌 🚀
运行刚构建的镜像:
docker run \
--name hello-app \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_URL=jdbc:mysql://mysql-host:3306/userdb \
--rm \
hello-spring-boot:1.0.0
参数详解:
| 参数 | 作用 | 类比 Java |
|---|---|---|
--name hello-app |
为容器指定易记名称 | Thread.currentThread().setName("hello-worker") |
-p 8080:8080 |
将宿主机 8080 端口映射到容器 8080 端口 | ServerSocket server = new ServerSocket(8080)(但绑定的是容器网络) |
-e KEY=VALUE |
向容器注入环境变量 | System.setProperty("spring.profiles.active", "prod") |
--rm |
容器退出后自动删除(清理可写层) | try { ... } finally { cleanup(); }(自动资源回收) |
✅ 启动后,你会看到 Spring Boot 的标准日志:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0)
2024-04-10T10:22:33.123Z INFO 1 --- [ main] c.e.h.HelloSpringBootApplication : Started HelloSpringBootApplication in 2.1 seconds (process running for 2.5)
此时,打开浏览器访问 http://localhost:8080/api/hello → 显示 "Hello from Dockerized Spring Boot! 🌟" ✅
3.3 容器 vs 虚拟机:一张图看懂本质差异 🆚
核心结论:
| 维度 | 虚拟机(VM) | Docker 容器 |
|---|---|---|
| 启动速度 | 秒级(启动 Guest OS) | 毫秒级(启动进程) |
| 资源占用 | GB 级(完整 OS 内存 + 磁盘) | MB 级(仅应用 + 运行时) |
| 隔离强度 | 强(硬件级,完全独立内核) | 中(OS 级,共享宿主机内核) |
| 适用场景 | 运行不同 OS(Windows/Linux)、强安全隔离需求 | 微服务、CI/CD、开发环境一致性 |
🔗 对隔离原理好奇?推荐阅读 Red Hat 官方文章:How containers work(图文并茂,原理清晰)
3.4 容器生命周期管理:不只是 run 和 stop 🔄
容器是短暂的(Ephemeral)。掌握其状态流转至关重要:
常用命令实战:
# 查看正在运行的容器
docker ps
# 查看所有容器(包括已退出)
docker ps -a
# 查看容器日志(实时跟踪)
docker logs -f hello-app
# 进入容器执行命令(调试用,生产慎用)
docker exec -it hello-app /bin/sh
# 发送信号优雅停止(触发 Spring Boot 的 Shutdown Hook)
docker stop hello-app
# 强制杀死(类似 kill -9)
docker kill hello-app
# 删除已停止容器
docker rm hello-app
# 删除镜像(需先删容器)
docker rmi hello-spring-boot:1.0.0
💡 生产最佳实践:
- 永远使用
--rm或明确docker rm清理,避免磁盘被僵尸容器占满。 - 用
HEALTHCHECK定义健康探针,让编排工具(如 Kubernetes)知道何时重启。 - 容器内永不运行多个主进程(如
nginx + java),应拆分为多个容器(nginx-container+app-container)。
四、仓库(Registry):镜像的“Maven Central” —— 团队协作的枢纽 🌐
4.1 仓库是什么?—— 类比 Maven 仓库的核心定位 📦
Maven 项目 pom.xml 中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
Maven 如何找到这个 JAR?
→ 查询 https://repo.maven.apache.org/maven2/(中央仓库)
→ 下载 org/springframework/boot/spring-boot-starter-web/3.2.0/...jar
→ 本地缓存 ~/.m2/repository/
Docker Registry 就是这个逻辑的镜像版:
它是一个 HTTP 服务,用于:
- 存储 镜像的分层数据(tar 包 + JSON 元数据)
- 索引 镜像标签(tag),如
hello-spring-boot:1.0.0,hello-spring-boot:latest - 认证 用户权限(谁可以
push?谁可以pull?) - 分发 镜像到全球任何一台装了 Docker 的机器
✅ 仓库 ≠ Docker Hub。Docker Hub 是一个公共 Registry 实例(由 Docker Inc. 运营),就像 Maven Central 是 Apache 运营的公共 Maven 仓库。你完全可以搭建私有 Registry(如 Harbor、AWS ECR、阿里云 ACR)。
4.2 使用 Docker Hub:公共镜像的获取与推送 🚪
▶ 获取公共镜像(docker pull)
# 拉取官方 OpenJDK 镜像(无需指定 registry,缺省为 Docker Hub)
docker pull openjdk:17-jre-slim
# 拉取 Nginx(Web 服务器,常作反向代理)
docker pull nginx:alpine
# 拉取 MySQL(数据库)
docker pull mysql:8.0
这些镜像都托管在 Docker Hub —— 全球最大的公共容器镜像仓库。你可以在这里搜索、查看 Dockerfile、阅读文档。
🔗 访问 Docker Hub 官网(可直接搜索
openjdk,查看官方镜像详情页)
▶ 推送私有镜像(docker push)
要推送,需先登录:
docker login
# 输入 Docker ID 和密码(非邮箱!)
然后给镜像打上符合 username/repository:tag 格式的标签:
# 查看当前镜像 ID
docker images | grep hello-spring-boot
# 假设 IMAGE ID 是 9a8b7c6d5e4f
docker tag 9a8b7c6d5e4f your-docker-id/hello-spring-boot:1.0.0
# 推送到 Docker Hub
docker push your-docker-id/hello-spring-boot:1.0.0
✅ 推送成功后,任何人只要执行:
docker pull your-docker-id/hello-spring-boot:1.0.0
就能获得完全一致的镜像——无论他在东京、法兰克福还是圣保罗。这就是“一次构建,处处运行”的基石。
4.3 私有仓库实践:为什么企业必须用它? 🔐
Docker Hub 有明显局限:
- ❌ 免费账户仅支持 1 个私有仓库
- ❌ 无细粒度权限控制(如:研发组只能
pull,运维组可push) - ❌ 无漏洞扫描(CVE 检测)
- ❌ 网络延迟高(国内访问慢)
企业级方案:Harbor(CNCF 毕业项目,开源免费)
- ✅ Web UI 管理镜像、项目、用户
- ✅ RBAC 权限模型(
project-admin,developer,guest) - ✅ 集成 Trivy 扫描镜像漏洞
- ✅ 支持 Helm Chart 仓库(统一管理容器和 K8s 应用)
部署 Harbor 只需几行命令(基于 Docker Compose):
# 下载离线安装包(官网提供)
wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
# 解压并编辑 harbor.yml(配置域名、证书、存储路径)
tar xvf harbor-offline-installer-v2.10.0.tgz
cd harbor
vi harbor.yml # 设置 hostname: harbor.mycompany.com
# 安装(自动拉取所需镜像并启动)
sudo ./install.sh
🔗 获取 Harbor 最新安装指南:Harbor 官方文档
4.4 镜像标签(Tag)的语义化实践:别再用 latest ❌
latest 是 Docker 默认标签,但它是个“移动目标”:
docker pull nginx # 等价于 docker pull nginx:latest
# 下次 pull,可能已是全新大版本(如 1.25 → 1.26),导致兼容性问题!
✅ 推荐策略(参考 Semantic Versioning 2.0):
| 标签类型 | 示例 | 用途 | 安全性 |
|---|---|---|---|
| 精确版本 | hello-spring-boot:1.0.0 |
生产环境部署 | ✅ 最高(不可变) |
| 次要版本 | hello-spring-boot:1.0 |
CI/CD 流水线自动构建 | ⚠️ 中(1.0.x 自动更新) |
| 主要版本 | hello-spring-boot:1 |
基础设施测试(如兼容性验证) | ⚠️ 低(1.x.y 自动更新) |
| Git Commit | hello-spring-boot:abc1234 |
追溯构建来源 | ✅ 高(唯一) |
| 时间戳 | hello-spring-boot:20240410-1422 |
日常构建快照 | ✅ 高(唯一) |
在 Dockerfile 中,永远显式指定基础镜像版本:
# ✅ GOOD:锁定 OpenJDK 版本
FROM openjdk:17.0.2-jre-slim
# ❌ BAD:使用 latest,下次构建可能失败
# FROM openjdk:latest
五、Java 开发者必知:Docker 与 Spring Boot 的深度协同 🌱
5.1 Spring Boot 3.x 原生支持容器化:jlink 与 layers.idx 🎯
Spring Boot 2.3+ 引入 Buildpacks 和 Layered JAR,大幅优化容器体验。
▶ 方案一:使用 Buildpacks(零 Dockerfile)
Maven 插件自动构建 OCI 镜像:
<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base</builder>
<name>your-registry.com/myapp:${project.version}</name>
</image>
</configuration>
</plugin>
</plugins>
</build>
执行:
mvn spring-boot:build-image
✅ 自动生成优化镜像(基于 distroless,无 Shell,更小更安全),无需手写 Dockerfile。
▶ 方案二:分层 JAR(Layered JAR)加速构建
Spring Boot 2.3+ 将 app.jar 拆分为多层:
unzip -l target/hello-spring-boot-0.0.1-SNAPSHOT.jar | head -20
输出含:
META-INF/
META-INF/MANIFEST.MF
BOOT-INF/
BOOT-INF/classes/
BOOT-INF/lib/ ← 依赖层(变化少,可缓存)
BOOT-INF/layers.idx ← 层定义文件
layers.idx 内容示例:
- "dependencies":
- "BOOT-INF/lib/"
- "spring-boot-loader":
- "org/"
- "snapshot-dependencies":
- "application":
- "BOOT-INF/classes/"
- "BOOT-INF/resources/"
配合 Dockerfile 利用分层:
# 利用 Spring Boot 分层,实现极速 rebuild
FROM eclipse/jetty:11-jre17-slim
# 复制分层 JAR
ARG JAR_FILE=target/hello-spring-boot-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
# 提取分层(关键!)
RUN java -Djarmode=layertools -jar app.jar extract
# 按层复制(依赖层复用率高)
FROM eclipse/jetty:11-jre17-slim
COPY --from=0 dependencies/ ./
COPY --from=0 spring-boot-loader/ ./
COPY --from=0 snapshot-dependencies/ ./
COPY --from=0 application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
✅ 效果:当只改业务代码(application 层),docker build 仅需复制最后一层,秒级完成。
5.2 容器内 Java 应用调优:别让 JVM “瞎猜” 🧠
在容器中,JVM 默认行为可能出错:
- ❌
Runtime.getRuntime().availableProcessors()返回宿主机 CPU 数,而非容器限制值 - ❌
-Xmx未设置时,JVM 用宿主机内存的 1/4,远超容器--memory=512m限制 → OOM Kill!
✅ 正确做法(Spring Boot 2.2+ 自动适配):
# application.yml
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
# JVM 启动参数(推荐写在 ENTRYPOINT 或 docker run -e JAVA_TOOL_OPTIONS)
JAVA_TOOL_OPTIONS: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
-XX:+UseContainerSupport:启用容器感知(JDK 10+ 默认开启)-XX:MaxRAMPercentage=75.0:JVM 堆最大为容器内存限制的 75%
验证是否生效:
docker run --rm -m 512m hello-spring-boot:1.0.0 java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
# 输出应为约 384MB (512 * 0.75),而非数 GB
5.3 容器网络实战:Spring Boot 连接 MySQL 和 Redis 🌐
典型微服务架构:
[Spring Boot App] <--(HTTP)--> [Nginx]
↓
[MySQL] [Redis] [RabbitMQ]
在容器中,服务间通信靠 Docker 网络,而非 localhost。
▶ 步骤 1:创建自定义网络(推荐)
docker network create myapp-network
▶ 步骤 2:启动 MySQL(带初始化脚本)
docker run -d \
--name mysql-db \
--network myapp-network \
-e MYSQL_ROOT_PASSWORD=root123 \
-e MYSQL_DATABASE=userdb \
-v $(pwd)/init.sql:/docker-entrypoint-initdb.d/init.sql \
-p 3306:3306 \
mysql:8.0
▶ 步骤 3:启动 Redis
docker run -d \
--name redis-cache \
--network myapp-network \
-p 6379:6379 \
redis:7-alpine
▶ 步骤 4:启动 Spring Boot App(使用服务名作为 host)
docker run -d \
--name hello-app \
--network myapp-network \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=docker \
-e SPRING_DATASOURCE_URL=jdbc:mysql://mysql-db:3306/userdb \
-e SPRING_REDIS_HOST=redis-cache \
hello-spring-boot:1.0.0
✅ 关键点:mysql-db 和 redis-cache 是容器名,Docker 内置 DNS 会将其解析为对应 IP。无需硬编码 IP!
🔗 学习 Docker 网络原理:Docker 官方网络教程
六、避坑指南:Java 工程师踩过的 5 个经典 Docker 坑 🚧
❌ 坑 1:在容器里用 localhost 连接其他容器
现象:Connection refused
原因:localhost 指向容器自身,而非宿主机或其他容器
解法:用 --network + 容器名(如 mysql-db),或用 host.docker.internal(Mac/Win,Linux 需 --add-host=host.docker.internal:host-gateway)
❌ 坑 2:docker build 时 mvn clean package 太慢
现象:每次构建都重新下载 Maven 依赖
解法:利用 Docker BuildKit 的 --mount=type=cache:
# 开启 BuildKit:export DOCKER_BUILDKIT=1
FROM maven:3.8-openjdk-17 AS builder
WORKDIR /app
COPY pom.xml .
# 缓存 ~/.m2/repository
RUN --mount=type=cache,target=/root/.m2/repository \
mvn dependency:go-offline
COPY . .
RUN mvn clean package -DskipTests
FROM openjdk:17-jre-slim
COPY --from=builder /app/target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
❌ 坑 3:日志不输出到 docker logs
现象:docker logs hello-app 为空
原因:Spring Boot 默认将日志写入 ./logs/ 文件,而非 stdout
解法:在 application.yml 中配置:
logging:
level:
root: INFO
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: "logs/app.log" # 仍可写文件,但 console 必须开启
❌ 坑 4:容器时间与宿主机不同步
现象:JWT Token 报 Invalid JWT timestamp
原因:容器内时钟未同步
解法:启动时挂载宿主机时间:
docker run -v /etc/localtime:/etc/localtime:ro ...
❌ 坑 5:java.io.tmpdir 空间不足
现象:java.lang.InternalError: XXX 或 PDF 生成失败
原因:Docker 默认 /tmp 很小(64MB)
解法:启动时指定更大 tmpdir:
docker run -e JAVA_TOOL_OPTIONS="-Djava.io.tmpdir=/tmp-large" \
-v /tmp-large:/tmp-large ...
七、总结:一张图收束全部核心概念 🧩
✅ 终极一句话总结:
镜像是“Java 的 .jar”,容器是“java -jar 的进程”,仓库是“Maven Central”。
Docker 没有发明新概念,它只是把 Java 工程师早已熟稔的“封装-分发-运行”范式,完美迁移到了操作系统层。
八、下一步行动清单:学完就用 🚀
- ✅ 立刻实践:把你当前的 Spring Boot 项目,按本文流程写一个
Dockerfile,docker build并docker run起来。 - ✅ 升级构建:尝试
mvn spring-boot:build-image,对比传统Dockerfile的体积与构建速度。 - ✅ 接入仓库:注册 Docker Hub,
docker push你的镜像,并让同事docker pull验证。 - ✅ 学习编排:用
docker-compose.yml编排 MySQL + Redis + 你的 App,一键启动整套环境。 - ✅ 深入原理:阅读 Linux Namespaces 和 Cgroups v2 官方文档,理解底层魔法。
🌟 记住:Docker 的价值不在技术本身,而在于它消除了环境摩擦,让开发者专注业务逻辑。当你不再为“在我机器上是好的”而争论,真正的高效协作才开始。
祝你 Docker 之旅顺畅,代码世界澄澈如初 🐳✨
—— 写于一个容器正在后台安静运行的清晨
🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨
更多推荐



所有评论(0)