docker-compose.yml文件中使用.env文件以及密码特殊字符解决方案

docker-compose.yml文件中使用.env文件以及密码特殊字符解决方案

基础说明

  1. .env 文件:和 docker-compose.yml 放在同一目录,compose 启动时自动读取,无需额外配置。
  2. 变量语法:
    • yml 中取值:${变量名}
    • 支持默认值:${VAR:-默认值}(变量不存在时使用默认)
  3. 优先级(从高到低):
    命令行传参 > 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"

注意区分两个概念:

  1. 根目录 .envcompose 全局变量,用于解析 yml 自身(镜像、端口、路径等)
  2. env_file: 配置项:注入到容器内部的环境变量,只给容器程序读取

常用变量语法

  1. 基础取值 ${VAR}
ports:- "${PORT}:80"
  1. 变量不存在给默认 ${VAR:-default}
image: nginx:${NGINX_TAG:-latest}
  1. 变量不存在直接报错 ${VAR:?提示文字}
# 没配置DB_PASS则启动直接失败并提示
DB_PASSWORD: ${DB_PASS:?必须在.env配置数据库密码}

查看加载的环境变量

# 打印compose识别到的所有环境变量
docker-compose config

执行后会输出完整解析后的 yml,可校验变量是否读取成功。

常见坑

  1. .env 不能有空格
    错误:PORT = 8080
    正确:PORT=8080
  2. 注释 # 必须单独一行,不能跟变量同行末尾
    错误:PORT=8080 # web端口
  3. 变量区分大小写 MYSQL_PASSmysql_pass
  4. yml 中端口、路径数字必须用引号包裹 ${VAR},否则部分版本解析报错
  5. 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内部拼接,可读性更高:

  1. .env
DB_USER=postgres
# 密码带@,引号包裹
DB_PASS="abc@123!#$"
DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=agent
  1. 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'

避坑重点

  1. 引号只能解决.env文件解析问题,不能替代URL编码
    哪怕加引号,链接内部密码中的 @ 不转义依然会解析错误;
  2. 不止@#在URL里代表锚点,必须转义为%23
  3. 不要在=两侧加空格,.env语法严格:
    错误:DATABASE_URL = "xxx"
    正确:DATABASE_URL="xxx"
  4. 校验是否生效命令
docker-compose config

查看输出的完整链接,确认密码部分是否正确转义、无截断。

拓展:密码包含%怎么办?

如果密码本身有百分号 %,需要二次转义:%%25
例:密码 a@%b → 转义为 a%40%25b