docker-compose.yml文件中使用.env文件以及密码特殊字符解决方案
基础说明
.env文件:和docker-compose.yml放在同一目录,compose 启动时自动读取,无需额外配置。- 变量语法:
- yml 中取值:
${变量名} - 支持默认值:
${VAR:-默认值}(变量不存在时使用默认)
- yml 中取值:
- 优先级(从高到低):
命令行传参 >docker-compose --env-file指定文件 > 当前目录.env
目录结构
项目根目录/
├── .env # 环境变量文件
└── docker-compose.yml # 编排文件
示例 .env 文件
# .env
# 数据库配置
MYSQL_ROOT_PASSWORD=123456
MYSQL_DATABASE=test_db
MYSQL_USER=test
MYSQL_PASSWORD=654321# 服务端口
WEB_PORT=8080
DB_PORT=3306# 镜像版本
MYSQL_VERSION=8.0
NGINX_VERSION=1.25# 带空格的值用引号
APP_NAME="demo project"
docker-compose.yml 引用变量示例
version: '3.8'services:mysql:image: mysql:${MYSQL_VERSION} # 读取.env镜像版本ports:- "${DB_PORT}:3306" # 数据库端口environment:# 直接读取环境变量注入容器MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}MYSQL_DATABASE: ${MYSQL_DATABASE}MYSQL_USER: ${MYSQL_USER}MYSQL_PASSWORD: ${MYSQL_PASSWORD}volumes:- mysql_data:/var/lib/mysqlrestart: alwaysnginx:image: nginx:${NGINX_VERSION}ports:- "${WEB_PORT}:80"# 变量默认值:如果.env没有APP_PORT就用9000environment:APP_PORT: ${APP_PORT:-9000}APP_NAME: ${APP_NAME}restart: alwaysvolumes:mysql_data:
自定义 .env 文件
(不使用默认 .env)
方式1:启动命令指定 env 文件
# 使用 env.prod 环境文件启动
docker-compose --env-file env.prod up -d# 停止
docker-compose --env-file env.prod down
目录结构:
├── docker-compose.yml
├── .env # 开发环境默认
├── env.prod # 生产环境
└── env.test # 测试环境
方式2:yml 中 env_file 配置(给单个容器加载独立环境文件)
services:mysql:image: mysql:8.0# 容器单独加载 db.env,优先级高于全局.envenv_file:- ./db.envports:- "3306:3306"
注意区分两个概念:
- 根目录
.env:compose 全局变量,用于解析 yml 自身(镜像、端口、路径等)env_file:配置项:注入到容器内部的环境变量,只给容器程序读取
常用变量语法
- 基础取值
${VAR}
ports:- "${PORT}:80"
- 变量不存在给默认
${VAR:-default}
image: nginx:${NGINX_TAG:-latest}
- 变量不存在直接报错
${VAR:?提示文字}
# 没配置DB_PASS则启动直接失败并提示
DB_PASSWORD: ${DB_PASS:?必须在.env配置数据库密码}
查看加载的环境变量
# 打印compose识别到的所有环境变量
docker-compose config
执行后会输出完整解析后的 yml,可校验变量是否读取成功。
常见坑
.env不能有空格
错误:PORT = 8080
正确:PORT=8080- 注释
#必须单独一行,不能跟变量同行末尾
错误:PORT=8080 # web端口 - 变量区分大小写
MYSQL_PASS≠mysql_pass - yml 中端口、路径数字必须用引号包裹
${VAR},否则部分版本解析报错 env_file里的变量不能用来解析yml本身,只能进容器;全局.env才能解析yml
完整启动命令
# 后台启动,自动读取同目录.env
docker-compose up -d# 查看配置解析结果
docker-compose config# 停止并删除容器
docker-compose down
解决.env 密码特殊字符方案
解决.env 密码含 @、#、:、/ 等特殊字符方案
解决.env密码含@、#、:、/等特殊字符方案
核心问题
连接串格式:postgresql://用户:密码@地址:端口/库名
如果密码本身包含 @,直接写会被解析器误判为「密码结束、主机开始」,导致连接报错。
示例错误写法:
# 错误!密码 abc@123 里的@会截断地址
DATABASE_URL=postgresql://postgres:abc@123@127.0.0.1:5432/agent
方案一:URL 百分号编码(推荐,通用所有数据库)
对密码里的特殊字符做 URL 转义:
| 特殊字符 | 转义编码 |
|---|---|
@ |
%40 |
: |
%3A |
/ |
%2F |
# |
%23 |
$ |
%24 |
& |
%26 |
示例
原密码:abc@123!
转义后密码:abc%40123!
.env 正确写法:
DATABASE_URL=postgresql://postgres:abc%40123!@127.0.0.1:5432/agent
方案二:.env 变量加引号包裹
(仅解决.env解析,不能替代URL编码)
.env 文件中值带特殊符号时,用双引号/单引号包裹变量值,避免 compose 解析.env时截断,但URL内的@仍要编码。
# 引号只是保护.env读取,@依旧要转义
DATABASE_URL="postgresql://postgres:abc%40123@127.0.0.1:5432/agent"
补充:纯密码单独变量场景(拆分写法,更易维护)
不把完整链接写死,拆分用户名、密码、地址,yml内部拼接,可读性更高:
- .env
DB_USER=postgres
# 密码带@,引号包裹
DB_PASS="abc@123!#$"
DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=agent
- docker-compose.yml
使用拼接 + 自动URL编码(部分ORM/驱动支持自动编码,纯compose拼接仍需手动转义)
services:app:environment:# 方式1:手动编码密码拼接(稳定通用)DATABASE_URL: postgresql://${DB_USER}:abc%40123!%23%24@${DB_HOST}:${DB_PORT}/${DB_NAME}# 方式2:若你的程序支持自动转义参数,分开注入变量,程序内部拼接(推荐开发)DB_USER: ${DB_USER}DB_PASS: ${DB_PASS}DB_HOST: ${DB_HOST}DB_PORT: ${DB_PORT}DB_NAME: ${DB_NAME}
方案三:单引号包裹.env值(部分兼容场景)
单引号内内容不会被.env解析转义,适合大量特殊符号:
DATABASE_URL='postgresql://postgres:abc%40123@127.0.0.1:5432/agent'
避坑重点
- 引号只能解决.env文件解析问题,不能替代URL编码
哪怕加引号,链接内部密码中的@不转义依然会解析错误; - 不止
@,#在URL里代表锚点,必须转义为%23; - 不要在
=两侧加空格,.env语法严格:
错误:DATABASE_URL = "xxx"
正确:DATABASE_URL="xxx" - 校验是否生效命令
docker-compose config
查看输出的完整链接,确认密码部分是否正确转义、无截断。
拓展:密码包含%怎么办?
如果密码本身有百分号 %,需要二次转义:% → %25
例:密码 a@%b → 转义为 a%40%25b
希望多多沟通和交流
