自動部署實戰

透過 SSH 或自架 Runner 實現自動部署,支援多環境分離與零停機更新。

部署架構設計

推送程式碼到 main branch

觸發 GitLab CI Pipeline

Build stage

Docker 建置映像,推送到 GitLab Container Registry

Test stage

自動執行單元測試、整合測試、程式碼掃描

Deploy staging(自動)

部署到測試環境,等待 QA 驗證

Deploy production(手動審核)

點擊 GitLab 上的部署按鈕後,自動執行生產部署

方式一:透過 SSH 部署(最常見)

# 在 GitLab CI 變數中設定(Settings → CI/CD → Variables):
# SSH_PRIVATE_KEY: 部署用的 SSH 私鑰(Protected, Masked)
# DEPLOY_HOST: 部署伺服器 IP
# DEPLOY_USER: 部署用的 SSH 使用者

deploy-production:
  stage: deploy
  image: alpine:latest
  before_script:
    # 設定 SSH
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - ssh-keyscan $DEPLOY_HOST >> ~/.ssh/known_hosts
  script:
    - |
      ssh $DEPLOY_USER@$DEPLOY_HOST << 'EOF'
        # 登入 Container Registry
        docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
        # 更新並重啟容器
        docker pull $CI_REGISTRY_IMAGE:latest
        cd /opt/my-app
        docker compose up -d --no-build
        # 清理舊映像
        docker image prune -f
      EOF
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - main

方式二:使用自架 Runner(直接在部署機執行)

在部署伺服器上安裝並註冊 GitLab Runner,打上 deploy 標籤,Pipeline 中指定 tags: [deploy] 即可直接在伺服器上執行:

deploy-production:
  stage: deploy
  tags:
    - deploy          # 只在有 deploy 標籤的 Runner 執行
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - cd /opt/my-app
    # 更新 .env 檔案中的映像版本
    - echo "IMAGE_TAG=$CI_COMMIT_SHA" > .env.deploy
    - docker compose --env-file .env.deploy up -d --no-build
    - docker image prune -f
  environment:
    name: production
  only:
    - main
本教學的 FastTrack 部署架構正是使用此方式:自架 Runner 標記為 deploy,推送到 main branch 自動觸發部署。

Docker Compose 生產環境設定

# docker-compose.yml(生產環境)
version: "3.9"
services:
  app:
    image: ${CI_REGISTRY_IMAGE:-my-app}:${IMAGE_TAG:-latest}
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: ${DATABASE_URL}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app

回滾機制

# 快速回滾到上一個版本
rollback:
  stage: deploy
  tags:
    - deploy
  script:
    # 使用前一個 commit SHA 的映像
    - PREV_SHA=$(git log --format="%H" -n 2 | tail -1)
    - docker pull $CI_REGISTRY_IMAGE:$PREV_SHA
    - docker tag $CI_REGISTRY_IMAGE:$PREV_SHA $CI_REGISTRY_IMAGE:latest
    - docker compose up -d --no-build
  when: manual
  only:
    - main
學習完部署流程後,查看 進階技巧 了解快取優化、環境變數管理與安全掃描。