Build iOS automatici con GitHub Actions e Match
Configurare la CI/CD per le app Capacitor può essere complesso e richiedere tempo. Ecco cosa devi sapere:
Consigliato per le nuove build: usa Capgo Build
Ora consigliamo di usare Capgo Build con Capgo CLI per le build native Capacitor. Questa guida a Fastlane Match è pensata per i team che mantengono pipeline GitHub Actions esistenti, ma per le nuove build iOS conviene usare Capgo CLI, così non devi mantenere tu Fastlane, i repository Match, i runner Xcode, i certificati e gli script di upload.
Capgo Build per CI/CD con Capgo
Evita la manutenzione di Fastlane Match, runner Xcode, certificati, provisioning profile e script di upload. Build Capgo esegue build iOS native firmate dalla tua pipeline CI/CD esistente:
- Si integra con la tua pipeline: Avvia Capgo Build tramite GitHub Actions, GitLab CI, Jenkins o script locali dopo la web build e
npx cap sync. - Firma tramite segreti CI: Mantieni le chiavi di App Store Connect, certificati, provisioning profile, password e team ID nei tuoi segreti CI.
- Nessuna gestione runner native: Capgo Build offre ambienti di build Apple gestiti, così non dovrai occuparti di macOS runner, immagini Xcode, Fastlane o repository Match.
- Artifacts and submission: Scarica gli artifact firmati per la QA o invia le release build tramite Capgo CLI.
Prezzi
- I piani Capgo partono da $12/mese
- Include aggiornamenti OTA e circa 15 Native Build al mese
- I minuti di build extra sono fatturati al minuto tramite crediti
Configura Capgo Build in CI/CD
Guida alla configurazione manuale
Ecco cosa devi fare:
Continuous Delivery per iOS con Fastlane e GitHub Actions tramite match
Prerequisiti
Prima di continuare con il tutorial…
- Assicurati di avere Fastlane installato sulla tua macchina di sviluppo.
- Iscrizione al programma per sviluppatori iOS.
- Voglia di leggere 😆…
- Un team con molti dev, altrimenti consigliamo di usare fastlane cert per workflow più semplici.
Da sapere sul prezzo

https://github.com/features/actions
Il servizio è 'gratuito' entro certi limiti, a seconda della macchina scelta.
We are going to use a macOS machine; nello screenshot vedi prezzi e limiti (prezzi validi al momento del tutorial, soggetti a variazioni)
🔴 Presi nota requisiti e costi, se ti va, procediamo...
📣 In the post we assume that we have the app created in iTunes connect, we do have the certificates of the Apple ecosystem, everything will be copied by Fastlane!
Tuffiamoci subito 🤿
Passaggi descritti nel post
- Usare App Store Connect API con Fastlane Match
- Requisiti
- Creazione di una API Key per App Store Connect
- Usare una API Key di App Store Connect
- Copia i file di Fastlane
- Configura Fastlane match
1. Usare API di App Store Connect con Fastlane Match
Da febbraio 2021, l'autenticazione a due fattori o la verifica in due passaggi è obbligatoria per accedere ad App Store Connect. Questo livello di sicurezza extra per il tuo Apple ID garantisce che solo tu possa accedere al tuo account.
Da Supporto Apple
Per iniziare con match dovrai revocare i certificati esistenti. Non preoccuparti, riceverai subito quelli nuovi.
Requisiti
Per poter usare API di App Store Connect, Fastlane ha bisogno di tre elementi.
- ID dell'issuer.
- ID chiave.
- File della chiave o contenuto della chiave.
Creazione di una API Key per App Store Connect
Per generare le chiavi devi avere i permessi di Admin in App Store Connect. Se non li hai, puoi indicare questo articolo alla persona responsabile e seguire le istruzioni qui sotto.
1 — Accedi a App Store Connect.
2 — Seleziona Utenti e accesso.

3 — Seleziona la scheda Integrazioni.

4 — Clicca su Generate API Key o sul pulsante Add (+).

5 — Inserisci un nome per la chiave. Il nome serve solo come riferimento e non fa parte della chiave stessa.

Alla voce Access, seleziona il ruolo per la chiave. I ruoli validi per le chiavi sono gli stessi degli utenti del tuo team. Consulta permessi dei ruoli. Ti consigliamo di selezionare App Manager.
7 — Clicca su Generate.
L'accesso di una chiave API non può essere limitato a singole app.
Nella pagina compaiono il nome della nuova chiave, il key ID, il link per il download e altre informazioni.

Qui puoi recuperare tutte e tre le informazioni necessarie.
<1> ID dell'issue.
<2> Key ID.
<3> Clicca su “Download API Key” per scaricare la tua API private key. Il link per il download compare solo se la private key non è ancora stata scaricata. Apple non conserva una copia della private key. Quindi puoi scaricarla una sola volta.
🔴 Conserva la tua private key in un luogo sicuro. Non condividere mai le chiavi, non salvarle in un repository code e non includerle nel code lato client.
Usare una API Key di App Store Connect
Per creare il token JWT per l'autorizzazione, sono necessari il file API Key (il file .p8 scaricato), il key ID e l'issuer ID. Esistono diversi modi per inserire queste informazioni in Fastlane utilizzando la nuova action di Fastlane, app_store_connect_api_key. Puoi scoprire altri modi nella documentazione di Fastlane. Ti mostro questo metodo perché credo sia il modo più semplice per lavorare con la maggior parte delle CI, dove puoi impostare variabili d'ambiente.
Ora possiamo gestire Fastlane con la API key di App Store Connect, fantastico!
2. Copia i file di Fastlane
Fastlane è una libreria Ruby creata per automatizzare i comuni task di sviluppo mobile. Con Fastlane puoi configurare "lane" personalizzate che raggruppano una serie di "action" per eseguire operazioni che normalmente faresti su Android Studio. Puoi fare moltissimo con Fastlane, ma per questo tutorial useremo solo poche action fondamentali.
Crea una cartella Fastlane nella root del tuo progetto e copia i seguenti file: Fastfile
default_platform(:ios)
DEVELOPER_APP_IDENTIFIER = ENV["DEVELOPER_APP_IDENTIFIER"]
DEVELOPER_APP_ID = ENV["DEVELOPER_APP_ID"]
PROVISIONING_PROFILE_SPECIFIER = ENV["PROVISIONING_PROFILE_SPECIFIER"]
TEMP_KEYCHAIN_USER = ENV["TEMP_KEYCHAIN_USER"]
TEMP_KEYCHAIN_PASSWORD = ENV["TEMP_KEYCHAIN_PASSWORD"]
APPLE_ISSUER_ID = ENV["APPLE_ISSUER_ID"]
APPLE_KEY_ID = ENV["APPLE_KEY_ID"]
APPLE_KEY_CONTENT = ENV["APPLE_KEY_CONTENT"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
def delete_temp_keychain(name)
delete_keychain(
name: name
) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end
def create_temp_keychain(name, password)
create_keychain(
name: name,
password: password,
unlock: false,
timeout: 0
)
end
def ensure_temp_keychain(name, password)
delete_temp_keychain(name)
create_temp_keychain(name, password)
end
platform :ios do
lane :build do
build_app(
configuration: "Release",
workspace: "./ios/App/App.xcworkspace",
scheme: "App",
export_method: "app-store",
export_options: {
provisioningProfiles: {
DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
}
}
)
end
lane :refresh_profiles do
match(
type: "development",
force: true)
match(
type: "adhoc",
force: true)
end
desc "Register new device"
lane :register_new_device do |options|
device_name = prompt(text: "Enter the device name: ")
device_udid = prompt(text: "Enter the device UDID: ")
device_hash = {}
device_hash[device_name] = device_udid
register_devices(
devices: device_hash
)
refresh_profiles
end
lane :closed_beta do
keychain_name = TEMP_KEYCHAIN_USER
keychain_password = TEMP_KEYCHAIN_PASSWORD
ensure_temp_keychain(keychain_name, keychain_password)
api_key = app_store_connect_api_key(
key_id: APPLE_KEY_ID,
issuer_id: APPLE_ISSUER_ID,
key_content: APPLE_KEY_CONTENT,
duration: 1200,
in_house: false
)
match(
type: 'appstore',
git_basic_authorization: Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"),
readonly: true,
keychain_name: keychain_name,
keychain_password: keychain_password,
api_key: api_key
)
gym(
configuration: "Release",
workspace: "./ios/App/App.xcworkspace",
scheme: "App",
export_method: "app-store",
export_options: {
provisioningProfiles: {
DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
}
}
)
pilot(
apple_id: "#{DEVELOPER_APP_ID}",
app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
skip_waiting_for_build_processing: true,
skip_submission: true,
distribute_external: false,
notify_external_testers: false,
ipa: "./App.ipa"
)
delete_temp_keychain(keychain_name)
end
lane :submit_review do
version = ''
Dir.chdir("..") do
file = File.read("package.json")
data = JSON.parse(file)
version = data["version"]
end
deliver(
app_version: version,
submit_for_review: true,
automatic_release: true,
force: true, # Skip HTMl report verification
skip_metadata: false,
skip_screenshots: false,
skip_binary_upload: true
)
end
end
Appfile
app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"])
apple_id(ENV["FASTLANE_APPLE_ID"])
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"])
Configura Fastlane match
Fastlane match è un nuovo approccio alla firma code su iOS. Fastlane match permette ai team di gestire facilmente i certificati e i provisioning profile necessari per le tue app iOS.
Crea un nuovo repository privato chiamato certificates, ad esempio sul tuo account personale GitHub o sulla tua organizzazione.
Inizializza Fastlane match per la tua app iOS.
fastlane match init
Poi seleziona l'opzione #1 (Git Storage).
[01:00:00]: fastlane match supports multiple storage modes, please select the one you want to use:1. git2. google_cloud3. s3?
Inserisci l'URL del repository appena creato.
[01:00:00]: Please create a new, private git repository to store the certificates and profiles there[01:00:00]: URL of the Git Repo: <YOUR_CERTIFICATES_REPO_URL>
Ora, all'interno della cartella Fastlane, troverai un file chiamato Matchfile e
_git_url_deve essere impostato sull'URL HTTPS del repository dei certificati. Puoi anche usare SSH, ma richiede uno step diverso.
# ios/Matchfilegit_url("https://github.com/gitusername/certificates")storage_mode("git")type("appstore")
Ora passiamo a generare i certificati e inserisci le tue credenziali quando Fastlane Match te le chiede.
Ti verrà chiesto di inserire una passphrase. Ricordala bene, perché verrà usata in seguito dalle GitHub Actions per decriptare il repository dei certificati.
fastlane match appstore
Se è andato tutto bene, dovresti vedere qualcosa del genere:
[01:40:52]: All required keys, certificates and provisioning profiles are installed 🙌
Se hai riscontrato problemi con GitHub e i permessi necessari, forse questo post ti aiuterà a generare i token di autenticazione per git.
I certificati e i provisioning profile generati vengono caricati nelle risorse del repository dei certificati

Infine, apri il tuo project in Xcode e aggiorna il provisioning profile per la release configuration della tua app.

Alcune note importanti 💡
MATCH
Affinché la CI/CD possa importare certificati e provisioning profile, deve avere accesso al repository dei certificati. Puoi farlo generando un personal access token (creato in precedenza) con i permessi di lettura per i repository privati.
In GitHub, vai su Impostazioni → Impostazioni sviluppatore → Token di accesso personale → clicca su Generate New Token → seleziona lo repo scope → quindi clicca Generate token.

Conserva una copia del personal access token generato. Ti servirà in seguito per la variabile d'ambiente GIT_TOKEN.
Poi sostituisci il file match generato nella cartella Fastlane con Matchfile
CERTIFICATE_STORE_URL = ENV["CERTIFICATE_STORE_URL"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
FASTLANE_APPLE_ID = ENV["FASTLANE_APPLE_ID"]
git_url(CERTIFICATE_STORE_URL)
storage_mode("git")
type("appstore")
git_basic_authorization(Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"))
username(FASTLANE_APPLE_ID)
Questo verrà usato dalle GitHub Actions per importare certificati e provisioning profiles. La variabile sarà impostata nei GitHub Secrets, evitando di scriverli in chiaro nel file.
Elaborazione Build
In GitHub Actions, la fatturazione avviene in base ai minuti utilizzati per eseguire il tuo workflow CI/CD. Per esperienza, occorrono circa 10–15 minuti prima che una build venga elaborata in App Store Connect.
Per i progetti privati, il costo stimato per build può arrivare a $0.08/min x 15 min = $1.2, o più, a seconda della configurazione o delle dipendenze del tuo progetto.
Se hai le stesse preoccupazioni sui costi che ho io per i progetti privati, puoi mantenere il skip_waiting_for_build_processing a true.
Qual è l'inghippo? Devi aggiornare manualmente la conformità della tua app in App Store Connect dopo che il build è stato processato, per poter distribuire il build ai tuoi utenti.
Questo è solo un parametro opzionale da aggiornare se vuoi risparmiare sui minuti di build per i progetti privati. Per i progetti free, non dovrebbe essere affatto un problema. Vedi prezzi.
3. Configura le GitHub Actions
Configura i secret di GitHub
Ti sei mai chiesto da dove vengono i valori di ENV vengono? Beh, non è più un segreto: vengono dai secret del tuo progetto. 🤦

1. APP_STORE_CONNECT_TEAM_ID - l'ID del tuo team App Store Connect se fai parte di più team.
2. DEVELOPER_APP_ID - in App Store Connect, vai sull'app → Informazioni sull'app → Scorri verso la General Information sezione della tua app e cerca Apple ID.
3. DEVELOPER_APP_IDENTIFIER - il bundle identifier della tua app.
4. DEVELOPER_PORTAL_TEAM_ID - l'ID del team sul Developer Portal, se fai parte di più team.
5. FASTLANE_APPLE_ID - l'Apple ID o l'email developer che usi per gestire l'app.
6. GIT_USERNAME & GIT_TOKEN - Il tuo username git e il tuo personal access token.
7. MATCH_PASSWORD - la passphrase impostata durante l'inizializzazione di match verrà utilizzata per decriptare i certificati e i provisioning profile.
8. PROVISIONING_PROFILE_SPECIFIER - match AppStore <YOUR_APP_BUNDLE_IDENTIFIER>, es. match AppStore com.domain.blabla.demo.
9. TEMP_KEYCHAIN_USER & TEMP_KEYCHAIN_PASSWORD - assegna un utente e una password temporanei per il keychain del workflow.
10. APPLE_KEY_ID — Chiave App Store Connect API 🔺Key ID.
11. APPLE_ISSUER_ID — Chiave API di App Store Connect 🔺Issuer ID.
12. APPLE_KEY_CONTENT — App Store Connect API Key 🔺 File della chiave o contenuto della chiave di .p8, controllalo
13. CERTIFICATE_STORE_URL — L'URL del repo delle tue chiavi Match (es: https://github.com/***/fastlane_match.git)
4. Configura il file di workflow di GitHub
Crea una directory per il workflow di GitHub.
cd .github/workflows
All'interno della workflow cartella, crea un file chiamato build-upload-ios.ymle aggiungi quanto segue.
name: Build source code on ios
on:
push:
tags:
- '*'
jobs:
build_ios:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v6
- name: set Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: npm
- name: Install dependencies
id: install_code
run: npm ci
- name: Build
id: build_code
run: npm run build
- uses: actions/cache@v5
with:
path: ios/App/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Sync
id: sync_code
run: npx cap sync
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
- uses: maierj/fastlane-action@v2.3.0
env:
DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
PROVISIONING_PROFILE_SPECIFIER: match AppStore ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
CERTIFICATE_STORE_URL: https://github.com/${{ secrets.CERTIFICATE_STORE_REPO }}.git
GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
GIT_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
FASTLANE_APPLE_ID: ${{ secrets.FASTLANE_APPLE_ID }}
MATCH_USERNAME: ${{ secrets.FASTLANE_APPLE_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
with:
lane: closed_beta
- name: Upload release bundle
uses: actions/upload-artifact@v2
with:
name: ios-release
path: ./App.ipa
retention-days: 60
Questo workflow dovrebbe essere attivato dopo ogni GitHub tag, se hai bisogno di automatizzare i tag, consulta Build e release automatiche con le action di GitHub prima.
Questo workflow scaricherà le dipendenze NodeJS, le installerà e builderrà la tua app JavaScript.
Ogni volta che invii un nuovo commit, verrà creata una release in TestFlight.
La tua app non deve usare Ionic, è necessario solo Capacitor. Può avere vecchi moduli Cordova, ma è preferibile usare il plugin JS Capacitor.
5. Trigger del workflow
Crea un commit
Esegui un commit, dovresti vedere il workflow attivo nel repository.
Avvia il workflow
Pusha i nuovi commit sul branch main o development per avviare il workflow.

Dopo pochi minuti, la build sarà disponibile nella dashboard di App Store Connect.

Si può deployare da locale?
Sì, è possibile e non richiede alcuno sforzo.
Immagina di avere un repository privato, di aver esaurito i minuti del piano free e di non voler pagare per nuovi release, o forse preferisci inviare l'app manualmente.
Cominciamo!
Ok, per prima cosa dobbiamo creare in my_project_path/fastlane percorso di un file chiamato .env, nello stesso percorso di Fastfile, per poter creare le stesse secret proprietà presenti nel nostro _GitHub, come mostrato sotto:
file .env per il deploy da locale
Ora puoi andare nel terminale ed eseguire il Fastlane dalla tua macchina:
fastlane closed_beta
❌ Nota importante sul .env file, dato che preferiamo non esporre questi dati, dobbiamo aggiungerli nel nostro .gitignore, in questo modo: ❌
fastlane/*.env
Dovrebbe funzionare esattamente come con GitHub Actions sulla macchina remota, ma sulla nostra macchina locale. 🍻

Esecuzione da terminale: $ Fastlane closed_beta
Se sei arrivato fin qui, complimenti: ora hai un processo completamente automatizzato per le tue app iOS con Fastlane e GitHub Actions.
Ogni volta che invii un nuovo commit, verrà generata una release nella beta channel della Google Play console. Aggiornerò questo blog con i vostri feedback; se avete domande o suggerimenti, scrivetemi via email. martin@capgo.app
Build sul tuo dispositivo
Se hai ancora bisogno di fare il build sul tuo dispositivo, devi aggiungerlo manualmente al provisioning.
Collega il dispositivo al Mac e apri il menu del dispositivo
Poi copia il tuo identifier
E poi avvia il comando:
fastlane register_new_device
ti verrà chiesto di impostare il nome del dispositivo e l'identifier:

se riscontri problemi
Se hai problemi con il dispositivo di dev che non riesce a testare, ecc., di solito questo risolve.
C'è un comando magico che può salvarti:
fastlane match nuke development
fastlane match development
Poi: Pulisci il progetto premendo Shift(⇧)+Command(⌘)+K o selezionando Product > Clean (potrebbe essere indicato come “Clean Build Folder”)
Poi prova a eseguire nuovamente l'app sul tuo dispositivo.
Grazie
Questo blog si basa sui seguenti articoli:
- Continuous delivery per iOS con Fastlane e le action GitHub
- Documentazione Fastlane
- Questo messaggio GitHub di @mrogunlana
Continua con l'Automatic Capacitor IOS build con GitHub actions usando match
Se stai usando Build automatica iOS di Capacitor con GitHub Actions usando match per pianificare l'automazione CI/CD e connetterla a CI/CD con Capgo per il workflow del prodotto in Capgo CI/CD, Capgo Native Build per il workflow di prodotto in Capgo Native Builds, Integrazioni Capgo per il workflow di prodotto in Integrazioni Capgo, Integrazione CI/CD per i dettagli implementativi in CI/CD Integration, e Integrazione GitHub Actions per i dettagli implementativi in GitHub Actions Integration.