Gitea Actions는 iOS 앱을 빌드할 수 있지만, Gitea는 hosted runner를 제공하지 않고 Xcode는 macOS에서만 실행되므로 직접 등록한 Mac에서만 가능합니다. 실질적인 두 가지 설정 방법은 Mac을 act_runner host mode로 실행하거나, 일반 Linux runner에서 iOS build를 cloud build 서비스로 전송하는 방식입니다. 이 가이드에서는 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@v4Gitea가 기본적으로 GitHub에서 actions를 가져오기 때문에(해당DEFAULT_ACTIONS_URL설정). 에어갭(air-gapped) 인스턴스는 사용하는 actions를 미러링하고 전체 URL로 참조해야 합니다.
옵션 1: act_runner를 사용하는 Mac runner
Mac 등록하기
다운로드하세요: act_runner Gitea releases 페이지에서 macOS용 binary(Apple Silicon은 arm64)를 받고, 이어서 등록 토큰을 다음에서 가져오세요: 사이트 관리 > Actions > Runners (인스턴스 전체), 또는 조직이나 저장소 설정에서 가져올 수 있습니다.
./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를 통해 데몬을 실행하세요.
호스트에 toolchain 설치
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, 또는
Gemfilewithbundle exec - CocoaPods (iOS 프로젝트에서 여전히 사용하는 경우에만). 최신 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
The 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 러너에서 그대로 유지됩니다. code를 체크아웃하고, 웹 레이어를 빌드하며, 실행하고, cap sync ios호출합니다 build request. Capgo CLI는 준비된 native project를 upload하고, Xcode logs를 Gitea job log로 스트리밍하며, 실패 시 non-zero로 종료됩니다.
이 모델은 self-hosted Gitea에 매우 적합합니다:
- 인바운드 액세스 불필요. Capgo는 repository를 clone하지 않습니다. Runner가 준비된 project를 HTTPS를 통해 외부로 push하므로, 방화벽이나 VPN 뒤에 있는 Gitea server도 설정 변경 없이 작동합니다.
- Repository token 불필요. Capgo 측에서 구성할 Git connection이 없습니다.
- 웹 소스는 로컬에 유지됩니다. native
ios/folder, Capacitor config, 그리고 plugin의 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 issuer ID |
APPLE_KEY_CONTENT |
Base64 .p8 key |
APP_STORE_CONNECT_TEAM_ID |
Apple 팀 ID |
Gitea는 다음으로 시작하는 secret 및 variable 이름을 거부합니다: GITEA_ 또는 GITHUB_, 따라서 이 이름들을 그대로 유지하세요. app ID와 같은 비민감 데이터는 Variables 에 넣고 ${{ vars.NAME }}로 읽어오세요. 조직(organization) 레벨에서 설정된 Secrets는 해당 조직 내 모든 repository에서 공유되므로, 하나의 Apple team에서 여러 앱을 배포할 때 매우 유용합니다. 작업이 끝나면 내보낸 파일은 삭제하세요.
태그 푸시 시 릴리스
# .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를 설정하므로, Capgo CLI이 대화형 입력을 기다리는 일은 없습니다. - Capgo는 최신 TestFlight 빌드에서 빌드 번호를 증가시키므로, 연속된 두 태그가 충돌하지 않습니다.
- 사용하지 않으면
--submit-to-store-review빌드가 TestFlight에 업로드된 후 중단됩니다. CI가 App Review 제출까지 수행하게 하려면 해당 플래그를 추가하세요.
이제 릴리스는 git tag v2.3.0 && git push origin v2.3.0입니다. 태그를 푸시할 수 있는 권한이 있다면 v* 를 배포할 수 있으므로, 해당 태그에 보호 규칙을 추가하세요. 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로 인코딩하여 secret (ADHOC_PROFILE_BASE64 위)에 저장한 뒤, job에서 디코딩하여 다음을 통해 전달하세요: --ios-provisioning-profile. CLI 플래그는 환경 변수보다 우선순위가 높으므로, 이 빌드에 대해서만 App Store 프로필을 덮어씁니다. UDID는 iOS UDID 찾기를 사용하여 수집하세요. 대부분의 테스터에게는 UDID가 필요 없는 TestFlight 내부 테스트가 더 간편합니다.
수동 릴리스 승인
GitHub를 사용하면 환경별로 리뷰어를 지정할 수 있습니다. Gitea Actions는 환경 보호 규칙(environment protection rules)을 지원하지 않으므로, 환경 이름을 지정한 job이 승인을 위해 일시 중지되지는 않습니다. 대신 다음 두 가지 패턴을 사용할 수 있습니다:
- 보호된 태그. 릴리스 매니저만 태그를
v*푸시할 수 있으며, 태그 워크플로우가 실행됩니다. - 빌드 후 프로모션하세요. 푸시할 때마다 빌드하여
mainwith--ios-distribution ad_hocTestFlight 내부 테스트용으로만 업로드하거나, 이후workflow_dispatchworkflow (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 native dependencies가 채널의 현재 상태와 일치하면 0을, 새로운 binary가 필요하면 1을 반환합니다. 또한 파일이 변경되어도 ios/ 또는 capacitor.config.* 의존성 비교 대상이 아니므로 native path를 강제합니다. 자세한 내용은 live update 자동 선택 또는 Native Build.
Mac runner vs cloud build
| Mac의 act_runner | Capgo Linux에서 Build | |
|---|---|---|
| 하드웨어 | 직접 구매 및 호스팅 | 없음 |
| Xcode 업그레이드 | 기기별 수동 작업 | Capgo에서 처리 |
| 작업 간 격리 | 호스트 모드에서는 없음 | 작업마다 새로운 빌드 환경 |
| 병렬 빌드 | Mac당 1개 | 모든 러너에서 여러 개 가능 |
| 서명 파일 | Gitea secrets에 저장 후 Mac에서 디코딩 | Gitea secrets에 저장 후 빌드 시 전송 |
| 방화벽 환경에서도 작동 | 예 | 네, 아웃바운드 HTTPS만 지원합니다 |
| Native Swift 앱 (Capacitor 미포함) | 예 | 아니요, Capacitor 앱만 지원합니다 |
| 빌드 시간 제한 | 직접 설정 가능 | 빌드당 10분 |
문제 해결
| 증상 | 해결 |
|---|---|
| 작업이 계속 대기 상태로 머무름 | 해당 라벨을 가진 온라인 러너가 없음 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 하고 다시 export 하세요. |
| SDK 버전 업로드 거부됨 | Mac runner의 Xcode 버전이 26보다 낮습니다. |
cap sync ios Linux 환경의 CocoaPods 경고 |
정상입니다. Capgo 빌드 머신이 pods를 설치합니다. |
다른 실패 사례는 다음을 참조하세요: Capacitor를 위한 CI/CD: 흔히 발생하는 실수 및 Capgo 빌드 트러블슈팅 가이드.
요약
Xcode 실행 환경만 결정하면 Gitea Actions로 iOS를 원활하게 관리할 수 있습니다. 호스트 모드 Mac runner를 사용하면 모든 제어권과 책임을 직접 가질 수 있습니다. Capgo Build를 호출하는 Linux job을 사용하면 Gitea 설정을 변경할 필요가 없고, 인바운드 액세스도 필요 없으며, TestFlight 릴리스를 태그 푸시로 자동화할 수 있습니다. 어떤 방식을 선택하든 서명 관련 자료는 Gitea secrets에 보관하고, 릴리스 태그를 보호하세요. 또한 웹 관련 변경 사항은 Live Update로 배포하여 Native Build 파이프라인은 꼭 필요할 때만 실행되도록 하세요.