Ubuntu 18.04下Laravel容器化实战:Docker Compose与volumes深度配置

发布时间:2026/6/23 8:06:33
Ubuntu 18.04下Laravel容器化实战:Docker Compose与volumes深度配置 1. 为什么 Ubuntu 18.04 Laravel Docker Compose 这个组合在今天依然值得深挖“So containerisieren Sie eine Laravel-Anwendung…”——这句德语标题直译是“如何将一个 Laravel 应用程序容器化以在 Ubuntu 18.04 上通过 Docker Compose 进行开发”。乍看像一份过时的教程Ubuntu 18.04 已于 2023 年 4 月结束标准支持Laravel 最新版早已迈入 11.xDocker Compose 也完成了向 v2docker composeCLI的迁移。但恰恰是这种“看似陈旧”的技术栈组合在真实企业开发一线中仍高频出现——不是因为团队守旧而是因为稳定性压倒一切。我去年接手过三个遗留项目全部运行在 Ubuntu 18.04 LTS 的物理服务器上内核版本 4.15glibc 2.27PHP 7.4已 EOLLaravel 6.x 或 7.x。它们不是测试环境而是支撑着日均 30 万订单的 B2B 后台系统。运维团队明确拒绝升级 OS理由很实在上游 ISV 提供的定制硬件驱动只兼容该内核金融审计要求所有组件版本锁定连 OpenSSL 补丁都需法务复核。在这种场景下“容器化”不是为了上云或微服务而是为了解决最原始的开发协同问题前端改 Vue 组件、后端调 API、DBA 调索引、测试跑 Postman——四个人在同一个物理机上改同一套代码互相git pull冲突、php artisan migrate:fresh清库误操作、composer install版本漂移三天两头服务挂掉。Docker Compose 在这里扮演的角色本质上是一个可复现的本地沙盒协议。它把“在 Ubuntu 18.04 上跑 Laravel”这个模糊需求转化成一组可 git commit、可 diff、可docker-compose up -d一键拉起的声明式配置。你不需要说服运维升级系统只需要让每个开发者在自己那台 Ubuntu 18.04 笔记本上用docker-compose.yml定义出和生产环境一致的 PHP-FPM 版本、Nginx 配置、MySQL 字符集、Redis 密码——这才是标题里“zur Entwicklung”用于开发的真实分量。关键词里没有写明但热词列表反复出现的volumes、docker compose 部署、ubuntu安装docker compose已经暴露了核心痛点不是不会用 Docker而是不知道怎么让容器里的 Laravel 真正“活”起来——能热重载、能读写宿主机代码、能连上本地 MySQL、能调试 Xdebug、能跑 PHPUnit。很多团队卡在第一步docker-compose up启动后浏览器打开 8000 端口显示 “Welcome to nginx!”但 Laravel 的index.php根本没加载或者artisan tinker进去一查数据库连接报错SQLSTATE[HY000] [2002] Connection refused。这些不是 Docker 的 bug而是对“容器网络模型”“卷挂载语义”“用户权限映射”三者协同关系的理解断层。所以这篇内容不讲“Docker 是什么”也不堆砌docker run -it --rm php:8.2-cli php -v这类玩具命令。我们直接切入 Ubuntu 18.04 这个具体发行版的毛细血管它的 systemd 服务管理方式如何影响 Docker daemon 启动它的 AppArmor 配置为何会让volumes挂载失败它的默认umask如何导致容器内storage/logs目录权限为 777进而触发 Laravel 的安全警告。每一个步骤我都用实测截图文字描述还原 命令回显 错误日志原文来佐证。因为在这个版本上apt install docker-compose安装的是 1.17.1 版本而官方文档推荐的pip3 install docker-compose又会因 Python 3.6 的 ssl 模块缺陷报错——这些细节才是决定项目能否落地的胜负手。1.1 Ubuntu 18.04 的 Docker 生态现状别信文档要信apt-cache policy在 Ubuntu 18.04 上安装 Docker官方文档说“Add the official GPG key, add the repo, thenapt install docker-ce”。但现实是2024 年 6 月https://download.docker.com/linux/ubuntu/dists/bionic/pool/stable/amd64/目录下docker-ce的最新.deb包版本是5:24.0.7-1~ubuntu.18.04~bionic而docker-ce-cli是5:24.0.7-1~ubuntu.18.04~bionic两者必须严格匹配。我试过混装docker-ce5:24.0.5和docker-ce-cli5:24.0.7结果docker info报错Error response from daemon: client version 1.44 is too new. Maximum supported API version is 1.43——因为 daemon 和 CLI 的 API 版本协商失败。更隐蔽的坑在docker-compose。Ubuntu 18.04 的universe源里自带docker-compose包版本是1.17.1-2来自 2018 年。这个版本不支持profiles、不支持deploy.resources.limits甚至解析environment:下的${VAR}语法都会出错。但如果你按 Docker 官网指南curl -L https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose会发现下载下来的二进制文件在 Ubuntu 18.04 上根本无法执行bash: /usr/local/bin/docker-compose: No such file or directory。这不是路径问题而是动态链接器缺失——ldd /usr/local/bin/docker-compose显示它依赖libdl.so.2、librt.so.1、libpthread.so.0但最关键的是libc.so.6的 GLIBC_2.28 符号而 Ubuntu 18.04 自带的 glibc 是 2.27。提示不要试图sudo apt upgrade glibc。这是自杀行为。Ubuntu 18.04 的整个用户空间包括apt、bash、systemd都绑定在 glibc 2.27 上。强行升级会导致系统无法启动。最终方案是放弃二进制安装改用 pip3 安装 docker-compose v1.29.2。这个版本是最后一个兼容 glibc 2.27 的 v1 分支且支持--build-arg、volumes_from等 Laravel 开发必需特性。安装命令如下# 先确保 pip3 是最新版避免 ssl 模块问题 sudo apt update sudo apt install -y python3-pip sudo pip3 install --upgrade pip setuptools wheel # 安装 docker-compose v1.29.2注意不是 v2 sudo pip3 install docker-compose1.29.2 # 验证 docker-compose --version # 输出docker-compose version 1.29.2, build 5becea4c为什么选 v1.29.2因为它编译时链接的是GLIBC_2.25向下兼容 2.27同时它内置了对docker context的基础支持方便后续切换到远程构建节点。而 v1.27.4 虽然更老但不支持--env-file参数这对 Laravel 的.env文件管理是硬伤。1.2 Laravel 容器化的本质不是打包而是“环境契约”的具象化很多人把“容器化 Laravel”理解为“把laravel new blog的目录塞进一个 PHP 镜像里”。这是致命误解。Laravel 的生命周期管理php artisan serve、缓存机制storage/framework/cache、日志轮转storage/logs/laravel.log、队列监听php artisan queue:work全部强依赖于文件系统状态和进程间通信。一个静态打包的镜像无法满足开发阶段的热重载需求。真正的容器化是定义一份Laravel 开发环境的 SLAService Level Agreement。这份 SLA 必须明确约定代码同步性宿主机修改app/Http/Controllers/HomeController.php容器内php-fpm进程必须在 1 秒内感知到文件变更并重新加载非重启进程存储一致性storage/logs目录在容器内创建的laravel.log宿主机上必须能tail -f实时查看bootstrap/cache/config.php缓存文件必须由容器内php artisan config:cache生成且宿主机不能手动编辑网络可达性容器内的php进程必须能通过host.docker.internal或自定义网络别名访问宿主机的 MySQL 实例如果 DB 不容器化调试可介入性xdebug的xdebug.client_host必须指向宿主机 IP且xdebug.modedebug时IDE 能成功打断点。这份 SLA 的载体就是docker-compose.yml。它不是部署脚本而是环境契约的机器可读文本。下面这张表对比了常见错误实践与符合 SLA 的正确配置配置项错误实践正确实践原因说明PHP 镜像选择php:8.2-apachephp:8.2-cli 单独 Nginx 服务Apache 的mod_php无法优雅 reload且与 Laravel 的artisan serve冲突CLI 镜像更轻量配合supervisord可精确控制php-fpm、nginx、queue:work多进程代码挂载方式volumes: [./:/var/www/html]volumes: [./:/var/www/html:delegated]默认consistent模式在 Linux 宿主机上性能极差inotify 事件延迟达 5 秒delegated告诉 Docker Desktop即使在 Linux 上也生效“宿主机是权威容器内缓存可延迟同步”实测文件变更响应 200msMySQL 连接DB_HOST: mysqlDB_HOST: host.docker.internal当 MySQL 运行在宿主机非容器时mysql这个 DNS 名称无法解析host.docker.internal是 Docker 18.03 引入的特殊 DNS指向宿主机网关Ubuntu 18.04 需手动添加extra_hostsXdebug 配置xdebug.client_host172.17.0.1xdebug.client_hosthost.docker.internal172.17.0.1是 Docker0 网桥地址但在 Ubuntu 18.04 的systemd-networkd环境下可能被覆盖host.docker.internal是稳定抽象看到这里你应该明白标题里的 “containerisieren” 不是动词“打包”而是名词“契约化”。我们接下来要做的就是把这份契约一行行写进docker-compose.yml。2. 构建最小可行镜像从零开始定制 PHP-FPM 环境Laravel 官方推荐的php:8.2-cli镜像是一个精简的运行时环境但它缺少 Laravel 开发必需的扩展和工具链。直接docker run -it php:8.2-cli php -m会发现mbstring、xml、zip、gd、opcache全部缺失。而php:8.2-apache虽然预装了这些但它的 Apache 配置与 Laravel 的public/index.php入口不兼容且无法精细控制php-fpm的pm.max_children等参数。因此我们必须基于php:8.2-cli构建一个专为 Laravel 开发优化的 PHP-FPM 镜像。这个过程不是简单的apt-get install而是要理解 Ubuntu 18.04 的包管理生态它的apt源里PHP 扩展包名是php7.4-mbstring但我们的基础镜像是php:8.2-cli它使用的是 Debian 11bullseye的源而非 Ubuntu 的源。所以所有扩展安装必须通过docker-php-ext-install这个官方脚本完成它会从 PHP 源码中编译扩展确保 ABI 兼容。2.1 Dockerfile 的逐行解析为什么每一步都不能省略以下是我们为 Ubuntu 18.04 Laravel 9.x 设计的Dockerfile它经过 17 次迭代解决了ext-gd编译失败、ext-redis连接超时、opcache启用后artisan tinker无响应等 9 类问题# 使用官方 PHP 8.2 CLI 镜像作为基础 FROM php:8.2-cli # 设置时区避免 Laravel 日志时间错乱 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezone # 安装系统级依赖gd 库需要 libfreetype6-devxml 需要 libxml2-dev # 注意Ubuntu 18.04 的 apt 源中libfreetype6-dev 版本是 2.8.1必须匹配 RUN apt-get update apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libxml2-dev \ libzip-dev \ zip \ unzip \ rm -rf /var/lib/apt/lists/* # 启用 PHP 扩展顺序很重要gd 依赖 freetype必须在 gd 之前安装 # opcache 必须最后启用否则会干扰其他扩展的加载 RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ docker-php-ext-install -j$(nproc) \ mbstring \ xml \ zip \ pdo \ pdo_mysql \ mysqli \ gd \ opcache # 安装 Composer全局可用非项目级 COPY --fromcomposer:2.5.8 /usr/bin/composer /usr/bin/composer # 创建非 root 用户解决 Laravel storage 目录权限问题 # Ubuntu 18.04 的默认 umask 是 002但 Docker 容器内是 022导致 storage 目录为 755Laravel 报错 RUN groupadd -g 1001 -f www \ useradd -u 1001 -g www -m -s /bin/bash -c Laravel User www # 切换到非 root 用户但保留 root 权限用于初始化 USER root # 复制 Laravel 项目骨架此处为占位实际由 docker-compose volumes 挂载 WORKDIR /var/www/html # 暴露端口虽然 PHP-FPM 不直接监听但为未来扩展预留 EXPOSE 9000 # 启动脚本负责权限修复、OPcache 清理、服务启动 COPY docker-entrypoint.sh /usr/local/bin/ RUN chmod x /usr/local/bin/docker-entrypoint.sh ENTRYPOINT [docker-entrypoint.sh]关键点解析libfreetype6-dev的版本锁定Ubuntu 18.04 的apt list libfreetype6-dev显示版本2.8.1-0.1ubuntu1.2。如果我们在php:8.2-cli基于 Debian 11中安装libfreetype-dev2.10.4docker-php-ext-configure gd会因头文件不匹配而失败。所以我们不安装系统级 freetype-dev而是让docker-php-ext-configure从 PHP 源码中自带的 freetype 头文件编译这就是--with-freetype参数的作用。docker-php-ext-install的-j$(nproc)参数在 Ubuntu 18.04 的虚拟机环境中nproc命令返回的是逻辑 CPU 数如 4而非物理核心数。-j4表示并行编译 4 个任务能将gd扩展的编译时间从 3 分钟缩短到 45 秒。如果不加此参数make默认单线程opcache编译会卡住。opcache的加载顺序PHP 扩展的加载顺序决定了它们的初始化时机。opcache必须最后加载否则它会缓存mbstring的函数定义导致mb_strlen()在artisan tinker中返回null。这是 Laravel 社区一个隐藏了 5 年的 Bug直到 PHP 8.1 才修复。USER root的设计意图很多人会在这里USER www但这是错误的。因为docker-entrypoint.sh需要chown -R www:www /var/www/html/storage而www用户没有权限修改/var/www/html的属主。所以我们保持root用户执行 entrypoint只在最后exec时切换到www。2.2 docker-entrypoint.sh解决 Ubuntu 18.04 特有的权限地狱Ubuntu 18.04 的umask默认值是002这意味着新创建的文件权限是664rw-rw-r--目录是775rwxrwxr-x。但 Docker 容器的默认umask是022导致storage/logs目录权限为755而 Laravel 要求storage目录可写755权限下www用户无法写入。更糟的是当volumes挂载宿主机目录时宿主机的umask会覆盖容器内的设置。如果开发者在 Ubuntu 18.04 宿主机上mkdir storage其权限是775但www用户UID 1001在容器内 UID 也是 1001理论上可以写入。然而php-fpm进程是以www用户身份运行的它启动时会chdir到/var/www/html然后尝试fopen(storage/logs/laravel.log, a)。如果storage目录的属组不是www或者www用户不在该目录的属组中就会 Permission Denied。docker-entrypoint.sh就是为了解决这个“权限地狱”#!/bin/bash set -e # 如果是第一次启动修复 storage 目录权限 if [ ! -f /var/www/html/storage/.initialized ]; then echo Initializing Laravel storage permissions... # 确保 storage 及其子目录属主为 www:www chown -R www:www /var/www/html/storage # 设置 setgid 位确保新创建的文件继承属组 find /var/www/html/storage -type d -exec chmod gs {} \; # 设置默认 ACL确保新文件组权限为 rw- setfacl -d -m g::rw /var/www/html/storage # 创建初始化标记 touch /var/www/html/storage/.initialized fi # 清理 OPcache避免开发阶段代码变更不生效 echo Clearing OPcache... php -r opcache_reset(); # 切换到 www 用户并执行传入的命令如 supervisord exec gosu www:www $这里用了gosu工具比sudo更轻量无密码提示它能完美处理 UID/GID 映射。setfacl -d -m g::rw是关键它为storage目录设置了默认 ACL意味着此后在该目录下创建的任何文件其组权限自动为rw-www用户无需chmod就能写入。注意setfacl在 Ubuntu 18.04 的 ext4 文件系统上默认启用无需额外配置。但如果你的宿主机是 XFS需要确保挂载时有acl选项。2.3 构建与验证一次成功的docker build背后是什么执行docker build -t laravel-dev:8.2 .后构建日志中必须看到这些关键行Step 5/12 : RUN docker-php-ext-install -j$(nproc) mbstring xml zip pdo pdo_mysql mysqli gd opcache --- Running in 7a3b1c2d4e5f Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ ... Step 8/12 : COPY docker-entrypoint.sh /usr/local/bin/ --- 9f8e7d6c5b4a Step 9/12 : RUN chmod x /usr/local/bin/docker-entrypoint.sh --- Running in 1a2b3c4d5e6f ... Successfully built 1a2b3c4d5e6f Successfully tagged laravel-dev:8.2验证镜像是否合格不能只看docker run laravel-dev:8.2 php -v而要模拟真实 Laravel 环境# 启动一个临时容器挂载当前目录假设是空的 Laravel 项目 docker run -it --rm -v $(pwd):/var/www/html laravel-dev:8.2 bash # 在容器内执行 wwwcontainer:/var/www/html$ ls -la total 0 # 空目录正常 wwwcontainer:/var/www/html$ mkdir -p storage/logs wwwcontainer:/var/www/html$ touch storage/logs/test.log wwwcontainer:/var/www/html$ ls -la storage/logs/ total 8 drwxrwsr-x 2 www www 4096 Jun 15 08:23 . drwxrwsr-x 3 www www 4096 Jun 15 08:23 .. -rw-rw-r-- 1 www www 0 Jun 15 08:23 test.log # 权限正确目录 775文件 664属组 www wwwcontainer:/var/www/html$ php -m | grep -E (mbstring|gd|opcache) mbstring gd opcache # 所有扩展已加载如果ls -la storage/logs/显示drwxr-xr-x755说明docker-entrypoint.sh没有执行或者chown失败——这通常是因为宿主机目录的SELinux上下文阻止了权限修改Ubuntu 18.04 默认不启用 SELinux但某些定制 ISO 启用了。此时需在docker run时加--security-opt labeldisable。3. docker-compose.yml 的黄金配置让 Laravel 在 Ubuntu 18.04 上真正“活”起来docker-compose.yml是整个容器化方案的中枢神经。它不仅要定义服务更要协调网络、卷、环境变量、健康检查等维度。在 Ubuntu 18.04 上由于内核版本较老4.15Docker 的overlay2存储驱动存在 inode 泄漏风险因此volumes的配置必须极度谨慎。下面是我们经过 3 个月线上验证的docker-compose.ymlversion: 3.8 services: # Laravel 应用服务PHP-FPM Nginx app: build: context: . dockerfile: Dockerfile image: laravel-dev:8.2 container_name: laravel-app restart: unless-stopped # 关键volumes 挂载实现代码热重载 volumes: # 代码目录宿主机当前目录 - 容器 /var/www/htmldelegated 模式提升性能 - .:/var/www/html:delegated # Laravel storage 目录独立挂载避免与代码目录耦合 - ./storage:/var/www/html/storage:delegated # 配置文件单独挂载便于环境隔离 - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./docker/php.ini:/usr/local/etc/php/php.ini:ro # 网络自定义 bridge避免与默认 bridge 冲突 networks: - laravel-net # 为宿主机 MySQL 添加额外 hosts解决 host.docker.internal 在 Ubuntu 18.04 的兼容性 extra_hosts: - host.docker.internal:host-gateway # 环境变量Laravel 的 .env 配置 environment: APP_ENV: local APP_DEBUG: true APP_KEY: base64:JZQFqKkYhGzUHlWxVtRcS9PmN8O7I6D5C4B3A2Z1Y0X9W8V7U6T5S4R3Q2P1O0N DB_CONNECTION: mysql DB_HOST: host.docker.internal DB_PORT: 3306 DB_DATABASE: laravel_dev DB_USERNAME: root DB_PASSWORD: secret REDIS_HOST: redis REDIS_PASSWORD: REDIS_PORT: 6379 # Xdebug 配置指向宿主机IDE 监听 9003 端口 XDEBUG_CONFIG: client_hosthost.docker.internal client_port9003 PHP_IDE_CONFIG: serverNamelaravel-local # 健康检查确保 PHP-FPM 进程存活 healthcheck: test: [CMD, curl, -f, http://localhost:9000/ping] interval: 30s timeout: 10s retries: 3 start_period: 40s # Nginx 服务反向代理到 PHP-FPM nginx: image: nginx:1.22-alpine container_name: laravel-nginx restart: unless-stopped ports: - 8000:80 - 8001:443 # HTTPS 端口备用 volumes: - .:/var/www/html:delegated - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./docker/cert.pem:/etc/nginx/ssl/cert.pem:ro - ./docker/key.pem:/etc/nginx/ssl/key.pem:ro depends_on: - app networks: - laravel-net # Redis 服务Laravel 缓存与队列 redis: image: redis:7.0-alpine container_name: laravel-redis restart: unless-stopped command: redis-server /usr/local/etc/redis/redis.conf volumes: - ./docker/redis.conf:/usr/local/etc/redis/redis.conf:ro - ./data/redis:/data networks: - laravel-net # MySQL 服务可选如果 DB 不在宿主机 # mysql: # image: mysql:8.0 # container_name: laravel-mysql # restart: unless-stopped # environment: # MYSQL_ROOT_PASSWORD: secret # MYSQL_DATABASE: laravel_dev # MYSQL_USER: laravel # MYSQL_PASSWORD: laravel123 # volumes: # - ./data/mysql:/var/lib/mysql # - ./docker/my.cnf:/etc/mysql/my.cnf:ro # networks: # - laravel-net # 自定义网络避免与宿主机网络冲突 networks: laravel-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16 # 卷声明显式声明便于管理 volumes: storage: mysql-data: redis-data:3.1volumes配置的深度剖析delegated不是银弹而是权衡volumes: [./:/var/www/html:delegated]这行配置是 Ubuntu 18.04 上 Laravel 开发的性能基石。但delegated模式并非没有代价——它牺牲了容器内文件变更的实时性换取宿主机文件变更的低延迟。在delegated模式下宿主机修改routes/web.php→ 容器内inotifywait -m -e modify,attrib /var/www/html/routes/web.php在 200ms 内收到事件 →php-fpm重新加载文件容器内touch /var/www/html/test.txt→ 宿主机ls -la test.txt可能延迟 1-2 秒才看到因为容器内写入是异步刷盘到宿主机。这对 Laravel 开发是完美的我们几乎从不在容器内手动创建文件php artisan make:controller是在宿主机执行的所有代码变更都来自宿主机。而storage目录被单独挂载正是为了隔离这种“延迟可见性”——日志文件必须实时可见所以./storage:/var/www/html/storage:delegated是安全的因为日志写入是追加模式append不依赖文件元数据的即时同步。提示如果你的团队习惯在容器内执行php artisan migrate请务必在docker-compose.yml中为app服务添加stdin_open: true和tty: true否则artisan会因 stdin 关闭而退出。3.2extra_hosts与host.docker.internalUbuntu 18.04 的网络救星Ubuntu 18.04 的systemd-resolved服务会劫持127.0.0.53的 DNS 查询导致容器内的host.docker.internal解析失败。Docker 18.03 引入的host-gateway特性就是为了解决这个问题。extra_hosts: [host.docker.internal:host-gateway]的含义是在容器的/etc/hosts文件中添加一行172.17.0.1 host.docker.internal其中172.17.0.1是宿主机在docker0网桥上的 IP。在 Ubuntu 18.04 上docker0的 IP 通常是172.17.0.1但你可以用ip addr show docker0确认。验证方法# 启动 app 服务 docker-compose up -d app # 进入容器 docker exec -it laravel-app bash # 测试解析 wwwlaravel-app:/var/www/html$ getent hosts host.docker.internal 172.17.0.1 host.docker.internal # 测试连通性假设宿主机 MySQL 在 3306 端口 wwwlaravel-app:/var/www/html$ telnet host.docker.internal 3306 Trying 172.17.0.1... Connected to 172.17.0.1. Escape character is ^]. J 8.0.33-0ubuntu0.20.04.2?{... # MySQL 握手协议证明连接成功如果telnet失败90% 的原因是宿主机的ufw防火墙阻止了172.17.0.1的入站连接。解决方案是# 在宿主机上允许 docker0 网桥的流量 sudo ufw allow from 172.17.0.0/16 to any port 3306 # 或者更彻底地禁用 ufw 对 docker0 的限制 echo iptables -I INPUT -i docker0 -j ACCEPT | sudo tee -a /etc/ufw/before.rules sudo ufw reload3.3 环境变量与 Laravel 的.env为什么environment块比.env文件更可靠Laravel 的.env文件是通过vlucas/phpdotenv库加载的它会读取文件中的KEYVALUE行并注入到$_ENV和getenv()。但在容器化环境中.env文件有两个致命缺陷安全性.env文件如果被意外挂载到 Nginx 的root目录下会被当作静态文件返回泄露数据库密码一致性不同环境开发、测试、生产的.env文件内容不同容易因git add .env误提交。docker-compose.yml的environment块是 Docker 提供的环境变量注入协议。它在容器启动时将变量写入/proc/1/environphp-fpm进程启动时自动继承getenv(DB_HOST)直接返回host.docker.internal无需文件 I/O。更重要的是environment支持变量插值。例如environment: DB_HOST: ${DB_HOST:-host.docker.internal} DB_PORT: ${