본문 바로가기

Gitea Actions로 iOS 앱 빌드 및 배포하기

Gitea Actions로 iOS 앱을 빌드하고 서명해 TestFlight에 업로드하세요: act_runner를 사용하는 셀프 호스팅 Mac runner 또는 Linux runner에서의 클라우드 빌드.

Gitea Actions 워크플로우로 TestFlight용 iOS 앱 서명 및 빌드하기

Gitea Actions는 iOS 앱을 빌드할 수 있지만, Gitea에는 호스팅 runner가 없고 Xcode는 macOS에서만 실행되므로 직접 등록한 Mac에서만 빌드할 수 있습니다. 실용적인 두 가지 구성은 Mac을 act_runner 호스트 모드로 실행하거나, iOS 빌드를 클라우드 빌드 서비스로 보내는 일반 Linux runner를 사용하는 방식입니다. 이 가이드에서는 Capacitor 앱을 위해 두 방법을 모두 다루며, 바로 넣을 수 있는 workflow 파일도 함께 제공합니다. .gitea/workflows/.

팀들이 Gitea를 고르는 이유는 직접 관리하는 인프라에서 code 운영을 원하기 때문입니다. 앱에 Xcode가 필요해졌다고 해서 이게 바뀌어선 안 됩니다. 아래 두 가지 구성 모두 소스를 서버에 그대로 둡니다.

Gitea Actions의 작업 실행 방식

Gitea Actions는 GitHub Actions와 호환되는 구문을 사용하고, 작업은 다음에서 실행됩니다: act_runner Gitea 서버에 등록한 인스턴스입니다. 각 runner는 다음과 같은 label을 노출합니다: ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest. 콜론(:) 뒤의 부분이 작업 실행 방식을 결정합니다:

  • docker://image 작업을 컨테이너 안에서 실행합니다. Linux runner에서는 이게 기본입니다.
  • host 머신에 설치된 환경 그대로 작업을 직접 실행합니다.

macOS는 Docker 컨테이너에서 실행할 수 없으므로 Mac runner는 host label을 사용해야 합니다. 그로 인해 생기는 결과는 뒤에서 다룹니다.

GitHub에서 workflow를 복사하기 전에 알아둘 호환성 참고 사항 두 가지:

  • Gitea는 다음을 읽고, .gitea/workflows/ 없으면 다음으로 대체합니다: .github/workflows/ 첫 번째 폴더가 존재하지 않을 때입니다.
  • uses: actions/checkout@v4 Gitea가 기본적으로 GitHub에서 actions를 찾아 해결하기 때문에(해당 DEFAULT_ACTIONS_URL 설정 때문) 작동합니다. 폐쇄망(Air-gapped) 인스턴스는 사용하는 actions를 미러링하고 전체 URL로 참조해야 합니다.

옵션 1: act_runner를 사용하는 Mac runner

Mac 등록하기

다음을 다운로드한 뒤 act_runner Gitea releases 페이지에서 macOS용 binary(Apple Silicon의 경우 arm64)를 다운로드한 후, 다음 경로에서 등록 토큰을 가져옵니다. Site Administration > Actions > Runners (인스턴스 전체), 또는 조직(organization)이나 저장소(repository) 설정에서 가져올 수 있습니다.

./act_runner register --no-interactive \
  --instance https://gitea.example.com \
  --token <registration_token> \
  --name mac-mini-01 \
  --labels macos-arm64:host

./act_runner daemon

데몬을 전용 사용자 계정의 launch agent로 실행해, 재부팅 후 자동으로 재시작되고 자체 로그인 키체인을 갖도록 하세요.

호스트에 툴체인 설치

Host mode는 작업이 Mac 자체 도구를 사용한다는 뜻입니다. 다음을 설치하세요:

  • Xcode 26(2026년 4월 28일부터 App Store Connect 업로드에 필수) 및 실행: sudo xcodebuild -license accept
  • Node.js 22 이상 및 Bun (Capacitor 8은 Node 22 필요)
  • Ruby와 fastlane, 또는 Gemfile 포함 bundle exec
  • iOS 프로젝트에서 아직 사용하는 경우에만 CocoaPods를 사용하세요. 새 Capacitor 8 프로젝트는 Swift Package Manager를 사용합니다.

워크플로우

# .gitea/workflows/ios-mac.yaml
name: iOS release (Mac runner)

on:
  push:
    tags:
      - 'v*'

jobs:
  ios:
    runs-on: macos-arm64
    env:
      APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
      APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
      APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
      P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
      APP_STORE_CONNECT_TEAM_ID: ${{ vars.APP_STORE_CONNECT_TEAM_ID }}
      PROFILE_NAME: ${{ vars.IOS_PROFILE_NAME }}
    steps:
      - uses: actions/checkout@v4
      - run: bun install --frozen-lockfile
      - run: bun run build
      - run: bunx cap sync ios
      - name: Decode signing files
        run: |
          echo "${{ secrets.IOS_CERTIFICATE_BASE64 }}" | base64 --decode > dist.p12
          echo "${{ secrets.IOS_PROFILE_BASE64 }}" | base64 --decode > profile.mobileprovision
      - run: bundle exec fastlane ios beta
      - name: Clean up
        if: always()
        run: rm -f dist.p12 profile.mobileprovision

이 beta lane은 인증서를 임시 키체인으로 가져오고, 프로젝트를 수동 서명으로 전환하고, TestFlight 기준으로 빌드 번호를 올린 다음 아카이브하고 업로드합니다. Capacitor 프로젝트용 전체 Fastfile은 여기에 있습니다. Bitbucket Pipelines로 iOS 앱 빌드 및 배포하기; Gitea에서도 동일하게 작동합니다. fastlane 관련 내용은 다음을 참고하세요: GitHub Actions iOS 가이드.

호스트 모드 사용 시 발생하는 비용

이 act_runner 문서에는 이 점이 명확히 명시되어 있습니다. 호스트 작업은 격리되지 않습니다. 실제로는 다음과 같습니다:

  • 모든 작업이 동일한 사용자, 디스크, 키체인을 공유합니다. 작업이 중간에 중단되면 임시 키체인이나 오래된 프로비저닝 프로파일이 남아 다음 작업을 방해할 수 있습니다.
  • 해당 라벨을 대상으로 하는 모든 워크플로우는 해당 사용자의 권한으로 해당 Mac에서 code를 실행합니다. 신뢰할 수 있는 저장소에만 호스트 러너를 등록하고, 포크된 저장소의 pull request에서는 실행을 피하세요.
  • DerivedData, 아카이브, 시뮬레이터 런타임이 쌓여 디스크가 가득 찰 수 있습니다.
  • Xcode 업그레이드는 직접 관리해야 하며, Apple은 매년 봄마다 최소 SDK 요구 사양을 높입니다.
  • Mac 한 대는 한 번에 하나의 작업만 빌드할 수 있습니다. 출시일에는 대기열이 발생합니다.

팀에서 이미 Mac을 운영 중이고 관리자가 있다면 이 방식이 적합합니다. 그렇지 않다면, 다음 옵션은 인프라에서 Mac을 제외할 수 있는 방법입니다.

옵션 2: Capgo Build를 사용하여 Linux 러너에서 iOS 빌드하기

Capgo 빌드 Capgo에서 관리하는 Mac에서 Capacitor 앱을 컴파일하고 서명합니다. Gitea 작업은 기존에 사용하던 Linux runner에서 그대로 유지됩니다. code를 체크아웃하고, 웹 레이어를 빌드하며, 실행한 뒤 cap sync ios, 호출합니다 build request. Capgo CLI는 준비된 native project를 upload하고, Xcode logs를 Gitea job log로 스트리밍하며, 실패 시 non-zero로 종료합니다.

이 모델은 self-hosted Gitea에 매우 적합합니다:

  • 인바운드 액세스 불필요. Capgo는 저장소를 clone하지 않습니다. runner가 준비된 project를 HTTPS를 통해 외부로 push하므로, 방화벽이나 VPN 뒤에 있는 Gitea 서버도 설정 변경 없이 작동합니다.
  • 저장소 token 불필요. Capgo 측에서 설정할 Git 연결이 없습니다.
  • 웹 소스는 내부 서버에 유지됩니다. 오직 native ios/ 폴더, Capacitor config, 그리고 플러그인의 native 파트만 upload됩니다. src/, .git/, 그리고 .env 파일은 포함되지 않습니다.

초기 설정

개발자 머신에서:

bunx @capgo/cli@latest login
bunx @capgo/cli@latest app add
bunx @capgo/cli@latest build init --platform ios

build init 배포 인증서와 App Store 프로비저닝 프로파일을 생성하거나 재사용하고, App Store Connect API 키를 저장합니다. 서명이 모두 정상인지 확인하려면 로컬에서 빌드를 한 번 실행하세요:

bun run build && bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release

그런 다음 인증 정보를 내보내세요:

bunx @capgo/cli@latest build credentials manage --appId com.example.app --platform ios
# select "Export to .env"

Gitea에 Secrets 추가

열기 저장소 설정 > Actions > Secrets 내보낸 파일의 각 키와 Capgo API 키를 추가하세요:

Secret 목적
CAPGO_TOKEN 업로드 권한이 있는 Capgo API 키
BUILD_CERTIFICATE_BASE64 배포 인증서 (.p12)
P12_PASSWORD 인증서 비밀번호
CAPGO_IOS_PROVISIONING_MAP_BASE64 Provisioning profile 매핑
APPLE_KEY_ID App Store Connect API 키 ID
APPLE_ISSUER_ID App Store Connect 발급자 ID
APPLE_KEY_CONTENT Base64 .p8 키
APP_STORE_CONNECT_TEAM_ID Apple 팀 ID

Gitea는 다음으로 시작하는 secret 및 variable 이름을 거부하므로 GITEA_ 또는 GITHUB_이름을 그대로 유지하세요. app ID와 같은 non-secret 값은 아래에 넣으세요 변수 및 다음 명령어로 읽어옵니다. ${{ vars.NAME }}. 조직(organization) 레벨에서 설정된 Secret은 해당 조직 내 모든 레포지토리에서 공유되므로, 하나의 Apple 팀에서 여러 앱을 배포할 때 매우 유용합니다. 작업이 끝나면 내보낸 파일은 삭제해 주세요.

태그 푸시 시 릴리스

# .gitea/workflows/ios-release.yaml
name: iOS release

on:
  push:
    tags:
      - 'v*'

jobs:
  release-ios:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - run: bun run build
      - run: bunx cap sync ios
      - name: Build, sign, and upload to TestFlight
        env:
          CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          CAPGO_IOS_PROVISIONING_MAP_BASE64: ${{ secrets.CAPGO_IOS_PROVISIONING_MAP_BASE64 }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
        run: |
          bunx @capgo/cli@latest build request ${{ vars.APP_ID }} \
            --platform ios \
            --build-mode release \
            --ios-distribution app_store \
            --store-release-name "${{ gitea.ref_name }}"

참고:

  • ${{ gitea.ref_name }} 는 태그 이름입니다. github.* context는 별칭으로도 동작하므로 워크플로를 이식할 때 유용합니다.
  • Gitea는 CI=true 를 모든 job에 설정하므로, Capgo CLI는 대화형 입력을 기다리지 않습니다.
  • Capgo는 최신 TestFlight 빌드에서 빌드 번호를 증가시키므로, 연속된 두 태그가 충돌하지 않습니다.
  • 없이 --submit-to-store-review 없이 빌드하면 TestFlight에 올라간 뒤 멈춥니다. CI가 App Review 제출까지 하게 하려면 해당 플래그를 추가하세요.

이제 릴리스는 git tag v2.3.0 && git push origin v2.3.0입니다. v* tag를 푸시할 수 있는 사람은 누구나 배포할 수 있으므로, 보호 태그 규칙을 추가하세요: v* 하위 Settings > Tags 릴리스 매니저로 권한을 제한하세요.

테스터를 위한 Ad hoc 빌드

등록된 기기에 직접 설치하는 QA 빌드의 경우, ad hoc 모드로 빌드하여 다운로드 링크를 생성할 수 있습니다:

# .gitea/workflows/ios-adhoc.yaml
name: iOS ad hoc build

on:
  workflow_dispatch:

jobs:
  adhoc:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile && bun run build
      - run: bunx cap sync ios
      - name: Ad hoc build
        env:
          CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
        run: |
          echo "${{ secrets.ADHOC_PROFILE_BASE64 }}" | base64 --decode > adhoc.mobileprovision
          bunx @capgo/cli@latest build request ${{ vars.APP_ID }} \
            --platform ios \
            --ios-distribution ad_hoc \
            --ios-provisioning-profile ./adhoc.mobileprovision \
            --output-upload --output-retention 3d \
            --output-record /tmp/build.json
          bunx @capgo/cli@latest build last-output --path /tmp/build.json --field outputUrl

Ad hoc 빌드에는 테스트 기기 목록이 포함된 ad hoc provisioning profile이 필요합니다. 해당 프로필을 base64로 인코딩하여 시크릿(ADHOC_PROFILE_BASE64 위 참조)에 저장한 뒤, 작업(job) 내에서 디코딩하여 다음 항목과 함께 전달하세요: --ios-provisioning-profile. CLI 플래그는 환경 변수보다 우선순위가 높으므로, 이 빌드에 대해서만 App Store 프로필을 덮어씁니다. UDID는 다음을 통해 수집하세요: iOS UDID 찾기. 대부분의 테스터에게는 UDID가 필요 없는 TestFlight 내부 테스트가 더 간편합니다.

수동 릴리스 승인

GitHub를 사용하면 특정 환경에 리뷰어를 지정할 수 있습니다. Gitea Actions는 환경 보호 규칙을 지원하지 않으므로, 환경 이름이 지정된 작업이 승인을 위해 일시 중지되지 않습니다. 대신 다음 두 가지 패턴을 사용할 수 있습니다:

  1. 보호된 태그 사용. 릴리스 매니저만 태그를 v* 푸시할 수 있으며, 태그 워크플로우를 통해 배포합니다.
  2. 빌드 후 승인(Promote). 푸시할 때마다 빌드하거나, main 포함 --ios-distribution ad_hoc TestFlight 내부 테스트로만 업로드한 뒤, 현재 Gitea 릴리스에서 사용 가능한 workflow_dispatch workflow (available in current Gitea releases) to rebuild the approved commit with --submit-to-store-review.

새 바이너리 없이 웹 변경사항 배포

Capacitor 앱의 대부분의 커밋은 JavaScript, CSS, HTML만 변경합니다. 이런 변경사항 때문에 iOS를 매번 다시 빌드하는 것은 시간과 App Review 리소스를 낭비하는 일입니다. Capacitor를 사용하면 Capgo Live Update 사용하기하나의 작업으로 적절한 경로를 선택할 수 있습니다:

      - name: Live update or native build
        env:
          CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }}
          # plus the iOS signing secrets from above
        run: |
          if bunx @capgo/cli@latest build needed ${{ vars.APP_ID }} --channel production; then
            bunx @capgo/cli@latest bundle upload ${{ vars.APP_ID }} --channel production
          else
            bunx cap sync ios
            bunx @capgo/cli@latest build request ${{ vars.APP_ID }} --platform ios --build-mode release
          fi

build needed exits 0 when native dependencies match what is live on the channel, and 1 when a new binary is required. Also force the native path when files under ios/ 또는 capacitor.config.* 변경되면 의존성 비교 대상이 아니므로 네이티브 경로를 강제로 실행합니다. 자세한 내용은 live update 또는 Native Build 자동 선택.

Mac runner vs 클라우드 빌드

Mac에서 act_runner를 확인하세요 Capgo Linux 빌드
하드웨어 직접 구매 및 호스팅 없음
Xcode 업그레이드 기기별 수동 작업 Capgo에서 처리
작업 간 격리 호스트 모드 시 없음 작업마다 새로운 빌드 환경
병렬 빌드 Mac당 1개 모든 러너에서 여러 개 가능
서명 파일 Gitea secrets에 저장 후 Mac에서 디코딩 빌드 시마다 전송되는 Gitea secrets
방화벽 환경에서도 작동 지원 네, 아웃바운드 HTTPS만 가능합니다
네이티브 Swift 앱 (Capacitor 미포함) 지원 아니요, Capacitor 앱만 가능합니다
빌드 시간 제한 직접 설정 가능 빌드당 10분

문제 해결

증상 해결 방법
Job이 계속 대기 상태로 남음 해당 라벨을 가진 온라인 runner가 없음 runs-on. Runner 목록과 label 철자를 확인하세요.
uses: actions/checkout@v4 다운로드 실패 Runner가 GitHub에 접속할 수 없습니다. Gitea에서 동일한 작업을 수행하고 전체 URL로 참조하세요.
Job 내 Secret 값이 비어 있습니다 이름이 다음으로 시작합니다: GITEA_/GITHUB_, 또는 Secret이 다른 repository에 정의되어 있습니다.
security: SecKeychainItemImport: MAC verification failed 잘못된 .p12 비밀번호, 또는 .p12 OpenSSL 3에서 생성되었으며 -legacy. 다시 export 하세요.
No profiles for 'com.example.app' were found Secrets에 설정된 프로필이 Bundle ID 또는 인증서와 일치하지 않습니다. 다시 실행하세요. build init 다시 내보내세요.
SDK 버전 업로드 거부됨 Mac runner의 Xcode 버전이 26보다 낮습니다.
cap sync ios Linux 환경의 CocoaPods 관련 경고 정상입니다. Capgo 빌드 머신이 pods를 설치합니다.

다른 오류 유형은 다음을 참조하세요: Capacitor를 위한 CI/CD: 흔한 함정 및 Capgo 빌드 트러블슈팅 가이드.

요약

Xcode 실행 환경만 결정하면 Gitea Actions로 iOS를 원활하게 관리할 수 있습니다. Host-mode Mac runner를 사용하면 모든 제어권과 책임을 직접 가질 수 있습니다. 반면 Capgo Build를 호출하는 Linux job을 사용하면 Gitea 설정을 그대로 유지하면서 인바운드 액세스 없이도 태그 푸시만으로 TestFlight 릴리스를 진행할 수 있습니다. 어떤 방식을 선택하든 서명 관련 자료는 Gitea secrets에 보관하고, 릴리스 태그를 보호하며, 웹 관련 변경 사항은 Live Update로 배포하여 Native Build 파이프라인은 꼭 필요할 때만 실행되도록 하세요.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.