Recentemente, enfrentei o desafio de estruturar uma pipeline de CI/CD completa para um aplicativo corporativo desenvolvido em Flutter. O objetivo era claro: automatizar, proteger e agilizar o deploy para Android e iOS. Neste artigo, compartilho os aprendizados, as soluções para os obstáculos encontrados e os exemplos práticos que formaram a espinha dorsal dessa implementação.
Por que investir em CI/CD mobile?
Em um cenário de desenvolvimento ágil, automatizar os processos de build, teste e deploy não é um luxo, mas uma necessidade. Para aplicações mobile, essa urgência é amplificada: os builds são demorados e consomem recursos, as dependências evoluem rapidamente e cada loja de aplicativos (Google Play e App Store) impõe seu próprio conjunto de regras e exigências. Uma pipeline bem arquitetada garante entregas rápidas, seguras e com a consistência que um ambiente corporativo exige.
Estrutura dos Workflows
Para organizar o processo, a pipeline foi modularizada em workflows distintos no GitHub Actions. Cada workflow é uma sequência de jobs que rodam em ambientes específicos, conhecidos como runners.
Runners: São as máquinas virtuais que executam seus jobs. Para builds Android, utilizamos os runners ubuntu-latest fornecidos pelo GitHub, que são eficientes e econômicos. Para iOS, a compilação exige o macOS. Como os runners macOS do GitHub têm um custo associado, optamos por configurar um runner self-hosted em uma máquina Mac local, garantindo controle total sobre o ambiente e otimização de custos.
Dividimos nossa esteira em workflows específicos, cada um com sua responsabilidade:
-
feature-workflow.yml
: Valida código novo, executa lint, testes unitários e análise SAST (Static Application Security Testing), garantindo qualidade e segurança desde o início. -
staging-workflow.yml
: Prepara builds de homologação, rodando validações e testes automatizados. -
mobile-workflow.yml
: Geram builds de produção e fazem o deploy para Google Play e App Store.
Controle dos Fluxos
Para garantir que cada etapa só rode no momento certo e que a pipeline de produção só seja executada após a validação em staging, utilizei gatilhos condicionais. Isso orquestra a transição entre ambientes de forma segura.
on:
workflow_dispatch: # Permite disparo manual
workflow_run:
workflows: ["staging-workflow"] # Nome do workflow que deve terminar
types:
- completed # Apenas quando ele for concluído
jobs:
build:
# A condição abaixo garante que o job só rode se o disparo for manual
# ou se o workflow de staging anterior tiver sido concluído com sucesso.
if: >
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'staging')
runs-on: ubuntu-latest
...
Isso permite que o workflow seja disparado manualmente ou automaticamente após a conclusão bem-sucedida do workflow de staging. O uso do workflow_dispatch
também foi essencial para execuções manuais e testes pontuais.
Configuração do Ambiente
Antes de qualquer build, preparei o ambiente instalando Flutter, Java e aplicando cache para dependências, acelerando o processo e evitando downloads desnecessários:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true
- name: Cache Flutter pub dependencies
uses: actions/cache@v4
with:
path: ~/.pub-cache
key: ${{ runner.os }}-pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('**/pubspec.lock') }}
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
No iOS, também utilizei cache para CocoaPods e limpei o ambiente antes de cada build:
- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/pubspec.lock') }}
Assinatura dos Apps
A assinatura digital é obrigatória para publicação nas lojas. No Android, trabalhei com keystore e no iOS, com certificados e provisioning profiles:
- name: Setup iOS certificates
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.CERTIFICATE_BASE64 }}
p12-password: ${{ secrets.CERTIFICATE_PASSWORD }}
- name: Download Provisioning Profiles
uses: apple-actions/download-provisioning-profiles@v2
with:
bundle-id: com.seuapp.id
profile-type: IOS_APP_STORE
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
O GitHub trabalha de uma forma bem peculiar com certificados, exigindo que o sejam salvos como secret em base64 e decodificado em tempo de execução no runner.
No Android, o keystore foi salvo via secret em base64 e decodificado no runner:
- name: Setup Android signing
run: |
echo "$KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks
Jobs de Qualidade
Implementei jobs de lint, testes unitários e análise estática de segurança (SAST) para garantir qualidade e segurança:
- name: Lint Flutter
run: flutter analyze
- name: Testes Unitários
run: flutter test --coverage
- name: SAST com MobSF
uses: MobSF/mobsfscan@main
with:
args: .
O SAST foi crucial para identificar vulnerabilidades cedo, especialmente em apps que lidam com dados sensíveis. Cada job gera relatórios que ajudam a manter a qualidade do código alta.
Deploy Automatizado
Com os builds assinados, o último passo é o deploy, utilizei actions específicas para upload dos builds:
-
Android:
r0adkll/upload-google-play@v1
para enviar o bundle diretamente à Google Play. -
iOS:
apple-actions/upload-testflight-build@v3
para subir o IPA ao TestFlight.
Exemplo de upload para TestFlight:
- name: Upload app to TestFlight
uses: apple-actions/upload-testflight-build@v3
with:
app-path: ${{ steps.find-ipa.outputs.ipa-path }}
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
Dificuldades e Aprendizados
Um dos maiores desafios foi adaptar o pipeline iOS para rodar em runner Mac self-hosted, já que o GitHub Actions não oferece MacOS gratuito. Também precisei ajustar versões de Java e Gradle para evitar conflitos e falhas silenciosas nos builds Android.
No fim, consegui uma esteira automatizada, segura e eficiente, que valida, testa, assina e publica os apps nas lojas com mínimo esforço manual. Se você está montando sua pipeline mobile, recomendo investir tempo nos jobs de qualidade, na configuração de secrets e no controle dos fluxos. O resultado compensa!
Top comments (0)