CollabOps

Hosting

Vercel, Firebase, Fly.io, Convex, SSH 배포 템플릿

collabops/vercel-deploy@v1

On-Premise: ❌ — Vercel SaaS 연결 필요

Vercel 프로젝트를 빌드하고 배포합니다. Preview 및 Production 환경을 지원합니다.

사전 준비

1. Vercel API Token 발급

Vercel Dashboard > Settings > Tokens에서 토큰을 생성합니다.

2. Vercel 프로젝트 생성

Git provider 연결 없이 Vercel API로 프로젝트를 생성해야 합니다.

curl -X POST https://api.vercel.com/v10/projects \
  -H "Authorization: Bearer {VERCEL_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-project", "framework": "nextjs"}'

name: 프로젝트 이름

framework: 프로젝트에 맞게 지정 (nextjs, vite, nuxtjs, svelte 등)

응답의 idVERCEL_PROJECT_ID로 사용

3. Organization ID 확인

Vercel Dashboard > Settings > General에서 Vercel ID (= Organization ID)를 확인합니다.

4. Secrets 등록

CollabOps 프로젝트 설정에서 다음 시크릿을 등록합니다.

Secret설명
VERCEL_TOKENVercel API 토큰
VERCEL_ORG_IDOrganization / Team ID
VERCEL_PROJECT_ID프로젝트 ID

Inputs

InputRequiredDefaultDescription
vercel-tokenYES-Vercel API 토큰. $\{\{ secrets.VERCEL_TOKEN \}\} 권장
vercel-org-idYES-Vercel Organization ID
vercel-project-idYES-Vercel Project ID
productionNO"false"Production 배포 여부 (false = Preview)
working-directoryNO"/workspace/source"프로젝트 루트 경로

사용 예시

steps:
  - name: Checkout
    uses: collabops/checkout@v2
    with:
      repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      ref: ${{ collabops.ref_name }}
      sha: ${{ collabops.sha }}

  - name: Deploy to Vercel
    uses: collabops/vercel-deploy@v1
    with:
      vercel-token: ${{ secrets.VERCEL_TOKEN }}
      vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
      vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
      production: "false"

Production vs Preview 분기

# push 와 change_request 모두 트리거. 이벤트 별로 Job 을 분리해서 production 여부를 결정.
triggers:
  push:
    branches: [main]
  change_request:
    branches: [main]

jobs:
  deploy-production:
    # main push 일 때만 production. Job 레벨 if 사용.
    if: "collabops.event_name == 'push'"
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: vercel-prod
        uses: "collabops/vercel-deploy@v1"
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          production: "true"

  deploy-preview:
    # CR 이벤트는 preview.
    if: "collabops.event_name == 'change_request'"
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: vercel-preview
        uses: "collabops/vercel-deploy@v1"
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          production: "false"

포인트production 분기는 triggers + Job 별 if: "collabops.event_name == ..." 로 한다. 여러 프로젝트가 한 리포에 있다면 working-directory 로 분리.

collabops/firebase-deploy@v1

On-Premise: ❌ — Firebase SaaS 연결 필요

Firebase 리소스를 배포합니다. Functions, Hosting, Firestore Rules 등 선택적 배포를 지원합니다.

InputRequiredDefaultDescription
service-account-keyYES-GCP 서비스 계정 키 JSON. $\{\{ secrets.FIREBASE_SA_KEY \}\} 권장
project-idYES-Firebase 프로젝트 ID
deploy-targetsNO""배포 대상 (쉼표 구분: functions, hosting, firestore:rules, storage:rules). 미지정 시 전체 배포
working-directoryNO"/workspace/source"firebase.json이 있는 디렉토리

예시

기본 — 전체 firebase.json 배포

jobs:
  deploy:
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: install-and-build
        run: |
          npm ci
          npm run build
        image: node:22-alpine
      # deploy-targets 미지정 → firebase.json 전체 대상이 배포됨.
      - name: firebase-deploy
        uses: "collabops/firebase-deploy@v1"
        with:
          service-account-key: ${{ secrets.FIREBASE_SA_KEY }}
          project-id: my-firebase-project

부분 배포 — 특정 리소스만

jobs:
  deploy-hosting:
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: install-and-build
        run: |
          npm ci
          npm run build
        image: node:22-alpine
      - name: firebase-deploy-targeted
        uses: "collabops/firebase-deploy@v1"
        with:
          service-account-key: ${{ secrets.FIREBASE_SA_KEY }}
          project-id: my-firebase-project
          # 쉼표 구분 — hosting, functions, firestore, storage, ...
          deploy-targets: "hosting,functions"

포인트service-account-key 는 GCP 콘솔에서 받은 JSON 키 전체. deploy-targets 으로 hot path 만 빠르게 배포 가능 (전체 배포는 느림). 모노레포라면 working-directoryfirebase.json 위치 지정.

collabops/fly-deploy@v1

On-Premise: ❌ — Fly.io SaaS 연결 필요

Fly.io 앱을 flyctl을 사용하여 원격 빌드 및 배포합니다.

InputRequiredDefaultDescription
api-tokenYES-Fly.io API 토큰. $\{\{ secrets.FLY_API_TOKEN \}\} 권장
app-nameNO""Fly.io 앱 이름 (미지정 시 fly.toml에서 읽음)
remote-onlyNO"true"원격 빌더 사용 여부
working-directoryNO"/workspace/source"fly.toml이 있는 디렉토리

예시

기본 — fly.toml 의 앱 이름 사용

jobs:
  deploy:
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      # app-name 생략 → 워크스페이스의 fly.toml 에서 app 이름을 읽음.
      - name: fly-deploy
        uses: "collabops/fly-deploy@v1"
        with:
          api-token: ${{ secrets.FLY_API_TOKEN }}

모노레포 — 환경별 앱 + 로컬 빌드

jobs:
  deploy-staging:
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: fly-deploy-staging
        uses: "collabops/fly-deploy@v1"
        with:
          api-token: ${{ secrets.FLY_API_TOKEN }}
          # fly.toml 의 [app] 와 다른 이름으로 배포할 때 명시.
          app-name: web-staging
          # 원격 빌더 대신 Job 컨테이너에서 직접 빌드 — Job 에 BuildKit 가용 시.
          remote-only: "false"
          working-directory: /workspace/source/apps/web

포인트app-name 은 fly.toml 의 [app] 와 다른 이름으로 띄울 때만 명시. remote-only 는 기본 true (Fly 원격 빌더 사용) — 자체 buildx 가 있다면 "false". Job 컨테이너 이미지는 fly 공식 flyio/flyctl 권장.

collabops/convex-deploy@v1

On-Premise: ❌ — Convex SaaS 연결 필요

Convex Functions를 배포합니다. 선택적으로 빌드 커맨드를 실행할 수 있습니다.

InputRequiredDefaultDescription
deploy-keyYES-Convex Deploy Key. $\{\{ secrets.CONVEX_DEPLOY_KEY \}\} 권장
cmdNO""배포 후 실행할 빌드 커맨드
working-directoryNO"/workspace/source"프로젝트 루트 경로

예시

기본 — Convex Functions 배포

jobs:
  deploy:
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: npm-install
        run: npm ci
        image: node:22-alpine
      # deploy-key 는 Convex 대시보드의 Production / Preview deploy key.
      - name: convex-deploy
        uses: "collabops/convex-deploy@v1"
        with:
          deploy-key: ${{ secrets.CONVEX_DEPLOY_KEY }}

배포 후 빌드 명령어 실행

jobs:
  deploy:
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: npm-install
        run: npm ci
        image: node:22-alpine
      - name: convex-deploy-with-cmd
        uses: "collabops/convex-deploy@v1"
        with:
          deploy-key: ${{ secrets.CONVEX_DEPLOY_KEY }}
          # 배포 직후 Next.js 사전렌더링 등 후속 빌드 트리거.
          cmd: "npm run build:next"

포인트deploy-key 는 환경(production/preview) 마다 별도 secret. cmd 는 Convex schema/codegen 이 끝난 뒤 실행되므로 codegen 산출물에 의존하는 빌드(Next.js prerender 등) 에 유용.

collabops/ssh-exec@v1

On-Premise: ✅ — 폐쇄망에서도 동작

원격 호스트에 SSH로 접속해 셸 스크립트를 실행해요. 배포 스크립트 호출, 서비스 재기동, 마이그레이션 등 임의의 명령을 원격에서 돌릴 때 사용해요.

런타임 이미지는 alpine/git:2.43.0 으로 고정되어 있고, 실행 시점에 apk/apt 같은 추가 패키지 설치가 없어요. 그래서 air-gap(폐쇄망) 환경에서도 그대로 동작합니다.

사전 준비

대상 호스트에 접속할 SSH private key 와, ssh-keyscan -p &lt;port&gt; &lt;host&gt; 으로 미리 얻어 둔 known_hosts 값이 필요해요. 두 값 모두 CollabOps 시크릿으로 등록해서 주입하는 것을 권장합니다.

known-hosts 는 MITM (중간자 공격) 을 막기 위한 호스트 키 검증 값이라서 필수입니다. 비워 두면 호스트 키가 바뀌어도 그대로 접속해 버려요.

Secret설명
DEPLOY_SSH_PRIVATE_KEY원격 접속용 OpenSSH private key
DEPLOY_KNOWN_HOSTSssh-keyscan -p &lt;port&gt; &lt;host&gt; 결과

Inputs

InputRequiredDefaultDescription
hostYES-원격 호스트 (IP 또는 도메인)
usernameYES-SSH 사용자명
portNO"22"SSH 포트
ssh-keyYES-SSH private key 내용 (OpenSSH 형식). $\{\{ secrets.DEPLOY_SSH_PRIVATE_KEY \}\} 권장
known-hostsYES-known_hosts 내용. ssh-keyscan -p &lt;port&gt; &lt;host&gt; 결과. MITM (중간자 공격) 방지를 위해 필수
scriptYES-원격에서 실행할 셸 스크립트 (멀티라인 가능). silent fail 방지를 위해 set -eu 가 자동 prepend 되어 bash -s 로 실행됨

사용 예시

steps:
  - name: Restart service over SSH
    uses: collabops/ssh-exec@v1
    with:
      host: ${{ vars.DEPLOY_HOST }}
      username: ${{ vars.DEPLOY_USER }}
      ssh-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
      known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
      script: |
        cd /opt/myapp
        docker compose pull
        docker compose up -d --remove-orphans

여러 호스트에 같은 스크립트 배포

# strategy.matrix 는 현재 미지원. 호스트별로 별도 Job 을 선언해서 병렬 실행 (Job 은 기본 병렬).
jobs:
  restart-web1:
    steps:
      - name: ssh-restart
        uses: "collabops/ssh-exec@v1"
        with:
          host: web1.prod
          username: deploy
          ssh-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
          known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
          script: |
            sudo systemctl restart api
            sudo systemctl status api --no-pager

  restart-web2:
    steps:
      - name: ssh-restart
        uses: "collabops/ssh-exec@v1"
        with:
          host: web2.prod
          username: deploy
          ssh-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
          known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
          script: |
            sudo systemctl restart api
            sudo systemctl status api --no-pager

  restart-web3:
    steps:
      - name: ssh-restart
        uses: "collabops/ssh-exec@v1"
        with:
          host: web3.prod
          username: deploy
          ssh-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
          known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
          script: |
            sudo systemctl restart api
            sudo systemctl status api --no-pager

포인트strategy.matrix 가 미지원이므로 호스트마다 Job 을 명시. Job 은 needs 가 없으면 기본적으로 병렬 실행됨. 모든 호스트의 known-hosts 가 같은 secret 에 포함되어 있어야 함 (ssh-keyscan 묶음).

collabops/scp-upload@v1

On-Premise: ✅ — 폐쇄망에서도 동작

원격 호스트로 SCP 를 통해 파일이나 디렉토리를 업로드해요. compose 파일·정적 자산·빌드 산출물을 배포 서버로 옮길 때 사용해요.

런타임 이미지는 alpine/git:2.43.0 으로 고정되어 있고 실행 시점 추가 패키지 설치가 없어요. air-gap (폐쇄망) 환경에서도 그대로 사용할 수 있습니다.

known-hosts 는 MITM (중간자 공격) 방지를 위한 호스트 키 검증 값으로 ssh-exec 과 동일하게 필수입니다. 두 값 모두 CollabOps 시크릿으로 등록해서 주입하는 것을 권장합니다.

Secret설명
DEPLOY_SSH_PRIVATE_KEY원격 접속용 OpenSSH private key
DEPLOY_KNOWN_HOSTSssh-keyscan -p &lt;port&gt; &lt;host&gt; 결과

Inputs

InputRequiredDefaultDescription
hostYES-원격 호스트 (IP 또는 도메인)
usernameYES-SSH 사용자명
portNO"22"SSH 포트
ssh-keyYES-SSH private key 내용 (OpenSSH 형식). $\{\{ secrets.DEPLOY_SSH_PRIVATE_KEY \}\} 권장
known-hostsYES-known_hosts 내용. ssh-keyscan -p &lt;port&gt; &lt;host&gt; 결과. MITM (중간자 공격) 방지를 위해 필수
sourceYES-업로드할 로컬 경로 (파일 또는 디렉토리). /workspace/source 기준 절대 경로 권장
targetYES-원격 대상 경로. 디렉토리인 경우 trailing slash 권장. scp user@host:path 문법 한계로 공백 포함 경로는 미지원
recursiveNO"false"디렉토리 재귀 업로드 (true/false). 디렉토리 source 시 true 필요

사용 예시

steps:
  - name: Upload compose file
    uses: collabops/scp-upload@v1
    with:
      host: ${{ vars.DEPLOY_HOST }}
      username: ${{ vars.DEPLOY_USER }}
      ssh-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
      known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
      source: "/workspace/source/docker-compose.yml"
      target: "/opt/myapp/docker-compose.yml"

디렉토리 업로드 후 ssh-exec 으로 재시작

jobs:
  release:
    steps:
      - name: checkout
        uses: "collabops/checkout@v2"
        with:
          repo-url: "https://<collabops-host>/<workspace>/<repository>.git"
      - name: build-dist
        run: |
          npm ci && npm run build       # → dist/ 생성
        image: node:22-alpine
      # 1) 빌드 산출물을 원격으로 푸시
      - name: upload-dist
        uses: "collabops/scp-upload@v1"
        with:
          host: web1.prod
          username: deploy
          ssh-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
          known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
          source: dist
          target: /var/www/app/current
      # 2) 원격 nginx reload
      - name: reload-nginx
        uses: "collabops/ssh-exec@v1"
        with:
          host: web1.prod
          username: deploy
          ssh-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
          known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
          script: |
            sudo nginx -t
            sudo systemctl reload nginx

포인트 — scp 직후 같은 호스트에 ssh-exec 으로 reload/restart 를 묶는 패턴이 흔함. 두 Step 이 같은 secret 을 공유 — Job 단위에서 한번만 인증 정보 준비.

목차