Tutorial: configurar Fastlane Match com GitHub Actions para publicar app iOS sem armazenar certificados no repositório

Apps mobile · Por · Publicado em · 9 min de leitura

Tela de laptop com editor de código aberto e barra de aplicativos na parte inferior
Ambiente de desenvolvimento macOS com código aberto, apropriado para tutoriais de CI e automação iOS Foto: Meet Patel / Pexels

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 iOS

Veja também