你以为docker-compose up -d只是启动几个容器大错特错。真正读懂docker-compose.yml的每一层含义你才能在生产环境里游刃有余——网络到底怎么隔离、依赖为什么有时失效、卷挂载有哪些坑、环境变量如何优雅替换。这篇文章以一个 Java Spring Boot 应用JAR 包 PostgreSQL 数据库的真实场景为蓝本逐行解析docker-compose.yml的所有核心字段让你从“会抄”变成“会写”。一、Compose 是什么为什么值得深度剖析Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。用一个 YAML 文件配置所有服务、网络、卷然后一行命令就能启动整个应用栈。但大多数人对它的理解停留在“照着模板改”一旦遇到容器启动顺序错乱导致应用连不上数据库环境变量没生效数据卷权限问题生产环境和开发环境配置混乱就束手无策。本文从一个Java Jar 应用 远程 PostgreSQL 数据库的场景出发把docker-compose.yml的每一个角落都翻出来讲透。二、场景案例一个典型的微服务雏形假设我们有一个Spring Boot 应用打包成app.jar它需要连接一个PostgreSQL 数据库通过 Docker 运行也可以理解为“拉取远程 PG 镜像”。我们的目标是用 Dockerfile 构建 Java 应用的镜像。用docker-compose.yml编排 Java 容器和 PostgreSQL 容器。确保 Java 容器能通过服务名postgres访问数据库。持久化 PostgreSQL 的数据避免重启丢失。三、从零构建 Java 应用镜像Dockerfile在项目根目录创建Dockerfiledockerfile# 基础镜像使用 OpenJDK 17 精简版 FROM openjdk:17-jdk-slim # 维护者信息 LABEL maintaineryournameexample.com LABEL descriptionSpring Boot App for Compose Demo # 设置工作目录 WORKDIR /app # 将宿主机的 JAR 包复制到镜像内构建时复制 COPY target/app.jar /app/app.jar # 创建非 root 用户运行安全实践 RUN addgroup --system javauser \ adduser --system --no-create-home --ingroup javauser javauser \ chown -R javauser:javauser /app USER javauser # 暴露应用端口根据你的 Spring Boot 配置比如 8080 EXPOSE 8080 # 健康检查可选用于 Compose 的 depends_on 条件 HEALTHCHECK --interval30s --timeout3s --start-period5s --retries3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # 启动命令 ENTRYPOINT [java, -jar, /app/app.jar]关键点解释COPY target/app.jar假设你在构建镜像前已经通过 Maven/Gradle 打包好 JAR 到target/目录。创建非 root 用户是安全基线防止容器内进程提权。HEALTHCHECK让 Compose 能检测应用是否真正就绪而不仅仅是容器启动。四、docker-compose.yml 完整解剖带逐行注释创建docker-compose.ymlyaml# 1. 版本声明决定支持的 Compose 特性3.8 是目前主流 version: 3.8 # 2. 定义所有服务容器 services: # ---------- PostgreSQL 服务 ---------- postgres: # 拉取远程镜像相当于从 Docker Hub 拉取 postgres:15-alpine image: postgres:15-alpine # 容器名称方便命令行操作 container_name: my-postgres # 重启策略除非手动停止否则总是重启 restart: unless-stopped # 环境变量设置数据库初始配置 environment: POSTGRES_DB: myappdb # 自动创建的数据库名 POSTGRES_USER: myuser POSTGRES_PASSWORD: secretpass PGDATA: /var/lib/postgresql/data/pgdata # 数据目录可自定义 # 卷挂载持久化数据库文件 volumes: - postgres_data:/var/lib/postgresql/data # 可选初始化脚本挂载首次启动自动执行 - ./init-scripts:/docker-entrypoint-initdb.d # 端口映射宿主机端口:容器端口可选不映射也可供其他容器访问 ports: - 5432:5432 # 网络自定义网络便于服务发现 networks: - app-network # 健康检查确认数据库真正可用 healthcheck: test: [CMD-SHELL, pg_isready -U myuser -d myappdb] interval: 10s timeout: 5s retries: 5 start_period: 10s # ---------- Java Spring Boot 应用 ---------- java-app: # 构建上下文当前目录即 Dockerfile 所在位置 build: . # 也可以直接用 image: my-java-app:latest但用 build 更灵活 container_name: my-java-app restart: unless-stopped # 依赖声明等待 postgres 服务“启动”不是就绪 depends_on: - postgres # 但 depends_on 默认只等容器状态为 running不关心健康。 # 使用 condition 可以等待健康检查通过Compose 3.8 实验特性需启用 # 实际生产更推荐用 wait-for-it 脚本此处演示 condition 语法 depends_on: postgres: condition: service_healthy # 环境变量传递给 Spring Boot 应用 environment: SPRING_PROFILES_ACTIVE: prod # 关键数据库连接 URL 使用服务名 postgres因为同网络 SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/myappdb SPRING_DATASOURCE_USERNAME: myuser SPRING_DATASOURCE_PASSWORD: secretpass # JVM 内存限制容器内生效 JAVA_OPTS: -Xmx256m -Xms128m # 端口映射将应用暴露到宿主机 ports: - 8080:8080 # 卷挂载挂载日志或配置文件可选 volumes: - ./logs:/app/logs # 如果不想每次重新构建镜像就更新 jar可以挂载 jar开发时有用 # - ./target/app.jar:/app/app.jar # 网络与 postgres 同一个网络 networks: - app-network # 资源限制防止容器耗尽宿主机资源 deploy: resources: limits: cpus: 0.5 memory: 512M reservations: memory: 256M # 3. 定义网络方便服务间使用服务名通信 networks: app-network: driver: bridge # 如果希望使用外部已有网络取消下面的注释 # external: true # name: my-custom-network # 4. 定义卷持久化数据 volumes: postgres_data: # 可以指定卷驱动和选项默认 local driver: local五、深度拆解每个字段为什么这么写5.1version—— 选对版本很重要3.8是目前广泛支持的稳定版支持depends_on的condition、deploy资源限制等。如果你用旧版2.x则没有deploy且depends_on不支持健康等待。建议新项目一律3.8或更高3.9、3.10差别不大。5.2services—— 心脏地带imagevsbuildimage: postgres:15-alpine直接从仓库拉取。build: .会从当前目录的 Dockerfile 构建镜像。如果同时指定image则构建后还会打上该镜像名。container_name—— 固定容器名不指定则自动生成项目名_服务名_序号。固定名称方便docker exec -it my-java-app bash。restart: unless-stoppedno不自动重启always总是重启包括守护进程重启on-failure仅非正常退出时重启unless-stopped除了手动停止外都重启 —— 生产推荐environment—— 注入环境变量两种写法yamlenvironment: - SPRING_DATASOURCE_URLjdbc:postgresql://postgres:5432/myappdb - JAVA_OPTS-Xmx256m或者字典格式。注意变量值不要加引号除非包含特殊字符。连接数据库的关键jdbc:postgresql://postgres:5432/myappdb—— 其中postgres是服务名因为两个容器在同一个自定义网络app-network中Docker 内部 DNS 会解析服务名为容器 IP。depends_on—— 启动顺序的误解与真相很多人以为depends_on会等待数据库完全就绪再启动 Java 应用。错默认情况下它只等待依赖容器的状态变为running即进程启动不代表数据库可以接受连接。解决方案条件等待Compose 3.8 需开启COMPOSE_EXPERIMENTAL1yamldepends_on: postgres: condition: service_healthy这需要 postgres 服务定义了healthcheck。更通用的方法在应用启动脚本中轮询数据库如使用wait-for-it.sh。示例dockerfileCOPY wait-for-it.sh /wait-for-it.sh CMD [/wait-for-it.sh, postgres:5432, --, java, -jar, app.jar]ports—— 端口映射8080:8080宿主机端口:容器端口。如果只写8080则宿主机随机端口映射。多端口可以列表形式。volumes—— 数据持久化与共享命名卷如postgres_data由 Docker 管理路径在/var/lib/docker/volumes/适合数据库等需要持久化的场景。绑定挂载如./logs:/app/logs直接挂载宿主机相对路径适合开发热加载或输出日志。注意权限容器内进程的 UID 可能不是宿主机的用户导致无法写入。可以用:Z或:z处理 SELinux或者指定user参数。networks—— 容器通信的桥梁自定义网络app-network让两个容器通过服务名互相访问。如果不定义网络Compose 会默认创建一个default网络服务名也可以互通但自定义网络可以更精细控制如设置 IPAM、限制外部访问。如果不需要对外暴露数据库端口可以不写ports只让 Java 容器内部访问提高安全性。deploy—— 资源限制仅用于 swarm 模式在docker-compose up模式下deploy字段被忽略。它只在docker stack deploySwarm 模式下生效。如果要在单机 Compose 中限制资源使用yamlcpus: 0.5 mem_limit: 512m mem_reservation: 256m注意mem_limit是旧版写法新版建议用deploy.resources但仅在 swarm 有效。坑点很多人搞混。单机模式使用cpus和mem_limit需要配合--compatibility或使用docker-compose 1.29的--compatibility标志。最佳实践生产环境用 Swarm 或 K8s单机开发暂不限制。5.3networks和volumes的顶层定义网络app-network可以被多个服务引用。driver: bridge是默认单机桥接网络。卷postgres_data如果不指定driver默认local。可以通过driver_opts配置 NFS 等。六、常用命令与调试技巧bash# 构建并启动所有服务-d 后台运行 docker-compose up -d # 只构建不启动 docker-compose build # 只拉取镜像postgres 等 docker-compose pull # 查看日志-f 实时跟踪 docker-compose logs -f java-app # 进入容器调试 docker-compose exec java-app sh # 在 Java 容器内测试数据库连通性 docker-compose exec java-app curl http://postgres:5432 # 不应直接访问仅测试网络 # 停止并删除所有容器、网络但保留卷 docker-compose down # 停止并删除卷数据丢失 docker-compose down -v # 重启单个服务 docker-compose restart java-app # 查看服务状态 docker-compose ps七、生产环境必加的配置项7.1 环境变量文件.env把敏感信息移到.env文件避免写死在 YAML 中。.env内容textDB_PASSWORDsecretpass DB_USERmyuser在docker-compose.yml中引用yamlenvironment: POSTGRES_PASSWORD: ${DB_PASSWORD} SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}7.2 日志驱动避免磁盘爆炸yamllogging: driver: json-file options: max-size: 10m max-file: 37.3 时区设置yamlenvironment: TZ: Asia/Shanghai volumes: - /etc/localtime:/etc/localtime:ro7.4 健康检查的 start_periodstart_period: 10s给予数据库启动的宽限期期间健康检查失败不计入重试。八、排坑指南血泪经验坑1Java 应用启动比数据库快导致连接失败解决使用wait-for-it.sh或 Spring Boot 的数据库重连机制spring.datasource.hikari.connection-timeout设置足够长。坑2卷权限导致 PostgreSQL 无法写入现象容器反复重启日志显示permission denied。解决可以指定user: 999:999postgres 官方镜像的 UID或者让宿主机目录权限为 999。坑3网络不通Java 应用报UnknownHostException: postgres检查两个服务是否在同一个网络是否拼写错误用docker-compose exec java-app ping postgres测试。坑4端口冲突宿主机 5432 或 8080 已被占用。修改ports左侧的宿主机端口如5433:5432。坑5depends_on不生效应用仍早于数据库启动根本原因depends_on不等健康。必须配合healthcheckcondition: service_healthy或启动脚本轮询。九、扩展连接远程已有的 PostgreSQL非容器如果你的 PostgreSQL 运行在外部服务器不是本机容器则不需要在docker-compose.yml中定义postgres服务。只需修改 Java 应用的SPRING_DATASOURCE_URL指向远程 IP。但为了网络连通性需要注意Java 容器需要能访问远程数据库的 IP 和端口。可以使用extra_hosts或宿主机网络模式network_mode: host。示例yamljava-app: build: . network_mode: host # 直接使用宿主机网络栈可通过 localhost 访问宿主机上的服务 environment: SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/myappdb但network_mode: host会失去网络隔离一般不建议。十、总结一份合格 Compose 文件的自我修养一份高质量的docker-compose.yml应当满足明确版本使用3.8及以上。所有服务都有健康检查至少关键依赖。敏感信息通过.env注入。数据持久化使用命名卷。自定义网络确保服务发现。日志限制防止磁盘占满。启动顺序保障健康等待或脚本。资源限制生产 Swarm 模式。掌握了这些你就从“docker-compose 用户”升级为“Compose 配置架构师”。下一步可以研究docker-compose.override.yml实现开发/生产环境分离或者向 Kubernetes 迁移。如果这篇文章帮你彻底啃下了docker-compose.yml的硬骨头欢迎点赞、收藏、转发。评论区留下你遇到的 Compose 怪问题我帮你排附录完整的项目文件树text. ├── docker-compose.yml ├── .env ├── Dockerfile ├── target/ │ └── app.jar ├── logs/ │ └── (运行时生成) └── init-scripts/ └── init.sql