Github Actions:部署Express至VPS时Secrets安全处理与Docker变量问题
部署场景:将Express应用部署至VPS,以Docker容器运行,需确保变量在容器中可用。
当前做法:将Secrets写入.env文件,并在docker-compose.yml中指定该文件为env_file,但担心服务器上明文存储的.env文件不够安全,想知道更优的最佳实践。
另外,使用如下CI/CD脚本的echo命令无法自动创建.env文件,必须手动在服务器上创建,求原因:
- name: Execute remote deployment script uses: appleboy/ssh-action@v1.0.3 with: HOST: ${{ secrets.HOST }} USERNAME: ${{ secrets.USERNAME }} PORT: ${{ secrets.PORT }} KEY: ${{ secrets.SSH_KEY }} script: | # Change directory to the app folder cd /usr/src/app/ # install dependencies npm ci --omit=dev # Stop and remove running containers docker-compose -f docker-compose.prod.yml down # Set environment variables stored in Github secrets echo "VITE_API_URL=${{ secrets.VITE_API_URL }}" > .env echo "DATABASE_URL=${{ secrets.DATABASE_URL }}" >> .env echo "SECRET_KEY=${{ secrets.SECRET_KEY }}" >> .env echo "REFRESH_SECRET_KEY=${{ secrets.REFRESH_SECRET_KEY }}" >> .env # start container and detach docker-compose -f docker-compose.prod.yml up -d
一、Secrets与变量的安全处理最佳实践
1. 直接通过docker-compose传递环境变量(无需生成.env文件)
跳过服务器本地存储.env文件的步骤,直接在启动容器时注入变量:
docker-compose -f docker-compose.prod.yml up -d \ -e VITE_API_URL=${{ secrets.VITE_API_URL }} \ -e DATABASE_URL=${{ secrets.DATABASE_URL }} \ -e SECRET_KEY=${{ secrets.SECRET_KEY }} \ -e REFRESH_SECRET_KEY=${{ secrets.REFRESH_SECRET_KEY }}
优势:服务器上不会留下明文敏感文件,变量仅在容器运行时生效,生命周期与容器绑定。
2. 使用Docker Secrets(适用于Swarm模式)
如果你的VPS采用Docker Swarm集群部署,可利用Docker原生的Secrets管理功能:
- 在CI脚本中将Secrets写入Docker Secret:
echo "${{ secrets.SECRET_KEY }}" | docker secret create secret_key - echo "${{ secrets.DATABASE_URL }}" | docker secret create database_url - - 在
docker-compose.prod.yml中引用Secrets:services: your-express-app: secrets: - secret_key - database_url environment: SECRET_KEY_FILE: /run/secrets/secret_key DATABASE_URL_FILE: /run/secrets/database_url
优势:Docker会加密存储Secrets,容器内仅能读取解密后的内容,彻底避免明文泄露。
3. 利用VPS系统环境变量
将Secrets预先配置到VPS的系统环境中,比如写入/etc/environment:
# 在VPS上执行一次 echo "VITE_API_URL=xxx" >> /etc/environment echo "DATABASE_URL=xxx" >> /etc/environment
之后在CI脚本中直接读取系统变量传递给容器:
docker-compose -f docker-compose.prod.yml up -d \ -e VITE_API_URL=$VITE_API_URL \ -e DATABASE_URL=$DATABASE_URL
优势:敏感数据存储在系统层面,权限控制更严格,无需每次部署传递变量。
二、echo命令无法自动创建.env文件的原因排查
1. 目录权限不足
执行cd /usr/src/app/后,当前SSH用户可能没有该目录的写入权限。可以在脚本中添加权限检查:
ls -ld /usr/src/app/
解决方法:给用户添加目录写入权限,或用sudo创建文件(注意sudo环境变量的继承问题):
sudo bash -c 'echo "VITE_API_URL=${{ secrets.VITE_API_URL }}" > /usr/src/app/.env'
2. 目标目录不存在
如果/usr/src/app/目录未创建,cd命令会失败,后续的echo自然无法生成文件。可以在脚本中先确保目录存在:
mkdir -p /usr/src/app/ && cd /usr/src/app/
3. CI变量替换异常
如果${{ secrets.XXX }}没有被正确替换(比如变量为空或CI配置错误),虽然不会导致文件无法创建,但会生成内容异常的.env文件。可以在脚本中添加调试输出:
echo "DEBUG: VITE_API_URL=${{ secrets.VITE_API_URL }}"
查看CI日志确认变量是否被正确注入。
4. SSH执行环境限制
appleboy/ssh-action默认使用非交互式shell,可能存在路径或环境变量差异。可以在脚本开头开启调试模式,查看每一步执行细节:
script: | set -x cd /usr/src/app/ # 后续命令...
内容的提问来源于stack exchange,提问作者Louis Eiden

