Saltar al contenido principal
CI/CD

Build automático de Capacitor para iOS con GitHub Actions usando match

Cómo configurar un pipeline de CI/CD para tu app Ionic para iOS usando fastlane y GitHub Actions en 5 min (2022)

Build automático de Capacitor para iOS con GitHub actions usando match Capgo ilustración

Builds automáticos para iOS con GitHub Actions usando Match

Configurar CI/CD para apps de Capacitor puede ser complejo y llevar mucho tiempo. Esto es lo que necesitas saber:

Ahora recomendamos usar Capgo Build con el Capgo CLI para builds nativos de Capacitor. Esta guía de Fastlane Match se mantiene para equipos que conservan pipelines existentes de GitHub Actions, pero los nuevos builds de iOS deberían usar el Capgo CLI para no tener que mantener tú mismo Fastlane, repositorios de Match, runners de Xcode, certificados y scripts de upload.

Capgo Build para CI/CD por Capgo

Olvídate de mantener Fastlane Match, runners de Xcode, certificados, provisioning profiles y scripts de upload. Build de Capgo ejecuta builds nativos de iOS firmados desde tu pipeline de CI/CD actual:

  • Funciona con tu pipeline: Dispara Capgo Build desde GitHub Actions, GitLab CI, Jenkins o scripts locales tras tu web build y npx cap sync.
  • Firma mediante secretos de CI: Mantén las claves de App Store Connect, certificados, provisioning profiles, contraseñas y team IDs en tus propios secretos de CI.
  • Sin mantenimiento de runners nativosCapgo Build ofrece entornos de compilación de Apple mantenidos, para que no tengas que gestionar runners de macOS, imágenes de Xcode, Fastlane o repositorios de Match.
  • Artifacts y envío: Descarga artifacts firmados para QA o envía builds de release mediante Capgo CLI.

Precios

  • Los planes de Capgo desde $12/mes
  • Incluye actualizaciones OTA y unos 15 Native Build al mes
  • Los minutos de build extra se cobran por minuto mediante créditos

Configura Capgo Build en tu CI/CD

Guía de configuración manual

Esto es lo que tienes que hacer:

Entrega continua para iOS con Fastlane y GitHub Actions usando match

Requisitos

Antes de seguir con el tutorial…

  • Asegúrate de tener Fastlane instalado en tu máquina de desarrollo.
  • Membresía del programa de desarrolladores de iOS.
  • Ganas de leer 😆…
  • Un equipo con muchos devs; si no, te recomendamos usar fastlane cert para flujos de trabajo más simples.

Información importante sobre el precio

Precio de GitHub Action

https://github.com/features/actions

El servicio es «gratis» hasta el límite, según la máquina elegida.
Vamos a utilizar un macOS máquina, en la captura puedes ver su precio y límites (precios vigentes al crear el tutorial, podrían cambiar en el futuro)

🔴 Tras aclarar requisitos y precios, si te parece, continuamos…

📣 En el post asumimos que la app ya está creada en iTunes Connect y que tenemos los certificados del ecosistema Apple; ¡Fastlane lo copiará todo!

¡Vamos a ello! 🤿

Pasos a seguir en el post

  1. Usar App Store Connect API con Fastlane Match
  2. Requisitos
  3. Crear una clave API de App Store Connect
  4. Usar una clave API de App Store Connect
  5. Copiar archivos de Fastlane
  6. Configurar Fastlane match

1. Usar API de App Store Connect con Fastlane Match

A partir de febrero de 2021, todos los usuarios deben usar la autenticación de dos factores o la verificación en dos pasos para iniciar sesión en App Store Connect. Esta capa adicional de seguridad para tu Apple ID ayuda a garantizar que seas la única persona que pueda acceder a tu cuenta.
Desde Soporte de Apple

Para empezar con match, primero debes revocar tus certificados actuales. Pero no te preocupes, obtendrás los nuevos directamente.

Requisitos

Para poder usar API de App Store Connect, Fastlane necesita tres cosas.

  1. ID del emisor.
  2. ID de la clave.
  3. Archivo o contenido de la clave.

Crear una clave API de App Store Connect

Para generar claves, debes tener permiso de Admin en App Store Connect. Si no lo tienes, puedes indicarle a la persona correspondiente que consulte este artículo y seguir estas instrucciones.

1 — Inicia sesión en App Store Connect.

2 — Selecciona Usuarios y acceso.

Acceso de usuarios de App Store Connect

3 — Selecciona la pestaña Integración.

Integración de API en App Store Connect

4 — Haz clic en Generate API Key o en el botón Add (+).

Crear claves API en App Store Connect

5 — Introduce un nombre para la clave. El nombre es solo para tu referencia y no forma parte de la clave.

Nombre de la clave API en App Store Connect

6 — En Access, selecciona el rol para la clave. Los roles que se aplican a las claves son los mismos que se aplican a los usuarios de tu equipo. Ver permisos de rol. Recomendamos seleccionar Gestor de apps.

7 — Haz clic en Generate.

El acceso de una clave API no se puede limitar a apps específicas.

En la página aparecerán el nombre de la nueva clave, su ID, un enlace de descarga y más info.

Claves de descarga de App Store Connect

Aquí puedes obtener los tres datos necesarios.
<1> ID del problema.
<2> ID de la clave.
<3> Haz clic en “Download API Key” para descargar tu clave privada API. El enlace de descarga solo aparece si aún no se ha descargado la clave privada. Apple no conserva una copia de la clave privada, así que solo podrás descargarla una vez.

🔴 Guarda tu clave privada en un lugar seguro. Nunca compartas tus claves ni las guardes en un repositorio code, ni las incluyas en code del lado del cliente.

Usar una clave API de App Store Connect

Para crear el token JWT de autorización, necesitas el archivo de clave API (el archivo .p8 que descargas), el key ID y el issuer ID. Existen varias formas de pasar esta información a Fastlane usando la nueva acción de Fastlane, app_store_connect_api_key. Puedes conocer otras formas en la documentación de Fastlane. Te muestro este método porque creo que es la forma más fácil de trabajar con la mayoría de los CI que hay, donde puedes configurar variables de entorno.

¡Genial! Ahora podemos gestionar Fastlane con la clave API de App Store Connect.

2. Copiar archivos de Fastlane

Fastlane es una librería de Ruby creada para automatizar tareas comunes de desarrollo móvil. Con Fastlane, puedes configurar “lanes” personalizadas que agrupan una serie de “actions” que realizan tareas que normalmente harías usando Android Studio. Puedes hacer mucho con Fastlane, pero para este tutorial usaremos solo unas pocas acciones principales.

Crea una carpeta Fastlane en la raíz de tu proyecto y copia los siguientes archivos: Fastfile

default_platform(:ios)

DEVELOPER_APP_IDENTIFIER = ENV["DEVELOPER_APP_IDENTIFIER"]
DEVELOPER_APP_ID = ENV["DEVELOPER_APP_ID"]
PROVISIONING_PROFILE_SPECIFIER = ENV["PROVISIONING_PROFILE_SPECIFIER"]
TEMP_KEYCHAIN_USER = ENV["TEMP_KEYCHAIN_USER"]
TEMP_KEYCHAIN_PASSWORD = ENV["TEMP_KEYCHAIN_PASSWORD"]
APPLE_ISSUER_ID = ENV["APPLE_ISSUER_ID"]
APPLE_KEY_ID = ENV["APPLE_KEY_ID"]
APPLE_KEY_CONTENT = ENV["APPLE_KEY_CONTENT"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]

def delete_temp_keychain(name)
  delete_keychain(
    name: name
  ) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end

def create_temp_keychain(name, password)
  create_keychain(
    name: name,
    password: password,
    unlock: false,
    timeout: 0
  )
end

def ensure_temp_keychain(name, password)
  delete_temp_keychain(name)
  create_temp_keychain(name, password)
end

platform :ios do
  lane :build do
    build_app(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )
  end
  lane :refresh_profiles do
    match(
      type: "development",
      force: true)
    match(
      type: "adhoc",
      force: true)
  end
  desc "Register new device"
  lane :register_new_device do  |options|
      device_name = prompt(text: "Enter the device name: ")
      device_udid = prompt(text: "Enter the device UDID: ")
      device_hash = {}
      device_hash[device_name] = device_udid
      register_devices(
                       devices: device_hash
                       )
    refresh_profiles
  end
  lane :closed_beta do
    keychain_name = TEMP_KEYCHAIN_USER
    keychain_password = TEMP_KEYCHAIN_PASSWORD
    ensure_temp_keychain(keychain_name, keychain_password)

    api_key = app_store_connect_api_key(
      key_id: APPLE_KEY_ID,
      issuer_id: APPLE_ISSUER_ID,
      key_content: APPLE_KEY_CONTENT,            
      duration: 1200,            
      in_house: false
    )

    match(
      type: 'appstore',
      git_basic_authorization: Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"),
      readonly: true,
      keychain_name: keychain_name,
      keychain_password: keychain_password,
      api_key: api_key
    )

    gym(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )

    pilot(
      apple_id: "#{DEVELOPER_APP_ID}",
      app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      notify_external_testers: false,
      ipa: "./App.ipa"
    )

    delete_temp_keychain(keychain_name)
  end
  lane :submit_review do
    version = ''
    Dir.chdir("..") do
      file = File.read("package.json")
      data = JSON.parse(file)
      version = data["version"]
    end
    deliver(
      app_version: version,
      submit_for_review: true,
      automatic_release: true,
      force: true, # Skip HTMl report verification
      skip_metadata: false,
      skip_screenshots: false,
      skip_binary_upload: true
    )
  end
end

Appfile

app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"])
apple_id(ENV["FASTLANE_APPLE_ID"])
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"])

Configurar Fastlane match

Fastlane match es un nuevo enfoque para el signing de code en iOS. Fastlane match facilita que los equipos gestionen los certificados y provisioning profiles necesarios para tus apps de iOS.

Crea un nuevo repositorio privado llamado certificates, por ejemplo en tu cuenta personal de GitHub o en tu organización.

Inicializa Fastlane match para tu app de iOS.

fastlane match init

Luego selecciona la opción #1 (Git Storage).

[01:00:00]: fastlane match supports multiple storage modes, please select the one you want to use:1. git2. google_cloud3. s3?

Asigna la URL del repositorio recién creado.

[01:00:00]: Please create a new, private git repository to store the certificates and profiles there[01:00:00]: URL of the Git Repo: <YOUR_CERTIFICATES_REPO_URL>

Ahora tienes dentro de la carpeta Fastlane un archivo llamado Matchfile y _git_url_debe ser la URL HTTPS del repositorio de certificados. Opcionalmente, puedes usar SSH, pero requiere un paso adicional.

# ios/Matchfilegit_url("https://github.com/gitusername/certificates")storage_mode("git")type("appstore")

Luego, generamos los certificados e ingresamos las credenciales cuando Fastlane Match lo solicite.

Se te pedirá una passphrase. Guárdala bien, ya que la usarán las GitHub Actions para desencriptar tu repositorio de certificados.

fastlane match appstore

Si todo salió bien, deberías ver algo como esto:

[01:40:52]: All required keys, certificates and provisioning profiles are installed 🙌

Si tuviste problemas con GitHub y los permisos necesarios, quizás este post te ayude a generar tokens de autenticación para git.

Los certificados y provisioning profiles generados se suben al repositorio de certificados

Certificados de App Store Connect

Por último, abre tu project en Xcode, y actualiza el provisioning profile para la configuración de release de tu app.

Certificados de Xcode

Cosas a tener en cuenta 💡

MATCH

Para que el CI/CD importe los certificados y provisioning profiles, necesita acceso al repositorio de certificados. Puedes hacerlo generando un personal access token (que ya deberías haber usado antes) con permisos para acceder o leer repositorios privados.

En GitHub, ve a Configuración → Configuración de desarrollador → Tokens de acceso personal → haz clic en Generate New Token → marca el repo scope → y luego haz clic en Generate token.

Crear un token de acceso personal

Guarda una copia del token de acceso personal generado. Lo usarás después para la variable de entorno GIT_TOKEN.

Luego, reemplaza el archivo match generado en la carpeta Fastlane por Matchfile

CERTIFICATE_STORE_URL = ENV["CERTIFICATE_STORE_URL"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
FASTLANE_APPLE_ID = ENV["FASTLANE_APPLE_ID"]

git_url(CERTIFICATE_STORE_URL)
storage_mode("git")
type("appstore")
git_basic_authorization(Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"))
username(FASTLANE_APPLE_ID)

Esto lo usarán las GitHub Actions para importar los certificados y provisioning profiles. Y la variable se configurará en GitHub Secrets, en lugar de hardcodearlos en el archivo.

Procesamiento del build

En GitHub Actions, se te factura según los minutos que hayas usado para ejecutar tu workflow de CI/CD. Por experiencia, tarda unos 10–15 minutos antes de que un build se procese en App Store Connect.

Para proyectos privados, el coste estimado por build puede llegar a $0,08/min x 15 min = $1,2, o más, dependiendo de la configuración o dependencias de tu proyecto.

Si compartes mis preocupaciones sobre los precios en proyectos privados, puedes mantener el skip_waiting_for_build_processing a true.

¿Cuál es el truco? Debes actualizar manualmente el cumplimiento de tu app en App Store Connect tras procesar el build para poder distribuirlo a tus usuarios.

Este es solo un parámetro opcional para actualizar si quieres ahorrar minutos de build en proyectos privados. Para proyectos gratuitos, esto no debería ser un problema. Ver precios.

3. Configurar GitHub Actions

Configurar los secrets de GitHub

Ever wonder where the values of the ENV ? Bueno, ya no es un secreto: vienen del secret de tu proyecto. 🤦

Configura los secrets de GitHub

1. APP_STORE_CONNECT_TEAM_ID - el ID de tu equipo de App Store Connect si perteneces a varios equipos.

2. DEVELOPER_APP_ID - en App Store Connect, ve a la app → Información de la app → Desplázate hasta la General Information sección de tu app y busca Apple ID.

3. DEVELOPER_APP_IDENTIFIER - el bundle identifier de tu app.

4. DEVELOPER_PORTAL_TEAM_ID - el ID de tu equipo en el Developer Portal si estás en varios equipos.

5. FASTLANE_APPLE_ID - el Apple ID o email de desarrollador que usas para gestionar la app.

6. GIT_USERNAME &amp; GIT_TOKEN - Tu usuario de git y tu personal access token.

7. MATCH_PASSWORD - la passphrase que asignaste al inicializar match, se usará para desencriptar certificados y provisioning profiles.

8. PROVISIONING_PROFILE_SPECIFIER - match AppStore <YOUR_APP_BUNDLE_IDENTIFIER>, ej. match AppStore com.domain.blabla.demo.

9. TEMP_KEYCHAIN_USER &amp; TEMP_KEYCHAIN_PASSWORD - asigna un usuario y una contraseña temporales de keychain para tu workflow.

10. APPLE_KEY_ID — API Key de App Store Connect 🔺Key ID.

11. APPLE_ISSUER_ID — API Key de App Store Connect 🔺Issuer ID.

12. APPLE_KEY_CONTENT — API Key de App Store Connect 🔺 Archivo Key o contenido de la Key de .p8, compruébalo

13. CERTIFICATE_STORE_URL — La URL del repo de tus Match keys (ej: https://github.com/***/fastlane_match.git)

4. Configurar el archivo de workflow de GitHub

Crea un directorio GitHub workflow.

cd .github/workflows

Dentro de la workflow carpeta, crea un archivo llamado build-upload-ios.ymly añade lo siguiente.

name: Build source code on ios

on:
  push:
    tags:
      - '*'

jobs:
  build_ios:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v6
      - name: set Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '24'
          cache: npm
      - name: Install dependencies
        id: install_code
        run: npm ci
      - name: Build
        id: build_code
        run: npm run build
      - uses: actions/cache@v5
        with:
          path: ios/App/Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-pods-
      - name: Sync
        id: sync_code
        run: npx cap sync
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7.2
      - uses: maierj/fastlane-action@v2.3.0
        env:
          DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
          PROVISIONING_PROFILE_SPECIFIER: match AppStore ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
          TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          CERTIFICATE_STORE_URL: https://github.com/${{ secrets.CERTIFICATE_STORE_REPO }}.git
          GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
          GIT_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          FASTLANE_APPLE_ID: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_USERNAME: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
          DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
        with:
          lane: closed_beta
      - name: Upload release bundle
        uses: actions/upload-artifact@v2
        with:
          name: ios-release
          path: ./App.ipa
          retention-days: 60

Este workflow debería ejecutarse tras cada GitHub etiqueta, si necesitas automatizar el tag, consulta Build y release automáticos con GitHub actions primero.

Este workflow descargará tus deps de NodeJS, las instalará y hará el build de tu app JS.

Cada vez que envíes un nuevo commit, se generará un release en TestFlight.

Tu app no necesita usar Ionic, solo la base de Capacitor es obligatoria. Puede tener módulos antiguos de Cordova, pero se recomienda usar el plugin JS de Capacitor.

5. Ejecutar workflow

Crear un commit

Haz un commit, y verás el workflow activo en el repositorio.

Ejecutar el workflow

Haz push de los nuevos commits a la rama main o development para ejecutar el workflow.

Iniciado con el commit

En unos minutos, el build debería estar disponible en tu dashboard de App Store Connect.

Dashboard de TestFlight

¿Se puede desplegar desde una máquina local?

Sí, puedes hacerlo sin esfuerzo.

Imagina que tienes un repositorio privado, ya agotaste los minutos del plan gratuito y no quieres pagar por nuevos releases, o quizás prefieres enviar la aplicación manualmente.

Vamos a ello

Bien, primero tenemos que crear en my_project_path/fastlane la ruta de un archivo llamado .env, en la misma ruta que Fastfile, para poder crear los mismos secret properties de nuestro _GitHub, como se muestra abajo:

archivo .env para deploy desde local

Ahora, puedes ir a la terminal y ejecutar el Fastlane desde tu máquina:

fastlane closed_beta

❌ Lo esencial sobre el archivo .env archivo, ya que preferimos no exponer estos datos, debemos añadirlo en nuestro .gitignore, algo como esto: ❌

fastlane/*.env

Debería funcionar igual que con GitHub Actions en la máquina remota, pero en nuestra máquina local. 🍻

Ejecución local de Fastlane

Ejecución en terminal: $ Fastlane closed_beta

Si has llegado hasta aquí, ¡felicidades! Ya tienes un proceso totalmente automatizado para tus apps de iOS con Fastlane y GitHub Actions.

Cada vez que envíes un nuevo commit, se generará un release en la consola de Google Play, canal beta. Mejoraré este blog con tus comentarios; si tienes dudas o sugerencias, por favor escríbeme por email. martin@capgo.app

Build en tu dispositivo

Si aún necesitas compilar en tu dispositivo, debes agregarlos manualmente al provisioning. Conecta tu dispositivo al Mac y abre el menú de dispositivos menú de dispositivos iOS Luego copia tu identificador buscar identificador iOS Y después ejecuta el comando: fastlane register_new_device te pedirá configurar un nombre de dispositivo y el identificador: configurar identificador iOS

si tienes problemas

Si tienes problemas para probar en tu dispositivo de desarrollo, esto suele solucionarlo.

Hay un comando mágico que puede salvarte:

fastlane match nuke development
fastlane match development

Después: Limpia el proyecto pulsando Shift(⇧)+Command(⌘)+K o seleccionando Product > Clean (podría aparecer como “Clean Build Folder”)

Luego, intenta ejecutar la app de nuevo en tu dispositivo.

Gracias

Este blog se basa en los siguientes artículos:

Continúa desde el build automático de iOS de Capacitor con GitHub actions usando match

Si estás usando Build automático de Capacitor para iOS con GitHub Actions usando match para planificar la automatización de CI/CD, conéctalo con CI/CD de Capgo para el flujo de trabajo del producto en Capgo CI/CD, Native Builds de Capgo para el flujo de trabajo del producto en Capgo Native Builds, Integraciones de Capgo para el flujo de trabajo del producto en Capgo Integraciones, Integración CI/CD para los detalles de implementación de CI/CD Integration, y Integración con GitHub Actions para los detalles de implementación de GitHub Actions Integration.

Live updates para apps Capacitor

Cuando un bug de la capa web está en producción, publica el fix a través de Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el flujo de revisión habitual.

Lo último de nuestro blog

Capgo te da la información clave que necesitas para crear una app móvil realmente profesional.