You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.gitlab-ci.yml中通过SSH在远程服务器正确执行多命令?

问题

在GitLab CI的.gitlab-ci.yml中尝试通过SSH登录远程服务器执行一系列命令,最初使用Here-Doc写法失败,改写为单行命令后出现bash: line 2: --env: command not found错误,Docker Run的参数被识别为独立命令。

原Here-Doc配置(报错):

deploy_to_server_job:
  stage: deploy_to_server
  tags:
    - server-runner
  image: ubuntu:22.04
  script:
    - chmod og= $ID_RSA
    - |
      ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP bash << EOF
        docker system prune --force
        echo "Running container with image: $FULL_IMAGE_NAME"
        docker run -p 127.0.0.1:$SERVER_PORT:$SERVER_PORT \
        --env JWT_AUTH_SECRET=$JWT_AUTH_SECRET \
        --env JWT_AUTH_EXPIRES=$JWT_AUTH_EXPIRES \
        --env JWT_REFRESH_SECRET=$JWT_REFRESH_SECRET \
        --env JWT_REFRESH_EXPIRES=$JWT_REFRESH_EXPIRES \
        --env MONGODB_URI=$MONGODB_URI \
        --env PORT=$PORT \
        -d --name admin-server $FULL_IMAGE_NAME
        exit
      EOF

单行命令配置(报错bash: line 2: --env: command not found):

- ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "whoami; echo Running container with image $FULL_IMAGE_NAME; docker system prune --force; docker run --name admin-server -d -p 127.0.0.1:$SERVER_PORT:$SERVER_PORT --env JWT_AUTH_SECRET=$JWT_AUTH_SECRET --env JWT_AUTH_EXPIRES=$JWT_AUTH_EXPIRES --env JWT_REFRESH_SECRET=$JWT_REFRESH_SECRET --env JWT_REFRESH_EXPIRES=$JWT_REFRESH_EXPIRES --env MONGODB_URI=$MONGODB_URI --env PORT=$PORT $FULL_IMAGE_NAME; exit"

解决方案

方法1:修复Here-Doc写法(规避GitLab CI解析冲突)

GitLab CI的YAML解析对Here-Doc的缩进、换行和变量解析有严格要求,修正后的配置如下:

deploy_to_server_job:
  stage: deploy_to_server
  tags:
    - server-runner
  image: ubuntu:22.04
  script:
    - chmod og= $ID_RSA
    - |
      ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP <<-'EOF'
        docker system prune --force
        echo "Running container with image: '"$FULL_IMAGE_NAME"'"
        docker run -p 127.0.0.1:'"$SERVER_PORT"':'"$SERVER_PORT"' \
        --env JWT_AUTH_SECRET='"$JWT_AUTH_SECRET"' \
        --env JWT_AUTH_EXPIRES='"$JWT_AUTH_EXPIRES"' \
        --env JWT_REFRESH_SECRET='"$JWT_REFRESH_SECRET"' \
        --env JWT_REFRESH_EXPIRES='"$JWT_REFRESH_EXPIRES"' \
        --env MONGODB_URI='"$MONGODB_URI"' \
        --env PORT='"$PORT"' \
        -d --name admin-server '"$FULL_IMAGE_NAME"'
      EOF
  • 用<<-'EOF'替代bash << EOF,-允许EOF标记前存在缩进,避免YAML解析报错;
  • 本地变量用'"$VAR"'包裹,确保Git Runner在本地解析变量后,传递到远程服务器时保留正确的引号格式,防止参数被意外拆分。

方法2:用单引号包裹远程命令,避免本地解析干扰

单行命令出错的核心原因是双引号下Git Runner会提前解析部分变量,破坏远程命令的结构。改用单引号包裹整个远程命令,同时对需要本地解析的变量单独拼接:

deploy_to_server_job:
  stage: deploy_to_server
  tags:
    - server-runner
  image: ubuntu:22.04
  script:
    - chmod og= $ID_RSA
    - ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP 'whoami; echo Running container with image '"$FULL_IMAGE_NAME"'; docker system prune --force; docker run --name admin-server -d -p 127.0.0.1:'"$SERVER_PORT"':'"$SERVER_PORT"' --env JWT_AUTH_SECRET='"$JWT_AUTH_SECRET"' --env JWT_AUTH_EXPIRES='"$JWT_AUTH_EXPIRES"' --env JWT_REFRESH_SECRET='"$JWT_REFRESH_SECRET"' --env JWT_REFRESH_EXPIRES='"$JWT_REFRESH_EXPIRES"' --env MONGODB_URI='"$MONGODB_URI"' --env PORT='"$PORT"' '"$FULL_IMAGE_NAME"''
  • 整个远程命令用单引号'包裹,阻止Git Runner解析命令内部的分号和参数;
  • 需要本地替换的变量(如$FULL_IMAGE_NAME)用'"$VAR"'形式拼接,确保变量值正确传递到远程,同时保留引号避免参数拆分。

方法3:写入临时脚本执行(最可靠,适合复杂命令)

对于复杂的远程执行逻辑,推荐将命令写入临时脚本后再通过SSH执行,彻底规避YAML和Shell解析的各类问题:

deploy_to_server_job:
  stage: deploy_to_server
  tags:
    - server-runner
  image: ubuntu:22.04
  script:
    - chmod og= $ID_RSA
    # 创建临时部署脚本
    - cat > deploy.sh << 'SCRIPT'
        #!/bin/bash
        docker system prune --force
        echo "Running container with image: $1"
        docker run -p 127.0.0.1:$2:$2 \
        --env JWT_AUTH_SECRET=$3 \
        --env JWT_AUTH_EXPIRES=$4 \
        --env JWT_REFRESH_SECRET=$5 \
        --env JWT_REFRESH_EXPIRES=$6 \
        --env MONGODB_URI=$7 \
        --env PORT=$8 \
        -d --name admin-server $1
      SCRIPT
    - chmod +x deploy.sh
    # 传递脚本和参数到远程执行
    - ssh -tt -i $ID_RSA -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP "bash -s" < ./deploy.sh "$FULL_IMAGE_NAME" "$SERVER_PORT" "$JWT_AUTH_SECRET" "$JWT_AUTH_EXPIRES" "$JWT_REFRESH_SECRET" "$JWT_REFRESH_EXPIRES" "$MONGODB_URI" "$PORT"
  • 将部署逻辑写入独立Shell脚本,避免YAML的解析限制;
  • 通过bash -s和标准输入传递脚本,用位置参数传递变量,彻底避免变量解析冲突;
  • 这种方式可读性更强,后续修改命令也更便捷。

内容的提问来源于stack exchange,提问作者ENIAC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:53:13