Tutorial: configurar Fastlane Match com GitHub Actions para publicar app iOS sem armazenar certificados no repositório
Apps mobile · Por Kevin Marcondes · Publicado em · 9 min de leitura
Este tutorial mostra como configurar Fastlane Match com GitHub Actions para publicar app iOS sem armazenar certificados no repositório. Você vai criar um repositório de certificados criptografado, proteger secrets no GitHub, usar um keychain temporário no runner macOS e automatizar build e upload para o TestFlight com uma lane dedicada do Fastlane.
Pré-requisitos e arquitetura segura (Fastlane Match + CI)
A arquitetura combina Fastlane Match para gerenciar certificados e perfis com um workflow do GitHub Actions que roda em macOS. Os certificados ficam em um repositório privado e criptografado, nunca no repositório do código-fonte. A CI usa um keychain temporário no runner e secrets protegidos.
Você precisa de:
- Conta no Apple Developer Program com acesso para criar certificados e perfis.
- macOS com Xcode e ferramentas de linha de comando.
- Ruby e Bundler instalados para executar o Fastlane.
- Um repositório Git privado separado para os certificados (Match).
- Repositório no GitHub com Actions habilitado e runner macOS.
- Permissão no App Store Connect para criar uma chave API.
Para reforçar práticas de segurança de secrets, consulte o checklist de pipelines em runners e secrets.
Passo 1 — Inicializar o Fastlane no projeto iOS
O Fastlane organiza tarefas de build, assinatura e distribuição em lanes. Vamos preparar o projeto com Bundler para padronizar a versão do Fastlane e evitar diferenças entre máquinas. Execute em um macOS com 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
Passo 2 — Configurar o Matchfile e o repositório de certificados
O Fastlane Match guarda certificados e perfis em um repositório Git privado, criptografados com uma senha. Não use o mesmo repositório do código do app. Separe um repositório apenas para os artefatos de assinatura. Abaixo, um exemplo de Matchfile para App Store.
- Mantenha o repositório de certificados privado e com acesso mínimo.
- Use uma branch dedicada (por exemplo, "certs").
- No CI, rode o Match em modo somente leitura (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"))
Passo 3 — Criar certificados e perfis localmente, com senha do Match
Gere os certificados e perfis uma única vez em um ambiente local confiável. Isso preenche o repositório de certificados com conteúdo criptografado. Nunca faça commit de certificados ou perfis no repositório do app.
- Escolha nomes claros para branches e mantenha histórico.
- Guarde a senha do Match em um cofre de secrets antes de configurar a CI.
- Rodar o tipo development/adhoc também facilita testes internos.
# 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
Como proteger os secrets no GitHub Actions?
Secrets devem ficar em GitHub Actions Secrets/Environments, nunca em arquivos versionados. A chave privada .p8 do App Store Connect não deve ir para o repositório. Use ambientes protegidos e aprovação manual para jobs sensíveis.
Veja boas práticas de ambientes e proteção de variáveis no guia de pipeline de deploy automático.
- Restrinja quem pode ler/gravar secrets e quem pode disparar publicações.
- Use Environments com protections (reviewers) para produção.
- Faça rotação periódica da senha do Match e da chave API.
| Secret/variável | Uso |
|---|---|
| MATCH_PASSWORD | Senha que criptografa o repositório de certificados do Match |
| MATCH_GIT_URL | URL do repositório privado de certificados |
| MATCH_GIT_BRANCH | Branch de certificados (ex.: certs) |
| APP_STORE_CONNECT_API_KEY_ID | Key ID da chave API do App Store Connect |
| APP_STORE_CONNECT_ISSUER_ID | Issuer ID da organização no App Store Connect |
| APP_STORE_CONNECT_PRIVATE_KEY | Conteúdo da chave .p8 (texto) |
| KEYCHAIN_PASSWORD | Senha do keychain temporário no runner |
Passo 4 — Preparar o Fastfile com uma lane para CI
Crie uma lane que configure o keychain temporário, consuma a chave API do App Store Connect via variáveis e chame o Match em modo somente leitura. Não coloque valores sensíveis hardcoded no Fastfile. A lane abaixo compila e envia para o TestFlight.
- Defina o scheme correto do seu app.
- Ajuste export_method e opções conforme sua configuração de assinatura.
- Apague o keychain ao final do job para reduzir superfícies 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
Passo 5 — Workflow do GitHub Actions para build, assinatura e TestFlight
O workflow roda em macOS, instala o Fastlane via Bundler, configura variáveis de ambiente e executa a lane. Os certificados nunca tocam o repositório do app: o Match baixa e instala no keychain temporário. Para apps React Native/Expo, veja também o pipeline de builds EAS.
- Use gatilhos por tag para controlar releases.
- Defina permissions mínimos para o job.
- Evite logs verbosos que exponham caminhos sensíveis.
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
Como confirmar que funcionou e resolver erros comuns
Confirme no log do Actions que o Match clonou o repositório de certificados, instalou o perfil e o build foi enviado ao TestFlight. Verifique o TestFlight no App Store Connect após o job. Se algo falhar, os comandos abaixo ajudam no diagnóstico no runner macOS.
Para publicação manual guiada, veja o passo a passo de como publicar um aplicativo iOS na App Store.
Erros frequentes e dicas rápidas:
- Senha do Match incorreta: gire a senha e regrave o repositório de certificados.
- Keychain bloqueado: aumente o timeout e garanta unlock: true.
- Certificado revogado: regenere localmente e faça novo push pelo Match.
- Permissões insuficientes no App Store Connect: ajuste o papel do usuário.
# 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
Perguntas frequentes
Preciso de runner macOS para compilar e assinar apps iOS?
Sim. A compilação e a assinatura de apps iOS exigem Xcode e, portanto, um ambiente macOS. No GitHub Actions, use macos-latest. É possível usar máquinas próprias como runners self-hosted, desde que mantenham o Xcode alinhado à versão do projeto.
É obrigatório usar chave API do App Store Connect na CI?
Não é obrigatório, mas é o caminho recomendado. A autenticação por Apple ID envolve 2FA e é frágil em ambientes não interativos. A chave API do App Store Connect é estável, rotacionável e funciona bem com o Fastlane para TestFlight e App Store Connect API.
Posso compartilhar o repositório de certificados entre vários apps?
Sim, o Match consegue gerenciar múltiplos apps no mesmo repositório, separados por identificadores e pastas internas. Ainda assim, prefira separar por time ou escopo de projeto. Controle acesso por time e use branches distintas para evitar conflitos entre pipelines.
Como rodar o Match em modo somente leitura no CI?
Informe readonly: true ao chamar o Match na lane do Fastlane. Antes, gere certificados e perfis localmente e suba para o repositório de certificados. No CI, o Match apenas baixa e instala no keychain, sem criar ou revogar artefatos.
O que fazer se a senha do Match ou a chave .p8 vazarem?
Troque imediatamente a senha do Match e regrave o repositório criptografado. Revogue e gere uma nova chave .p8 no App Store Connect. Em casos graves, regenere certificados com o Match e atualize perfis. Armazene secrets em Environments com revisão obrigatória.
Como lidar com vários times (teams) no Apple Developer Program?
Defina team_id ou team_name no Match/lanes, ou use variáveis de ambiente como FASTLANE_TEAM_ID. Mantenha um repositório de certificados por time e limite o acesso a quem realmente precisa, evitando mistura de credenciais entre produtos e organizações.
Próximos passos
Quer evoluir sua automação iOS com segurança? Use as referências deste guia, fortaleça secrets e ambientes e itere na pipeline. Se precisar discutir seu cenário, podemos conversar.
Quero falar sobre automação iOSVeja também
- tutorial: configurar mTLS entre Traefik e serviços em Docker Compose usando smallstep CA e certificados automáticos — passo a passo
- Tutorial: integrar Sentry ao pipeline de deploy com GitHub Actions para upload automático de source maps e criação de issues (PHP e JavaScript) — passo a passo
- Configurar retenção e compactação de logs de containers Docker com journald + logrotate + rsyslog em VPS com pouco disco
- tutorial: configurar GitHub Actions para testes de integração com PostgreSQL em containers efêmeros e rollback automático de migrações — guia prático