Configurando CI/CD para React Native com GitHub Actions e Fastlane

Publicado em 16 de Dezembro, 2025 • 14 min de leitura

Builds manuais são o pesadelo de todo desenvolvedor. Cada release se torna um processo de várias horas: rodar testes, fazer build para iOS, fazer build para Android, assinar, subir para TestFlight, subir para Play Store, rezar para nada quebrar. Depois de configurar pipelines de CI/CD para mais de 20 apps React Native, estou compartilhando a configuração exata que funciona em produção.

Ao final deste guia, você terá um pipeline totalmente automatizado que faz build, testa e faz deploy do seu app React Native para iOS e Android com um único git push. Sem mais builds manuais. Nunca mais.

Por Que CI/CD Importa para React Native

Aqui está o que um CI/CD adequado te dá:

  • Builds automatizados: Push para main, receba builds no TestFlight e Play Store
  • Releases consistentes: Sem mais problemas de "funciona na minha máquina"
  • Economia de tempo: 3-4 horas de trabalho manual → 0 horas
  • Iteração mais rápida: Deploy de builds beta várias vezes por dia
  • Capture bugs cedo: Testes automatizados rodam em cada PR
  • Escalabilidade do time: Qualquer desenvolvedor pode disparar releases

A Stack: GitHub Actions + Fastlane

GitHub Actions: CI/CD gratuito integrado ao GitHub. 2.000 minutos grátis/mês para repos privados (suficiente para a maioria dos projetos).

Fastlane: Ferramenta de automação baseada em Ruby que lida com toda a complexidade iOS/Android (assinatura de código, screenshots, metadados, uploads).

Esta combinação é testada em batalha, gratuita para times pequenos e escala para apps enterprise com milhões de usuários.

Pré-requisitos

Antes de começar, você vai precisar de:

  • App React Native (0.70+) com projetos iOS e Android
  • Conta Apple Developer ($99/ano)
  • Conta Google Play Developer ($25 única vez)
  • Repositório GitHub
  • Conhecimento básico de terminal/linha de comando

Parte 1: Configurando Fastlane

Instalar Fastlane

# Instalar Fastlane via RubyGems
sudo gem install fastlane -NV

# Ou usando Homebrew (macOS)
brew install fastlane

Inicializar Fastlane para iOS

cd ios
fastlane init

O Fastlane vai te fazer perguntas. Escolha:

  1. Para que você gostaria de usar o fastlane? → "Automatizar distribuição beta para TestFlight"
  2. Digite seu Apple ID e senha
  3. Selecione seu app da lista (ou crie um novo)

Isso cria ios/fastlane/Fastfile e ios/fastlane/Appfile.

Configurar Fastfile iOS

Edite ios/fastlane/Fastfile:

default_platform(:ios)

platform :ios do
  desc "Enviar novo build beta para TestFlight"
  lane :beta do
    # Incrementar build number (baseado no mais recente do TestFlight)
    increment_build_number(xcodeproj: "SeuApp.xcodeproj")

    # Fazer build do app
    build_app(
      scheme: "SeuApp",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          "com.suaempresa.seuapp" => "match AppStore com.suaempresa.seuapp"
        }
      }
    )

    # Upload para TestFlight
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false
    )
  end

  desc "Rodar testes"
  lane :test do
    run_tests(
      scheme: "SeuApp",
      devices: ["iPhone 15 Pro"]
    )
  end
end

Inicializar Fastlane para Android

cd android
fastlane init

Escolha "Automatizar distribuição beta para Google Play" quando solicitado.

Configurar Fastfile Android

Edite android/fastlane/Fastfile:

default_platform(:android)

platform :android do
  desc "Enviar novo build beta para Play Store"
  lane :beta do
    # Incrementar version code
    gradle(
      task: "clean bundleRelease",
      properties: {
        "android.injected.signing.store.file" => ENV["KEYSTORE_PATH"],
        "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
        "android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
        "android.injected.signing.key.password" => ENV["KEY_PASSWORD"],
      }
    )

    # Upload para Play Store (track de teste interno)
    upload_to_play_store(
      track: 'internal',
      release_status: 'draft',
      aab: 'app/build/outputs/bundle/release/app-release.aab'
    )
  end

  desc "Rodar testes"
  lane :test do
    gradle(task: "test")
  end
end

Parte 2: Configuração de Assinatura de Código

Assinatura de Código iOS (Match)

O Fastlane Match armazena certificados em um repo Git privado (criptografado). Esta é a abordagem mais limpa para times.

# Inicializar Match
cd ios
fastlane match init

Escolha "git" e forneça uma URL de repo privado (crie um no GitHub para certificados).

# Gerar certificados e profiles
fastlane match appstore
fastlane match development

Atualize seu Fastfile para usar Match:

lane :beta do
  # Sincronizar certificados
  match(type: "appstore", readonly: true)

  increment_build_number(xcodeproj: "SeuApp.xcodeproj")

  build_app(scheme: "SeuApp", export_method: "app-store")

  upload_to_testflight
end

Assinatura de Código Android

Gere um keystore para builds de release:

cd android/app
keytool -genkey -v -keystore release.keystore -alias release \
  -keyalg RSA -keysize 2048 -validity 10000

IMPORTANTE: Nunca faça commit deste keystore no Git. Armazene-o de forma segura e referencie via variáveis de ambiente.

Atualize android/app/build.gradle:

android {
  ...
  signingConfigs {
    release {
      if (project.hasProperty('RELEASE_STORE_FILE')) {
        storeFile file(RELEASE_STORE_FILE)
        storePassword RELEASE_STORE_PASSWORD
        keyAlias RELEASE_KEY_ALIAS
        keyPassword RELEASE_KEY_PASSWORD
      }
    }
  }
  buildTypes {
    release {
      signingConfig signingConfigs.release
      ...
    }
  }
}

Parte 3: Workflows do GitHub Actions

Criar Workflow iOS

Crie .github/workflows/ios-deploy.yml:

name: iOS Build & Deploy

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-ios:
    runs-on: macos-14

    steps:
      - name: Checkout código
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'

      - name: Instalar dependências
        run: yarn install --frozen-lockfile

      - name: Instalar CocoaPods
        run: |
          cd ios
          bundle install
          bundle exec pod install

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true
          working-directory: ios

      - name: Rodar testes
        run: |
          cd ios
          bundle exec fastlane test

      - name: Build e deploy para TestFlight
        if: github.ref == 'refs/heads/main'
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
          FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
          FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
          FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
        run: |
          cd ios
          bundle exec fastlane beta

Criar Workflow Android

Crie .github/workflows/android-deploy.yml:

name: Android Build & Deploy

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-android:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout código
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'

      - name: Setup Java
        uses: actions/setup-java@v4
        with:
          distribution: 'zulu'
          java-version: '17'

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true
          working-directory: android

      - name: Instalar dependências
        run: yarn install --frozen-lockfile

      - name: Rodar testes
        run: |
          cd android
          bundle exec fastlane test

      - name: Decodificar keystore
        if: github.ref == 'refs/heads/main'
        run: |
          echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/release.keystore

      - name: Build e deploy para Play Store
        if: github.ref == 'refs/heads/main'
        env:
          KEYSTORE_PATH: ${{ github.workspace }}/android/app/release.keystore
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
          PLAY_STORE_JSON_KEY_DATA: ${{ secrets.PLAY_STORE_JSON_KEY_DATA }}
        run: |
          cd android
          bundle exec fastlane beta

Parte 4: Configuração de Secrets do GitHub

Navegue até repo GitHub → Settings → Secrets and variables → Actions. Adicione estes secrets:

Secrets iOS

  • MATCH_PASSWORD: Senha para o repo de certificados criptografados
  • MATCH_GIT_BASIC_AUTHORIZATION: PAT do GitHub codificado em Base64 para acesso ao repo Match
  • FASTLANE_USER: Email do seu Apple ID
  • FASTLANE_PASSWORD: Senha do seu Apple ID
  • FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: Senha específica do app de appleid.apple.com

Secrets Android

  • ANDROID_KEYSTORE_BASE64: Seu arquivo keystore codificado em base64
  • KEYSTORE_PASSWORD: Senha do keystore
  • KEY_ALIAS: Alias da chave
  • KEY_PASSWORD: Senha da chave
  • PLAY_STORE_JSON_KEY_DATA: JSON da conta de serviço do Google Play Console

Codificando Keystore para GitHub Secrets

# Codificar keystore para base64
base64 -i android/app/release.keystore -o keystore.txt

# Copie o conteúdo e adicione aos GitHub Secrets
cat keystore.txt

Obtendo JSON da Conta de Serviço Google Play

  1. Vá para Google Play Console → Setup → Acesso à API
  2. Crie conta de serviço (link para Google Cloud)
  3. Conceda papel de "Release Manager"
  4. Baixe a chave JSON
  5. Codifique e adicione aos GitHub Secrets
Dica de segurança: Nunca faça commit de secrets no Git. Sempre use GitHub Secrets ou variáveis de ambiente. Um certificado vazado pode comprometer todo seu pipeline de release.

Parte 5: Testando Seu Pipeline

Teste Localmente Primeiro

# Testar build iOS localmente
cd ios
bundle exec fastlane beta

# Testar build Android localmente
cd android
bundle exec fastlane beta

Corrija quaisquer problemas antes de fazer push para GitHub Actions.

Teste no GitHub Actions

Faça push para uma branch de feature e crie um PR:

git checkout -b test-ci-cd
git add .
git commit -m "Adicionar pipeline CI/CD"
git push origin test-ci-cd

Isso dispara os workflows, mas não faz deploy (apenas roda testes). Verifique a aba Actions para resultados.

Deploy para Produção

Faça merge para main para disparar deploy completo:

git checkout main
git merge test-ci-cd
git push origin main

Assista a mágica acontecer na aba Actions. Em 20-30 minutos, você terá builds no TestFlight e track interno da Play Store.

Parte 6: Configuração Avançada

Incremento de Versão

Automatize incrementos de versão com Fastlane:

# iOS - no Fastfile
lane :bump_version do
  increment_version_number(
    bump_type: "patch" # ou "minor", "major"
  )
end

# Android - no Fastfile
lane :bump_version do
  increment_version_code(
    gradle_file_path: "app/build.gradle"
  )
end

Notificações Slack

Seja notificado quando builds completarem:

# Adicionar ao Fastfile
after_all do |lane|
  slack(
    message: "Deploy do novo build realizado com sucesso!",
    channel: "#mobile-releases",
    slack_url: ENV["SLACK_WEBHOOK_URL"]
  )
end

error do |lane, exception|
  slack(
    message: "Build falhou: #{exception.message}",
    channel: "#mobile-releases",
    slack_url: ENV["SLACK_WEBHOOK_URL"],
    success: false
  )
end

Múltiplos Ambientes

Configure ambientes de staging e produção:

lane :staging do
  match(type: "adhoc")
  build_app(
    scheme: "SeuApp-Staging",
    export_method: "ad-hoc"
  )
  firebase_app_distribution(
    app: ENV["FIREBASE_APP_ID_STAGING"]
  )
end

lane :production do
  match(type: "appstore")
  build_app(
    scheme: "SeuApp-Production",
    export_method: "app-store"
  )
  upload_to_testflight
end

Parte 7: Solução de Problemas Comuns

iOS: Problemas de Certificado

Se você receber erros de certificado:

# Regenerar certificados
fastlane match nuke development
fastlane match nuke appstore
fastlane match development
fastlane match appstore

Android: Falha no Build Gradle

Aumente a memória do Gradle em android/gradle.properties:

org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

GitHub Actions: Sem Minutos

Runners macOS usam 10x minutos. Otimize:

  • Rodando apenas na branch main para deploys
  • Usando cache para dependências
  • Rodando testes em runners Linux mais rápidos quando possível

Resultados do Mundo Real

Depois de implementar este pipeline para nossos clientes:

  • Tempo de build manual: 3-4 horas → 0 horas
  • Frequência de release: Semanal → Diária
  • Falhas de build: 30% → 5% (capturadas no CI antes do release)
  • Tempo para corrigir bugs: 2-3 dias → 4 horas (iteração rápida)
  • Velocidade do time: Melhoria de 2x (devs focam em features, não em builds)

Análise de Custos

Aqui está o custo mensal desta configuração:

  • GitHub Actions: Grátis (2.000 minutos/mês para repos privados)
  • Minutos adicionais: $0,08/minuto para runners macOS (se exceder o tier grátis)
  • Fastlane: Grátis (open source)
  • Apple Developer: $99/ano
  • Google Play Developer: $25 única vez

Total: Efetivamente grátis para a maioria dos times pequenos e médios.

Melhores Práticas

  1. Sempre teste localmente primeiro - Não faça debug no CI/CD, é lento e gasta minutos
  2. Use cache agressivamente - Cache node_modules, Pods, Gradle
  3. Rode testes em cada PR - Capture bugs antes de chegarem na main
  4. Separe jobs de teste e deploy - Rode testes em cada PR, deploy apenas na main
  5. Monitore seus tempos de build - Otimize se builds excederem 15-20 minutos
  6. Mantenha secrets seguros - Use GitHub Secrets, nunca faça commit no Git
  7. Documente seu pipeline - Adicione README explicando a configuração para novos membros

Conclusão: Automatize Tudo

Configurar CI/CD para React Native leva um dia de trabalho, mas economiza centenas de horas durante a vida de um projeto. Builds manuais são propensos a erros, demorados e não escalam conforme seu time cresce.

Com esta configuração, você pode:

  • Deploy para TestFlight e Play Store com um único git push
  • Rodar testes automatizados em cada mudança de código
  • Lançar correções de bugs em horas, não dias
  • Integrar novos desenvolvedores sem ensiná-los o processo de build
  • Dormir melhor sabendo que seus releases são consistentes e confiáveis

A configuração inicial pode parecer complexa, mas siga este guia passo a passo e você terá um pipeline CI/CD pronto para produção até o final do dia. Seu eu do futuro vai te agradecer.

Precisa de ajuda configurando CI/CD para seu app React Native? Na ThreadCode, configuramos pipelines CI/CD para mais de 20 apps React Native de produção. Vamos automatizar seus releases.