Guía

Configurar CI/CD de iOS en GitHub Actions desde cero

Este es el pipeline completo de una app iOS en GitHub Actions, en el orden en que lo montas. Primero compilar y probar. Luego la firma. Luego archivar y subir. Cada paso es pequeño y el YAML está listo para copiar. Funciona en runners alojados de GitHub y en un Mac dedicado. La única línea que cambia es runs-on.

1. El esqueleto

name: ios
on:
  pull_request:
  push:
    branches: [main]
concurrency:
  group: ios-${{ github.ref }}
  cancel-in-progress: true
jobs:
  build:
    runs-on: macos-latest      # or [self-hosted, macOS, your-label]
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4

2. Elige el Xcode

Las imágenes alojadas traen varias versiones de Xcode. Elige una de forma explícita para que una actualización de la imagen no te cambie el build sin avisar:

      - run: sudo xcode-select -s /Applications/Xcode_26.6.app
      - run: xcodebuild -version

En un Mac dedicado, la versión se fija una vez en la máquina y este paso desaparece.

3. Dependencias

      - uses: actions/cache@v4
        with:
          path: |
            ~/Library/Caches/org.swift.swiftpm
            Pods
          key: ${{ runner.os }}-deps-${{ hashFiles('**/Package.resolved', '**/Podfile.lock') }}
      - run: pod install --repo-update   # CocoaPods projects only

4. Build y tests en un simulador

      - run: |
          xcodebuild test \
            -scheme MyApp \
            -destination 'platform=iOS Simulator,name=iPhone 17' \
            -resultBundlePath TestResults \
            | xcpretty
      - uses: actions/upload-artifact@v4
        if: failure()
        with: { name: test-results, path: TestResults }

Para las pull requests, para aquí. Todo lo que sigue es trabajo de release y va solo en main.

5. Firma de código sin compartir una cuenta del Mac

Usa una API key de App Store Connect en lugar de un Apple ID. Guarda el key ID, el issuer ID y el contenido del .p8 como secrets del repositorio. Y deja que xcodebuild firme con perfiles gestionados en la nube:

      - run: echo "${{ secrets.ASC_KEY_P8 }}" > AuthKey.p8
      - run: |
          xcodebuild archive \
            -scheme MyApp -configuration Release \
            -archivePath build/MyApp.xcarchive \
            -allowProvisioningUpdates \
            -authenticationKeyPath "$PWD/AuthKey.p8" \
            -authenticationKeyID "${{ secrets.ASC_KEY_ID }}" \
            -authenticationKeyIssuerID "${{ secrets.ASC_ISSUER_ID }}"

6. Exporta y sube a TestFlight

      - run: |
          xcodebuild -exportArchive \
            -archivePath build/MyApp.xcarchive \
            -exportPath build/export \
            -exportOptionsPlist ExportOptions.plist \
            -allowProvisioningUpdates \
            -authenticationKeyPath "$PWD/AuthKey.p8" \
            -authenticationKeyID "${{ secrets.ASC_KEY_ID }}" \
            -authenticationKeyIssuerID "${{ secrets.ASC_ISSUER_ID }}"
      - run: |
          xcrun altool --upload-app -f build/export/MyApp.ipa -t ios \
            --apiKey "${{ secrets.ASC_KEY_ID }}" --apiIssuer "${{ secrets.ASC_ISSUER_ID }}"

Muchos equipos envuelven los pasos 5 y 6 en fastlane. Los comandos de debajo son los mismos.

7. Adónde van los minutos, y qué runner elegir

En un runner alojado, los pasos 2 y 3 se repiten en cada job y se cobran a unos $0.062 a $0.102 el minuto. En un Mac dedicado se hacen una vez y las cachés siguen en caliente. Por eso los equipos que pasan de unos 2,242 minutos al mes llevan todo el pipeline a una máquina de precio fijo. Solo cambian la línea runs-on. La guía del pipeline en hardware dedicado cubre esa parte.

Preguntas frecuentes

¿Necesito un Mac para el CI de iOS en GitHub Actions?

+

Sí. Compilar y firmar apps iOS requiere Xcode, que solo funciona en macOS. Así que el job de build necesita un runner de macOS, alojado en GitHub o un Mac que tú controles.

¿Cómo firmo una app iOS en CI sin mi Apple ID?

+

Crea una API key de App Store Connect. Guarda su ID, el issuer ID y el contenido del .p8 como secrets, y pásaselos a xcodebuild con -allowProvisioningUpdates. En el runner no hace falta ningún Apple ID ni contraseña del llavero.

¿Deben ejecutarse los tests en cada pull request?

+

Compila y prueba en cada pull request contra un simulador. Deja el archivo, la firma y la subida a TestFlight solo para main. Son los pasos lentos y caros.

¿Qué cambia cuando llevo este pipeline a un Mac dedicado?

+

La línea runs-on. La selección de Xcode y la caché de dependencias dejan de hacer falta, porque la máquina las conserva entre jobs. Y no hay cola delante del build.

Guías relacionadas