当前位置: 首页 > news >正文

Docker Compose 四层解析:从 YAML 语法到工程化落地

1. 为什么“写个 docker-compose.yml”不等于真正掌握了 Docker Compose

你有没有过这样的经历:照着教程复制粘贴一段docker-compose.yml,敲下docker compose up -d,服务跑起来了——然后就以为自己“会了”?结果第二天容器莫名退出,docker compose ps显示No configuration file provided: not found;改了个端口,前端连不上后端,查日志发现是网络不通;想加个 Redis 缓存,一通配置后 PHP 应用报错Connection refused,翻遍文档却找不到php容器和redis容器之间到底该用什么地址通信;更别提在 Ubuntu 服务器上部署时,docker compose命令根本不存在,而docker-compose(带横杠)又提示command not found……这些不是玄学,是工程化落地前必须跨过的认知断层。

Docker Compose 的本质,从来不是“把几个docker run命令写进 YAML 文件”这么简单。它是一套面向服务生命周期的声明式契约系统——你声明“我要一个 Nginx、一个 PHP-FPM、一个 MySQL、一个 Redis”,Compose 就负责按依赖顺序拉镜像、建网络、挂卷、设环境变量、启动容器、监控状态、处理重启策略,并在你执行down时干净地销毁所有资源。但这份契约的每一条条款,都隐含着底层机制、默认行为、常见陷阱和工程权衡。比如restart: always看似万能,实则在数据库容器里可能引发主从脑裂;volumes写成./data:/var/lib/mysql在 Windows 上可能因路径映射失败导致 MySQL 启动即崩溃;network_mode: "host"能解决端口冲突,却会让整个编排失去网络隔离能力,让depends_on形同虚设。

我第一次在 CentOS7 上部署 Jenkins + GitLab + Nexus 三件套时,就栽在docker-compose.ymlversion字段上。当时用的是3.8,本地 Docker Desktop 运行正常,但服务器上 Docker Engine 版本是 20.10,docker composeCLI 不支持3.8的某些字段,报错信息却只说invalid version,没提具体哪条不兼容。折腾两天才发现,3.8要求 Docker Engine ≥ 20.10.0,而我们线上是 20.10.0-rc1,差了那零点零一。这种细节,官方文档不会标红加粗,但工程上线时就是生死线。所以,这篇内容不讲“怎么安装 Docker”,也不罗列“10 个常用命令”,而是带你一层层剥开docker-compose.yml这个看似简单的文件背后,真实存在的四层结构:语法层(YAML 规则)、语义层(字段含义与约束)、运行时层(Docker Engine 如何解析执行)、工程层(多环境、CI/CD、可观测性如何嵌入)。只有看清这四层,你写的才不是脚本,而是可维护、可测试、可交付的服务蓝图。

2. 从docker rundocker compose up:一次彻底的范式迁移

很多人学 Compose 的第一课,就是把三个docker run命令“翻译”成 YAML。比如启动一个 WordPress 站点:

# 原始 docker run 方式 docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -v /data/mysql:/var/lib/mysql mysql:8.0 docker run -d --name wordpress -e WORDPRESS_DB_HOST=mysql -e WORDPRESS_DB_PASSWORD=123456 -p 8080:80 -v /data/wordpress:/var/www/html --link mysql:mysql wordpress:6.0 docker run -d --name nginx -p 80:80 -v /data/nginx/conf:/etc/nginx/conf.d -v /data/wordpress:/var/www/html nginx:1.24

然后写出这样的docker-compose.yml

version: "3.9" services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: "123456" ports: - "3306:3306" volumes: - "/data/mysql:/var/lib/mysql" wordpress: image: wordpress:6.0 environment: WORDPRESS_DB_HOST: "mysql" WORDPRESS_DB_PASSWORD: "123456" ports: - "8080:80" volumes: - "/data/wordpress:/var/www/html" depends_on: - mysql nginx: image: nginx:1.24 ports: - "80:80" volumes: - "/data/nginx/conf:/etc/nginx/conf.d" - "/data/wordpress:/var/www/html"

看起来完美?不。这恰恰是范式迁移失败的典型——你只是把命令行参数“平移”进了 YAML,没理解 Compose 的核心设计哲学:服务自治 + 网络协同

2.1--link已死,networks当立:服务发现的本质变革

--link是 Docker 早期为解决容器间通信引入的临时方案,它通过修改/etc/hosts文件,让被链接容器能用别名访问链接容器。但--link有致命缺陷:单向、静态、无法跨主机、与用户自定义网络不兼容。Compose 默认为每个docker-compose.yml创建一个独立的桥接网络(如myapp_default),所有服务自动加入该网络,并获得 DNS 解析能力。这意味着wordpress容器里,ping mysqlcurl http://mysql:3306是原生支持的,无需任何额外配置。depends_on字段的作用,仅仅是控制启动顺序(确保mysql先于wordpress启动),它不保证mysql服务已就绪(MySQL 进程监听了 3306 端口)。这就是为什么你常看到wordpress启动报错Can't connect to MySQL server——mysql容器是起来了,但 MySQL 进程还在初始化。

提示:真正的健康检查必须由应用自身完成。WordPress 官方镜像内置了healthcheck,你可以在docker-compose.yml中显式启用:

services: mysql: # ... 其他配置 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"] timeout: 20s retries: 10

2.2volumes的三种形态:宿主机路径、命名卷、绑定挂载的深层差异

上面 YAML 中的volumes全部用了绝对路径(/data/mysql)。这在开发机上可行,但在 CI/CD 流水线或不同操作系统(Windows/macOS/Linux)上必然失败。Compose 提供了三种 volume 模式,适用场景截然不同:

类型语法示例适用场景关键特性
命名卷(Named Volume)db-data:/var/lib/mysql生产环境数据库、需要持久化且不关心宿主机路径的数据由 Docker 管理,路径抽象,跨平台一致,支持备份/迁移
绑定挂载(Bind Mount)./src:/app/src开发环境代码热更新、配置文件共享直接映射宿主机目录,性能高,但路径硬编码,易出错
tmpfs 挂载tmpfs: /run敏感临时数据(如 session)、避免写入磁盘数据仅存于内存,容器停止即丢失,安全性高

实际工程中,我坚持一个铁律:数据库、缓存、消息队列等有状态服务,必须用命名卷;应用代码、静态资源、Nginx 配置等无状态或开发期内容,才用绑定挂载。比如修正后的 WordPress 配置:

services: mysql: # ... 其他配置 volumes: - "db-data:/var/lib/mysql" # 命名卷,安全可靠 # ... healthcheck wordpress: # ... 其他配置 volumes: - "wp-content:/var/www/html/wp-content" # 插件/主题数据用命名卷 - "./custom-conf:/var/www/html/wp-config.php" # 自定义配置用绑定挂载(开发) nginx: # ... 其他配置 volumes: - "./nginx/conf:/etc/nginx/conf.d" # Nginx 配置用绑定挂载(开发) - "wp-content:/var/www/html/wp-content" # 与 wordpress 共享同一命名卷

这里wp-content是一个命名卷,被wordpressnginx两个服务同时挂载,实现了“一份数据,多容器读写”的经典模式。而./nginx/conf是相对路径绑定挂载,方便你在 IDE 里直接编辑配置,改完docker compose restart nginx即可生效。

2.3ports的隐形陷阱:127.0.0.1:8080:808080:80的生死之别

ports字段看似简单,但8080:80127.0.0.1:8080:80有本质区别。前者将容器的 80 端口映射到宿主机所有网络接口的 8080 端口,意味着局域网内其他机器也能通过http://<server-ip>:8080访问;后者只映射到127.0.0.1(localhost),仅本机可访问。这在生产环境中是安全红线。很多团队在测试环境用8080:80,上线时忘记改成127.0.0.1:8080:80,导致测试接口直接暴露在公网,酿成事故。

更隐蔽的坑是端口冲突。当你在一台服务器上部署多个 Compose 项目(如project-aproject-b),如果它们都声明ports: ["8080:80"],第二个项目up时会报错Bind for 0.0.0.0:8080 failed: port is already allocated。解决方案不是改端口,而是用network_mode: "service:nginx"让其他服务复用 Nginx 的网络栈,或者用反向代理(Nginx/Traefik)统一分发流量,让每个项目只暴露内部端口(如80),由代理层做路由。这才是微服务架构的起点。

3.docker-compose.yml的四层结构解剖:从语法到工程实践

一个合格的docker-compose.yml文件,绝不是字段的堆砌。它是一个分层的契约文档,每一层解决一类问题。下面以一个真实的企业级 Spring Boot + PostgreSQL + Redis + Prometheus 监控栈为例,逐层拆解。

3.1 语法层:YAML 的严谨性与 Compose 的扩展性

YAML 是人类可读的数据序列化格式,但它的缩进、冒号、引号规则极易出错。一个常见的低级错误是:

environment: SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/mydb # 错误!URL 中的 : 和 / 会被 YAML 解析器当作分隔符,导致值被截断

正确写法必须加引号:

environment: SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/mydb" # 或使用 > 折叠块(适合长字符串) JAVA_OPTS: > -Xms512m -Xmx1024m -Dfile.encoding=UTF-8

Compose 还支持变量替换和环境文件,这是工程化的基石。创建.env文件:

# .env COMPOSE_PROJECT_NAME=myapp POSTGRES_USER=appuser POSTGRES_PASSWORD=secretpass SPRING_PROFILES_ACTIVE=prod

然后在docker-compose.yml中引用:

services: app: image: myapp:${APP_VERSION:-latest} # APP_VERSION 未定义时用 latest environment: - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # ...

docker compose up会自动加载.env,你甚至可以用APP_VERSION=1.2.3 docker compose up覆盖环境变量。这比在 YAML 里硬编码版本号强一万倍。

3.2 语义层:每个字段背后的“为什么”与“何时用”

docker-compose.yml的字段不是孤立的,它们构成了一套完整的服务描述语言。关键字段的深层逻辑如下:

  • depends_on:仅控制启动顺序,不等待依赖服务就绪。它等价于docker run --depends-on,底层是--add-host和启动顺序调度。要实现真正的“等待就绪”,必须配合healthcheck+condition: service_healthy

    services: app: # ... 其他配置 depends_on: postgres: condition: service_healthy # 必须等 postgres 的 healthcheck 通过 redis: condition: service_started # 只需 redis 容器启动即可
  • restart策略no,always,on-failure,unless-stopped四种。always最常用,但对数据库有风险。PostgreSQL 官方镜像的healthcheck会检测pg_isready,如果设置restart: always,当主库因磁盘满触发healthcheck失败时,Docker 会不断重启容器,导致 WAL 日志疯狂增长,进一步挤占空间。此时应选on-failure: 5(失败 5 次后停止),并由外部监控告警人工介入。

  • buildvsimagebuild指向一个 Dockerfile 目录,image指向远程镜像仓库。工程实践中,我坚持“所有生产镜像必须由 CI/CD 构建并推送到私有仓库,docker-compose.yml中只用image”。build只保留在docker-compose.dev.yml中用于本地开发。这样能保证环境一致性——开发、测试、生产用的都是同一个镜像 ID。

  • networks自定义:默认的default网络够用,但复杂场景需自定义。例如,让apppostgresbackend网络通信,appnginxfrontend网络通信,nginx单独暴露在public网络:

    networks: backend: driver: bridge frontend: driver: bridge public: driver: bridge services: app: networks: - backend - frontend postgres: networks: - backend nginx: networks: - frontend - public ports: - "80:80"

    这样,postgres容器完全隔离在backend网络,外部无法直接访问,安全性大幅提升。

3.3 运行时层:docker compose up背后的 12 步执行链

当你敲下docker compose up -d,Docker CLI 并非简单地启动容器。它执行一个精密的 12 步流程(基于 Docker Compose v2.20+ 源码分析):

  1. 解析.env文件,加载环境变量;
  2. 读取docker-compose.yml,验证 YAML 语法和 Compose Schema 版本;
  3. 合并覆盖文件(如docker-compose.prod.yml),生成最终配置;
  4. 检查build上下文,若存在Dockerfile,调用docker build构建镜像(此步可跳过,用--no-build);
  5. 拉取远程镜像image字段指定的),若本地不存在;
  6. 创建命名卷volumes中定义的),若不存在;
  7. 创建自定义网络networks中定义的),若不存在;
  8. 为每个服务生成容器配置:包括网络连接、端口映射、环境变量、健康检查等;
  9. depends_on顺序启动容器,但不等待healthcheck
  10. 启动healthcheck子进程,持续监控容器健康状态;
  11. 注册docker compose logs,将容器 stdout/stderr 聚合;
  12. 返回控制台,后台运行。

理解这个链条,你就知道为什么docker compose up --no-build能加速部署(跳过第 4 步),为什么docker compose down --volumes会删除命名卷(对应第 6 步的逆操作),以及为什么docker compose ps有时显示Unhealthy(第 10 步的监控结果)。

3.4 工程层:多环境、CI/CD 与可观测性的无缝集成

一个只能在本机up的 Compose 文件,离工程化还很远。真正的工程化要求它能适应开发、测试、预发、生产四套环境。我的标准做法是:

  • docker-compose.yml:定义通用服务结构、网络、卷,不含环境敏感配置;
  • docker-compose.dev.yml:开发专用,启用bind mountDEBUG模式、restart: on-failure
  • docker-compose.prod.yml:生产专用,禁用bind mount,启用restart: unless-stopped,配置healthcheck,设置mem_limit
  • docker-compose.ci.yml:CI/CD 专用,禁用ports,启用loggingsyslog,添加entrypoint执行单元测试。

通过docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d合并配置。CI/CD 流水线(如 GitHub Actions)脚本如下:

# .github/workflows/deploy.yml name: Deploy to Prod on: push: branches: [main] tags: ['v*.*.*'] jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: myorg/myapp:${{ github.sha }} - name: Deploy to Server uses: appleboy/scp-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} source: "docker-compose.yml,docker-compose.prod.yml,.env.prod" target: "/opt/myapp/" - name: Run on Server uses: appleboy/ssh-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} script: | cd /opt/myapp docker compose -f docker-compose.yml -f docker-compose.prod.yml down docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --remove-orphans

可观测性方面,logging字段是关键入口:

services: app: # ... 其他配置 logging: driver: "json-file" options: max-size: "10m" max-file: "3" # 或对接 ELK # logging: # driver: "fluentd" # options: # fluentd-address: "localhost:24224" # tag: "myapp.app"

配合docker compose logs -f app,你能实时看到聚合日志;配合docker compose top app,能看到容器内进程;配合docker compose exec app sh,能进入容器调试。这才是一个完整的服务生命周期管理闭环。

4. 真实世界踩坑实录:那些让老手也皱眉的 Compose 陷阱

再完美的理论,也要经受真实世界的毒打。以下是我在过去三年运维 200+ 个 Compose 项目中,总结出的 5 个最高频、最隐蔽、最让人抓狂的坑,附带根因分析和可立即复用的修复方案。

4.1 坑:docker compose ps no configuration file provided: not found—— 路径与上下文的战争

现象:在项目根目录执行docker compose ps,报错No configuration file provided: not found,但ls明明能看到docker-compose.yml

根因分析:这不是文件不存在,而是docker composeCLI 的工作目录(working directory)与配置文件路径不匹配。docker compose默认在当前目录查找docker-compose.ymlcompose.yaml。但如果你在子目录(如./src)执行命令,它就不会向上递归查找。更隐蔽的是,某些 CI/CD 工具(如 Jenkins Pipeline)的sh步骤,默认工作目录是 workspace 根,而非git checkout的子模块路径。

复现步骤

mkdir myapp && cd myapp echo "version: '3.9'" > docker-compose.yml cd src # 进入子目录 docker compose ps # 报错!

修复方案

  • 方案1(推荐):显式指定文件路径
    docker compose -f ../docker-compose.yml ps
  • 方案2:用-p指定项目名,强制工作目录
    docker compose -p myapp -f ../docker-compose.yml ps
  • 方案3(CI/CD 专用):在脚本开头cd回项目根
    cd $(dirname $0)/.. # 假设脚本在 ./scripts/deploy.sh docker compose up -d

注意:docker-compose(带横杠)是旧版 Python 实现,docker compose(无横杠)是新版 Go 实现,两者命令行为略有差异。Ubuntu 22.04+ 默认安装docker compose,而 CentOS7 需手动下载二进制。docker compose version输出Docker Compose version v2.x.x即为新版。

4.2 坑:Windows 上volumes映射失败,The system cannot find the path specified

现象:在 Windows Docker Desktop 上,volumes使用./config:/app/config,容器启动报错The system cannot find the path specified,或配置文件根本没挂载进去。

根因分析:Windows 路径分隔符是\,而 Docker CLI 期望/。更重要的是,Docker Desktop 的 Linux VM(WSL2)无法直接访问 Windows 的 NTFS 路径,必须通过 WSL2 的/mnt/c/挂载点。./config在 CMD/PowerShell 中解析为C:\myapp\config,但 WSL2 里没有C:盘概念。

修复方案

  • 方案1(开发机首选):用 WSL2 路径
    volumes: - /mnt/c/Users/yourname/myapp/config:/app/config
  • 方案2(跨平台通用):用命名卷 + 初始化脚本
    services: app: volumes: - app-config:/app/config # 启动时从宿主机复制配置 command: sh -c "cp -r /host-config/* /app/config/ && exec java -jar app.jar" volumes: - ./config:/host-config # 绑定挂载宿主机 config 目录 - app-config:/app/config # 命名卷供应用读取

4.3 坑:depends_on+healthcheck仍失败,app启动时postgres连不上

现象docker-compose.yml中已配置depends_onhealthcheck,但app容器日志仍显示Connection refused

根因分析healthchecktest命令是在容器内部执行的,而app容器要连接postgres,走的是 Docker 网络 DNS。healthcheck成功,只说明postgres容器内的pg_isready命令能连上localhost:5432,但app容器的网络栈、DNS 解析、防火墙规则可能仍有问题。

排查链路

  1. docker compose exec app ping postgres—— 测试 DNS 解析和基础连通性;
  2. docker compose exec app telnet postgres 5432—— 测试端口可达性(需先apt-get install telnet);
  3. docker compose exec postgres netstat -tuln | grep 5432—— 确认 PostgreSQL 监听0.0.0.0:5432,而非127.0.0.1:5432
  4. 检查postgrespg_hba.conf,确认允许host all all 0.0.0.0/0 md5

终极修复:在appentrypoint中加入重试逻辑(Bash 脚本):

#!/bin/bash # wait-for-postgres.sh set -e host="$1" shift cmd="$@" until pg_isready -h "$host" -U "$POSTGRES_USER"; do echo "Waiting for PostgreSQL at $host..." sleep 2 done exec $cmd

然后在docker-compose.yml中:

services: app: entrypoint: ["./wait-for-postgres.sh", "postgres", "--"] command: ["java", "-jar", "app.jar"]

4.4 坑:docker compose down后,命名卷里的数据“消失”了

现象docker compose down后,再up,数据库数据没了。docker volume ls显示卷存在,但docker volume inspect <vol>发现Mountpoint下是空的。

根因分析docker compose down默认不删除命名卷,但如果你在docker-compose.yml中定义了volumes,而docker compose down时加了--volumes参数,或者你执行了docker volume rm <vol>,数据就真没了。更常见的是,你up时用了不同的COMPOSE_PROJECT_NAME,导致创建了新卷,而旧卷被遗弃。

验证方法

# 查看当前项目使用的卷 docker compose config --services # 列出服务名 docker volume ls | grep myapp # myapp 是 COMPOSE_PROJECT_NAME # 查看卷内容 docker run --rm -v myapp_db-data:/volume -it alpine ls -la /volume

防护策略

  • 永远不要在生产环境用docker compose down --volumes
  • 为每个环境设置唯一COMPOSE_PROJECT_NAME(通过.env文件);
  • 定期备份命名卷
    # 备份 docker run --rm -v myapp_db-data:/volume -v $(pwd):/backup alpine tar czf /backup/db-data-$(date +%F).tar.gz -C /volume . # 恢复 docker run --rm -v myapp_db-data:/volume -v $(pwd):/backup alpine tar xzf /backup/db-data-2024-01-01.tar.gz -C /volume

4.5 坑:docker compose restart always导致服务雪崩

现象:一个restart: always的服务(如 Nginx)因配置错误反复重启,CPU 占用 100%,docker stats显示其 CPU% 持续 99%,其他服务全部卡死。

根因分析restart: always意味着容器退出后,Docker Daemon 会立即尝试重启。如果容器启动 1 秒就因配置错误崩溃,Daemon 会在毫秒级内再次启动,形成“启动-崩溃-重启”循环,消耗大量系统资源。Docker 的重启策略有指数退避(exponential backoff),但初始间隔太短(100ms),对 CPU 是毁灭性打击。

修复方案

  • 方案1:改用restart: on-failure: 5,让失败 5 次后停止,留出人工干预时间;
  • 方案2:增加启动延迟,在command中加sleep(不推荐,治标不治本);
  • 方案3(最佳):用healthcheck+restart组合
    services: nginx: # ... 其他配置 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s restart: on-failure
    这样,只有healthcheck失败 3 次后,才会触发restart,且每次失败后有 30 秒间隔,彻底避免雪崩。

5. 从入门到交付:一个可直接复用的工程化模板

前面讲了原理、陷阱、结构,现在给你一个经过 10 个生产项目验证的、开箱即用的docker-compose.yml模板。它不是一个玩具示例,而是真实企业级项目的骨架,你可以直接复制、修改、部署。

# docker-compose.yml - 通用服务结构(不包含环境敏感配置) version: "3.9" # 全局设置,影响所有服务 x-common: &common # 所有服务共享的网络 networks: - backend - frontend # 共享的资源限制 mem_limit: 1g mem_reservation: 512m cpus: 1.0 # 共享的日志配置 logging: driver: "json-file" options: max-size: "10m" max-file: "3" # 定义网络,分离前后端流量 networks: backend: driver: bridge internal: true # 不允许外部访问 frontend: driver: bridge # 定义命名卷,用于持久化数据 volumes: db-data: driver: local redis-data: driver: local app-logs: driver: local services: # PostgreSQL 数据库 postgres: <<: *common image: postgres:15-alpine environment: POSTGRES_DB: ${POSTGRES_DB:-myapp} POSTGRES_USER: ${POSTGRES_USER:-appuser} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secretpass} # 重要:允许所有客户端连接(Docker 网络内) POSTGRES_HOST_AUTH_METHOD: trust volumes: - db-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} || exit 1"] interval: 30s timeout: 10s retries: 5 start_period: 40s restart: on-failure: 5 networks: - backend # Redis 缓存 redis: <<: *common image: redis:7-alpine command: redis-server /usr/local/etc/redis/redis.conf volumes: - redis-data:/data - ./redis.conf:/usr/local/etc/redis/redis.conf:ro healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 10s retries: 5 start_period: 40s restart: on-failure: 5 networks: - backend # 应用服务(Spring Boot 示例) app: <<: *common image: ${APP_IMAGE:-myorg/myapp:latest} environment: SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod} SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/${POSTGRES_DB}" SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER} SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} REDIS_URL: "redis://redis:6379/0" depends_on: postgres: condition: service_healthy redis: condition: service_started # 挂载日志卷,便于收集 volumes: - app-logs:/app/logs # 暴露管理端点(仅限内部网络) expose: - "8080" - "8081" # actuator restart: on-failure: 5 networks: - backend - frontend # Nginx 反向代理 nginx: <<: *common image: nginx:1.24-alpine volumes: - ./nginx/conf:/etc/nginx/conf.d:ro - ./nginx/html:/usr/share/nginx/html:ro - app-logs:/var/log/nginx # 共享日志卷 ports
http://www.jsqmd.com/news/1157350/

相关文章:

  • 2026最新北京百达翡丽售后维修服务中心全维度考察报告 - 百达翡丽官方服务中心
  • Odoo+Docker中小型企业ERP部署实战指南
  • MOSFET 栅极驱动电路 5 种拓扑实战对比:图腾柱/自举/隔离驱动实测波形
  • Codex本地编程代理:VSCode接入Qwen2/DeepSeek等开源模型实操指南
  • 江诗丹顿中国官方售后服务中心|地址与客服服务热线权威信息通告(2026年7月最新) - 江诗丹顿服务中心
  • Plus Jakarta Sans 开源字体:现代设计系统的终极实战指南
  • Dify、Coze与LangChain对比:AI应用开发平台选型指南
  • 水平姿态的总装和测试
  • 基于STM32单片机智能窗帘控制系统智能晾衣架设计定时雨滴光线321(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_
  • PVE家庭数字中枢搭建:ikuai+iStoreOS+Win10+飞牛OS五系统协同实战
  • TRAE:模型驱动的IDE新范式,Solo与IDE无缝协同
  • STM32与TLP241A在电气隔离系统中的应用实践
  • Windows本地部署Claude Code代理服务实战指南
  • K210图像识别+STM32测温双主控人脸口罩检测方案(含烧录工具、双固件、驱动代码)
  • 天梭官方更换表蒙价格查询|地址与售后热线权威信息公告(2026年7月最新) - 天梭服务中心
  • 2026 年 7 月百达翡丽官方最新服务体系与线下网点全解析 - 百达翡丽官方服务中心
  • VC6.0精简版实战指南:轻量环境配置、核心工作流与经典问题解决
  • Linux 源码编译排错指南:3 类常见 configure 错误分析与解决
  • Unity游戏自动翻译终极指南:5分钟实现外语游戏汉化
  • 无约束优化 3 种迭代法解析:最速下降、牛顿法、外点罚函数法 Python 实现
  • EM3080-W解码芯片与PIC24FJ256GA110的工业条码识别方案
  • 高校教材编写新趋势:AI教材生成工具,提升编写效率与质量!
  • 2026年7月最新南宁宝珀官方售后热线及客户服务网点地址 - 宝珀官方售后服务中心
  • 游戏逆向实战:从网络封包到技能Call的完整分析流程
  • OpenStack Glance手动安装:Keystone认证与9292端口服务集成详解
  • 为什么作业不能用豆包代写:认知科学视角下的学习本质
  • Codex接入DeepSeek实战指南:本地化AI编程助手配置与优化
  • 江诗丹顿官方售后专业维修服务保障腕表精准与优雅 - 江诗丹顿官方维修中心
  • 具身智能上市竞速赛打响:四大技术路线分野,谁能抢滩万亿市场?
  • 2026年7月最新深圳积家官方售后维修服务网点地址与客服电话 - 积家官方售后服务中心