数据库连接信息必须通过环境变量注入而非硬编码,因配置文件暴露会导致敏感信息泄露,且环境变量符合十二要素应用原则,支持多环境隔离与安全分发。

数据库连接信息必须从代码中剥离,硬编码在 config/database.php 或直接写死在 PDO 构造参数里,等于把锁孔焊死还把钥匙挂在门把手上。
为什么不能把 DB_HOST、DB_PASSWORD 写进 PHP 配置文件
一旦配置文件被意外暴露(比如 Nginx 未禁用 .php 文件源码下载、Git 提交漏加 .gitignore),攻击者 5 秒内就能拿到完整数据库访问权限。更危险的是:团队协作时,开发、测试、生产环境共用同一份配置,容易误连错库、清空线上表。
环境变量的本质是「运行时注入」——它不随代码版本流动,也不参与构建过程,天然满足十二要素应用原则中的「配置即外置」。
- 敏感值不进入 Git 历史(
.env加入.gitignore即可) - 同一份代码镜像,通过不同环境变量即可对接开发 MySQL、测试 Redis、生产分库
- CI/CD 流水线可安全注入密钥,无需修改源码或打补丁
本地开发用 vlucas/phpdotenv 加载 .env
这是目前最轻量、兼容性最好的方案,支持 CLI、Apache、PHP 内置服务器,且不依赖 Web 服务器配置。
立即学习“PHP免费学习笔记(深入)”;
安装和加载只需两步:
composer require vlucas/phpdotenv
在入口文件(如 index.php 或框架的 bootstrap/app.php)顶部加入:
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->safeLoad();
safeLoad() 的关键是:没有 .env 文件时不报错,适合生产环境跳过加载(靠系统级环境变量兜底)。
你的 .env 文件示例应长这样:
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=dev_app DB_USERNAME=dev_user DB_PASSWORD=dev_pass APP_ENV=local
⚠️ 必须确保该文件已加入 .gitignore;同时建议提交一个 .env.example,只留键名和空值,供新成员快速上手。
Docker 环境下优先用 env_file 而非 environment 明文定义
在 docker-compose.yml 中直接写 - DB_PASSWORD=mypass123 是高危操作——变量会留在镜像层或容器元数据中,docker inspect 一眼可见。
正确做法是用 env_file 引用外部文件,并确保该文件不进 Git:
services:
php:
image: php:8.2-fpm
env_file:
- .env.prod # 或 .env.staging
# 不再写 environment 下的明文 DB_*此时 PHP 应用仍通过 getenv('DB_HOST') 或 $_ENV['DB_HOST'] 读取,与本地开发完全一致。
额外注意两点:
- PHP 容器内需确保
variables_order包含E(即启用$_ENV),检查phpinfo()或运行php -i | grep variables_order - 若用 Apache + mod_php,需在虚拟主机中显式
PassEnv DB_HOST DB_PASSWORD,否则$_ENV为空
生产环境禁止依赖 .env,改用系统级环境变量注入
容器化部署时,K8s Secret、Docker Swarm Secrets、AWS ECS Task Definition 或 CI/CD 的 secret 变量,都应映射为容器启动时的原生环境变量。这时 vlucas/phpdotenv 已无作用,getenv() 直接生效。
验证是否生效,可在 PHP 中加一行调试:
var_dump(getenv('DB_HOST'), $_ENV['DB_HOST'] ?? null);如果两者都为 false 或 null,说明变量根本没注入成功——常见原因有:
- Docker Compose 版本低于 1.28,不支持
env_file在 service 级别使用 - PHP-FPM 配置中
clear_env = no被设为yes(默认是yes,需手动改为no) - 使用了
apache2-mpm-itk等特殊 MPM,会隔离环境变量
真正容易被忽略的点在于:环境变量注入是一次性的,容器启动后无法热更新。哪怕你改了宿主机的 .env.prod,也必须 docker-compose up -d --force-recreate 才生效。这点和配置文件热重载完全不同,得提前在运维流程里卡住。



















