Tutorial: configurar Fastlane Match con GitHub Actions para publicar una app iOS sin almacenar certificados en el repositorio
Apps mobile · Por Kevin Marcondes · Publicado el · 9 min de lectura
Este tutorial muestra cómo configurar Fastlane Match con GitHub Actions para publicar una app iOS sin almacenar certificados en el repositorio. Crearás un repositorio de certificados cifrado, protegerás secrets en GitHub, usarás un keychain temporal en el runner macOS y automatizarás la compilación y subida a TestFlight con una lane dedicada de Fastlane.
Requisitos y arquitectura segura (Fastlane Match + CI)
La arquitectura combina Fastlane Match para gestionar certificados y perfiles con un workflow de GitHub Actions que corre en macOS. Los certificados quedan en un repositorio privado y cifrado, nunca en el repositorio del código fuente. La CI usa un keychain temporal en el runner y secrets protegidos.
Necesitas:
- Cuenta en el Apple Developer Program con acceso para crear certificados y perfiles.
- macOS con Xcode y herramientas de línea de comandos.
- Ruby y Bundler instalados para ejecutar Fastlane.
- Un repositorio Git privado separado para los certificados (Match).
- Repositorio en GitHub con Actions habilitadas y runner macOS.
- Permiso en App Store Connect para crear una clave API.
Paso 1 — Inicializar Fastlane en el proyecto iOS
Fastlane organiza tareas de build, firma y distribución en lanes. Vamos a preparar el proyecto con Bundler para fijar la versión de Fastlane y evitar diferencias entre máquinas. Ejecuta estos pasos en un macOS con Xcode instalado.
# Dentro do diretório do app iOS
bundle init
# Adicione o Fastlane ao Gemfile
echo 'gem "fastlane"' >> Gemfile
bundle install
# Inicialize o Fastlane (responda os prompts conforme seu projeto)
bundle exec fastlane init
Paso 2 — Configurar el Matchfile y el repositorio de certificados
Fastlane Match guarda certificados y perfiles en un repositorio Git privado, cifrados con una contraseña. No uses el mismo repositorio del código de la app. Separa un repositorio solo para los artefactos de firma. Abajo, un ejemplo de Matchfile para App Store.
- Mantén el repositorio de certificados privado y con acceso mínimo.
- Usa una branch dedicada (por ejemplo, "certs").
- En la CI, ejecuta Match en modo solo lectura (readonly).
# Matchfile (na pasta fastlane do projeto)
# Ajuste os valores conforme seu app/time
git_url("[email protected]:org/ios-certs.git")
storage_mode("git")
git_branch("certs")
type("appstore")
app_identifier(["com.example.app"]) # seu bundle id
username("[email protected]") # Apple ID
# Keychain temporário controlado por variáveis de ambiente no CI
keychain_name(ENV.fetch("KEYCHAIN_NAME", "ci"))
keychain_password(ENV.fetch("KEYCHAIN_PASSWORD", "temp-pass"))
Paso 3 — Crear certificados y perfiles localmente, con la contraseña de Match
Genera los certificados y perfiles una sola vez en un entorno local de confianza. Esto llena el repositorio de certificados con contenido cifrado. Nunca hagas commit de certificados o perfiles en el repositorio de la app.
- Elige nombres claros para branches y conserva el historial.
- Guarda la contraseña de Match en un vault de secrets antes de configurar la CI.
- Ejecutar los tipos development/adhoc también facilita pruebas internas.
# Defina a senha que criptografa os artefatos do Match
export MATCH_PASSWORD='sua-senha-forte'
# Gere certificados e perfis para App Store (primeira vez)
bundle exec fastlane match appstore
# Opcional: development para testes locais
bundle exec fastlane match development
# CUIDADO: o comando abaixo revoga tudo do tipo especificado
# **Use somente se precisar girar chaves/perfis comprometidos.**
# bundle exec fastlane match nuke appstore
¿Cómo proteger los secrets en GitHub Actions?
Los secrets deben mantenerse en GitHub Actions Secrets/Environments, nunca en archivos versionados. La clave privada .p8 de App Store Connect no debe ir al repositorio. Usa entornos protegidos y aprobación manual para jobs sensibles.
Consulta buenas prácticas de entornos y protección de variables en la guía de pipeline de despliegue automático.
- Restringe quién puede leer/grabar secrets y quién puede disparar publicaciones.
- Usa Environments con protections (revisores) para producción.
- Realiza rotación periódica de la contraseña del Match y de la clave API.
| Secreto/variable | Uso |
|---|---|
| MATCH_PASSWORD | Contraseña que cifra el repositorio de certificados del Match |
| MATCH_GIT_URL | URL del repositorio privado de certificados |
| MATCH_GIT_BRANCH | Branch de certificados (ej.: certs) |
| APP_STORE_CONNECT_API_KEY_ID | Key ID de la clave API de App Store Connect |
| APP_STORE_CONNECT_ISSUER_ID | Issuer ID de la organización en App Store Connect |
| APP_STORE_CONNECT_PRIVATE_KEY | Contenido de la clave .p8 (texto) |
| KEYCHAIN_PASSWORD | Contraseña del keychain temporal en el runner |
Paso 4 — Preparar el Fastfile con una lane para CI
Crea una lane que configure el keychain temporal, consuma la clave API de App Store Connect vía variables y llame a Match en modo solo lectura. No pongas valores sensibles hardcoded en el Fastfile. La lane de ejemplo compila y envía a TestFlight.
- Define el scheme correcto de tu app.
- Ajusta export_method y opciones según tu configuración de firma.
- Elimina el keychain al final del job para reducir la superficie de ataque.
# Fastfile (exemplo simplificado)
default_platform(:ios)
platform :ios do
lane :ci_appstore do
api_key = app_store_connect_api_key(
key_id: ENV['APP_STORE_CONNECT_API_KEY_ID'],
issuer_id: ENV['APP_STORE_CONNECT_ISSUER_ID'],
key_content: ENV['APP_STORE_CONNECT_PRIVATE_KEY'],
in_house: false
)
create_keychain(
name: ENV.fetch('KEYCHAIN_NAME', 'ci'),
password: ENV.fetch('KEYCHAIN_PASSWORD', 'temp-pass'),
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: true
)
match(
type: 'appstore',
readonly: true,
keychain_name: ENV.fetch('KEYCHAIN_NAME', 'ci'),
keychain_password: ENV.fetch('KEYCHAIN_PASSWORD', 'temp-pass'),
git_url: ENV['MATCH_GIT_URL'],
git_branch: ENV.fetch('MATCH_GIT_BRANCH', 'certs')
)
build_ios_app(
scheme: 'YourAppScheme',
export_method: 'app-store'
)
upload_to_testflight(api_key: api_key)
delete_keychain(name: ENV.fetch('KEYCHAIN_NAME', 'ci'))
end
end
Paso 5 — Workflow de GitHub Actions para build, firma y TestFlight
El workflow corre en macOS, instala Fastlane vía Bundler, configura variables de entorno y ejecuta la lane. Los certificados nunca tocan el repositorio de la app: Match los descarga e instala en el keychain temporal. Para apps React Native/Expo, consulta también el pipeline de builds EAS.
- Usa triggers por tag para controlar releases.
- Define permisos mínimos para el job.
- Evita logs verbosos que expongan rutas o variables sensibles.
name: ios-ci
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: macos-latest
permissions:
contents: read
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
MATCH_GIT_BRANCH: ${{ secrets.MATCH_GIT_BRANCH }}
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
APP_STORE_CONNECT_PRIVATE_KEY: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }}
KEYCHAIN_NAME: ci-keychain
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
steps:
- uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Install dependencies (Fastlane)
run: |
bundle install || (bundle init && echo 'gem "fastlane"' >> Gemfile && bundle install)
- name: Xcode info (sanity check)
run: xcodebuild -version
- name: Build and upload to TestFlight
run: bundle exec fastlane ios ci_appstore
Cómo confirmar que funcionó y resolver errores comunes
Confirma en los logs de Actions que Match clonó el repositorio de certificados, instaló el perfil y que el build fue enviado a TestFlight. Verifica TestFlight en App Store Connect después del job. Si algo falla, los comandos abajo ayudan a diagnosticar en el runner macOS.
Para publicación manual guiada, consulta el paso a paso sobre cómo publicar una app iOS en la App Store.
- Contraseña del Match incorrecta: rota la contraseña y vuelve a cifrar el repositorio de certificados.
- Keychain bloqueado: aumenta el timeout y asegúrate de unlock: true.
- Certificado revocado: regenera localmente y sube de nuevo con Match.
- Permisos insuficientes en App Store Connect: ajusta el rol del usuario.
# Listar identidades de assinatura disponíveis
security find-identity -v -p codesigning
# Verificar perfis instalados
ls ~/Library/MobileDevice/Provisioning\ Profiles || true
# Atualizar Fastlane (se necessário)
bundle update fastlane || bundle exec fastlane update_fastlane
Preguntas frecuentes
¿Necesito un runner macOS para compilar y firmar apps iOS?
Sí. La compilación y firma de apps iOS requieren Xcode y, por tanto, un entorno macOS. En GitHub Actions, usa macos-latest. Es posible usar máquinas propias como self-hosted runners, siempre que mantengan Xcode alineado con la versión del proyecto.
¿Es obligatorio usar la clave API de App Store Connect en la CI?
No es obligatorio, pero es el método recomendado. La autenticación por Apple ID implica 2FA y es frágil en entornos no interactivos. La clave API de App Store Connect es estable, rotacionable y funciona bien con Fastlane para TestFlight y la API de App Store Connect.
¿Puedo compartir el repositorio de certificados entre varias apps?
Sí, Match puede gestionar múltiples apps en el mismo repositorio, separadas por identificadores y carpetas internas. Aun así, es preferible separar por equipo o alcance de proyecto. Controla el acceso por equipo y usa branches distintas para evitar conflictos entre pipelines.
¿Cómo ejecutar Match en modo solo lectura en la CI?
Pasa readonly: true al llamar a Match en la lane de Fastlane. Antes, genera certificados y perfiles localmente y súbelos al repositorio de certificados. En la CI, Match solo descarga e instala en el keychain, sin crear ni revocar artefactos.
¿Qué hacer si la contraseña del Match o la clave .p8 se filtran?
Cambia inmediatamente la contraseña del Match y vuelve a cifrar el repositorio. Revoca y genera una nueva clave .p8 en App Store Connect. En casos graves, regenera certificados con Match y actualiza los perfiles. Almacena secrets en Environments con revisión obligatoria.
¿Cómo manejar varios teams en Apple Developer Program?
Define team_id o team_name en Match/lanes, o usa variables de entorno como FASTLANE_TEAM_ID. Mantén un repositorio de certificados por equipo y limita el acceso a quienes realmente lo necesitan, evitando mezclar credenciales entre productos y organizaciones.
Próximos pasos
¿Quieres mejorar tu automatización iOS con seguridad? Usa las referencias de este guía, fortalece secrets y entornos, y itera en la pipeline. Si necesitas discutir tu escenario, podemos conversar.
Quiero hablar sobre automatización iOSVer también
- Tutorial: configurar mTLS entre Traefik y servicios en Docker Compose usando smallstep CA y certificados automáticos — paso a paso
- Tutorial: integrar Sentry en el pipeline de deploy con GitHub Actions para subir source maps automáticamente y generar issues (PHP y JavaScript) — paso a paso
- Configurar retención y compresión de logs de contenedores Docker con journald + logrotate + rsyslog en VPS con poco disco
- Tutorial: configurar GitHub Actions para tests de integración con PostgreSQL en contenedores efímeros y rollback automático de migrations — guía práctica