欢迎光临
我们一直在努力

Docker Compose 深度剖析:一文打尽所有配置信息

你以为 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)

    在项目根目录创建 Dockerfile:

    dockerfile

    # 基础镜像:使用 OpenJDK 17 精简版
    FROM openjdk:17-jdk-slim

    # 维护者信息
    LABEL maintainer="yourname@example.com"
    LABEL description="Spring 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 –interval=30s –timeout=3s –start-period=5s –retries=3 \\
    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.yml:

    yaml

    # 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.1 version —— 选对版本很重要

    • '3.8' 是目前广泛支持的稳定版,支持 depends_on 的 condition、deploy 资源限制等。

    • 如果你用旧版 2.x,则没有 deploy,且 depends_on 不支持健康等待。

    • 建议:新项目一律 3.8 或更高(3.9、3.10 差别不大)。

    5.2 services —— 心脏地带

    image vs build
    • image: postgres:15-alpine 直接从仓库拉取。

    • build: . 会从当前目录的 Dockerfile 构建镜像。如果同时指定 image,则构建后还会打上该镜像名。

    container_name —— 固定容器名
    • 不指定则自动生成 项目名_服务名_序号。固定名称方便 docker exec -it my-java-app bash。

    restart: unless-stopped
    • no:不自动重启

    • always:总是重启(包括守护进程重启)

    • on-failure:仅非正常退出时重启

    • unless-stopped:除了手动停止外都重启 —— 生产推荐

    environment —— 注入环境变量

    两种写法:

    yaml

    environment:
    – SPRING_DATASOURCE_URL=jdbc: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_EXPERIMENTAL=1):

      yaml

      depends_on:
      postgres:
      condition: service_healthy

      这需要 postgres 服务定义了 healthcheck。

    • 更通用的方法:在应用启动脚本中轮询数据库(如使用 wait-for-it.sh)。示例:

      dockerfile

      COPY 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 deploy(Swarm 模式)下生效。

    • 如果要在单机 Compose 中限制资源,使用:

      yaml

      cpus: '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.3 networks 和 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 内容:

    text

    DB_PASSWORD=secretpass
    DB_USER=myuser

    在 docker-compose.yml 中引用:

    yaml

    environment:
    POSTGRES_PASSWORD: ${DB_PASSWORD}
    SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}

    7.2 日志驱动(避免磁盘爆炸)

    yaml

    logging:
    driver: "json-file"
    options:
    max-size: "10m"
    max-file: "3"

    7.3 时区设置

    yaml

    environment:
    TZ: Asia/Shanghai
    volumes:
    – /etc/localtime:/etc/localtime:ro

    7.4 健康检查的 start_period

    • start_period: 10s 给予数据库启动的宽限期,期间健康检查失败不计入重试。

    八、排坑指南(血泪经验)

    坑1:Java 应用启动比数据库快导致连接失败
    解决:使用 wait-for-it.sh 或 Spring Boot 的数据库重连机制(spring.datasource.hikari.connection-timeout 设置足够长)。

    坑2:卷权限导致 PostgreSQL 无法写入
    现象:容器反复重启,日志显示 permission denied。
    解决:可以指定 user: "999:999"(postgres 官方镜像的 UID),或者让宿主机目录权限为 999。

    坑3:网络不通,Java 应用报 UnknownHostException: postgres
    检查:两个服务是否在同一个网络?是否拼写错误?用 docker-compose exec java-app ping postgres 测试。

    坑4:端口冲突
    宿主机 5432 或 8080 已被占用。修改 ports 左侧的宿主机端口,如 "5433:5432"。

    坑5:depends_on 不生效,应用仍早于数据库启动
    根本原因:depends_on 不等健康。必须配合 healthcheck + condition: service_healthy 或启动脚本轮询。

    九、扩展:连接远程已有的 PostgreSQL(非容器)

    如果你的 PostgreSQL 运行在外部服务器(不是本机容器),则不需要在 docker-compose.yml 中定义 postgres 服务。只需修改 Java 应用的 SPRING_DATASOURCE_URL 指向远程 IP。但为了网络连通性,需要注意:

    • Java 容器需要能访问远程数据库的 IP 和端口。

    • 可以使用 extra_hosts 或宿主机网络模式 network_mode: host。

    示例:

    yaml

    java-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

    赞(0)
    未经允许不得转载:171主机测评 » Docker Compose 深度剖析:一文打尽所有配置信息
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址