Tüm Yazılar
KategoriFlutter
Okuma Süresi
13 dk
Yayın Tarihi
2025-02-18
Kelime Sayısı
2.822kelime

Kahveni hazırla - bu içerikli bir makale!

Flutter CI/CD: GitHub Actions ve Fastlane ile Otomatik Yayın

Özet

Flutter CI CD GitHub Actions ve Fastlane ile analyze-test-build-deploy pipeline'ı nasıl kurulur: match sertifika yönetimi, TestFlight/Play Internal Testing yüklemesi, runner maliyet stratejisi.

  • Pipeline'ı analyze/test (ucuz ubuntu-latest) ve build/deploy (macos-14) olmak üzere ayrı job'lara böl.
  • Sertifika ve provisioning profile'ları fastlane match ile git/Google Cloud/S3'te paylaş, CI'da readonly: true kullan.
  • TestFlight'a pilot, Play Internal Testing'e upload_to_play_store (track: internal) ile otomatik yükle.
  • Build numarasını github.run_number ile otomatikleştir, tüm secret'ları GitHub repository secrets üzerinden yönet.
Flutter CI/CD: GitHub Actions ve Fastlane ile Otomatik Yayın

Her push'ta terminalden flutter build ipa çalıştırıp App Store Connect'e elle yüklemek, tek başına çalışırken sorun çıkarmaz; ama ekip büyüdüğü ve platform sayısı arttığı anda sürdürülemez hale gelir. GitHub Actions ve Fastlane ikilisiyle kurulan bir Flutter CI CD GitHub Actions pipeline'ı, kod her push'landığında analiz-test-build-deploy adımlarını otomatik yürütür ve insan hatasını süreçten çıkarır. Bu rehberde bu pipeline'ı sıfırdan, macOS runner maliyetini gözeterek kuruyoruz.

💡 Pro Tip: Deploy job'ını yalnızca main branch'e push'larda tetikle; her PR'da TestFlight'a yükleme yapmak hem dakika bütçeni hem de test cihazlarındaki tester'ların bildirim kutusunu gereksiz yere şişirir.

İçindekiler

Pipeline'ın 4 Aşaması: Analyze, Test, Build, Deploy

Bir Flutter CI/CD pipeline'ını tek dev job'a sıkıştırmak yerine dört ayrı adıma bölmek, hem hatayı erken yakalar hem de pahalı macOS runner dakikalarını israf etmez. Flutter'ın resmi continuous delivery rehberi de dağıtımı fastlane üzerinden ayrı aşamalar halinde kurmayı önerir.

Aşama
Komut / Araç
Amaç
Analyze
flutter analyze
Statik analiz, lint hataları — saniyeler sürer, Linux runner'da çalışabilir
Test
flutter test
Unit + widget testleri — Linux runner'da çalışır, macOS gerektirmez
Build
flutter build ipa / flutter build appbundle
Platform binary'lerini üretir — iOS için macOS runner şart
Deploy
fastlane pilot / supply
TestFlight ve Play Internal Testing'e yükleme

Kritik nokta: yalnızca build ve deploy adımları macOS runner'a ihtiyaç duyar. Analyze ve test adımlarını ucuz ubuntu-latest runner'da çalıştırmak, dakika faturanı doğrudan düşürür.

GitHub Actions Workflow İskeleti

GitHub Actions'ta bir workflow, .github/workflows/ altındaki bir YAML dosyasıdır; name, on (tetikleyici) ve jobs alanlarından oluşur, her job da runs-on (çalışacağı runner) ve steps (adım listesi) içerir. Aşağıdaki iskelet, 4 aşamayı ayrı job'lara böler; test job'u Linux'ta, build/deploy job'ları macOS'ta çalışır:

yaml
1name: Flutter CI/CD
2 
3on:
4 push:
5 branches: [main]
6 pull_request:
7 branches: [main]
8 
9jobs:
10 analyze_test:
11 runs-on: ubuntu-latest
12 steps:
13 - uses: actions/checkout@v4
14 - uses: subosito/flutter-action@v2
15 with:
16 flutter-version: "3.27.0"
17 - run: flutter pub get
18 - run: flutter analyze
19 - run: flutter test
20 
21 build_deploy:
22 needs: analyze_test
23 if: github.ref == 'refs/heads/main'
24 runs-on: macos-14
25 steps:
26 - uses: actions/checkout@v4
27 - uses: subosito/flutter-action@v2
28 with:
29 flutter-version: "3.27.0"
30 - run: flutter pub get
31 - name: Install signing via fastlane match
32 run: cd ios && bundle exec fastlane signing
33 env:
34 MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
35 - run: flutter build ipa --release
36 - name: Deploy via fastlane
37 run: cd ios && bundle exec fastlane release
38 env:
39 APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}

subosito/flutter-action topluluk aksiyonu, runner'a belirli bir Flutter SDK sürümünü kurar; resmi GitHub Actions dokümantasyonu bir workflow'un name, on, jobs, runs-on ve steps alanlarından oluştuğunu ve uses: actions/checkout@v4 ile reponun kodunu runner'a çektiğini tarif eder.

Tetikleyiciler (on)

on bloğu, workflow'un ne zaman çalışacağını belirler. Yukarıdaki örnekte hem push hem pull_request tetikleyicisi var; ama build_deploy job'undaki if: github.ref == 'refs/heads/main' koşulu, PR'larda yalnızca analyze_test job'unun çalışmasını, build ve deploy adımlarının ise sadece main'e gerçek bir push geldiğinde tetiklenmesini sağlıyor. Bu ayrım, her PR açılışında gereksiz macOS runner dakikası harcamanın önüne geçiyor.

Job'lar Arası Bağımlılık (needs)

needs: analyze_test satırı, build_deploy job'unun analyze_test job'u başarıyla bitmeden başlamayacağını garanti eder. Analiz veya testler kırmızı çıkarsa build hiç tetiklenmez — bu da bozuk bir kod parçasının TestFlight'a kadar ilerlemesini daha en baştan engeller.

macOS Runner Maliyeti ve Cache Stratejisi

macOS runner'lar GitHub Actions'ta Linux runner'lardan belirgin şekilde daha pahalıdır; bu yüzden yukarıdaki iskelette analyze/test job'unu bilerek ubuntu-latest'e, yalnızca build/deploy'u macos-14'e verdik. Maliyeti daha da azaltmanın ikinci yolu cache'tir.

GitHub'ın resmi actions/cache dokümantasyonu, aksiyonun önce key ile tam eşleşme aradığını, bulamazsa restore-keys ile sırayla kısmi eşleşmeye baktığını belirtir. Flutter projelerinde pub cache'i şu şekilde önbelleğe alabilirsin:

yaml
1- uses: actions/cache@v4
2 with:
3 path: |
4 ~/.pub-cache
5 **/.dart_tool
6 key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
7 restore-keys: |
8 ${{ runner.os }}-pub-

pubspec.lock değişmediği sürece bu cache aynı kalır ve flutter pub get saniyeler içinde biter. iOS tarafında CocoaPods için de benzer şekilde ios/Pods dizinini Podfile.lock hash'ine göre cache'leyebilirsin — bu, macOS runner'ın en yavaş adımlarından birini (pod install) doğrudan kısaltır.

Fastlane match ile Sertifika ve Profile Yönetimi

CI'da iOS imzalama en çok baş ağrıtan kısımdır çünkü sertifika ve provisioning profile'lar normalde tek bir Mac'e bağlıdır. fastlane'in match aracı bunu çözer: resmi dokümantasyona göre match "gerekli tüm sertifikaları ve provisioning profile'ları oluşturur ve ayrı bir git deposunda, Google Cloud'da veya Amazon S3'te saklar", böylece ekip ve CI aynı imzalama kimliğini paylaşır.

Depolama
Nasıl çalışır
Ne zaman tercih edilir
Git deposu
Sertifikalar OpenSSL ile şifrelenip private repoya push edilir
Küçük-orta ekip, ekstra bulut hesabı istemiyorsan
Google Cloud
Google'ın yönettiği anahtarlarla şifrelenip GCS'de saklanır
Zaten GCP kullanan ekipler
Amazon S3
Kendi sağladığın bucket içinde saklanır
Zaten AWS kullanan ekipler

Matchfile Oluşturma

fastlane match init çalıştırdığında bir Matchfile oluşur:

ruby
1git_url("https://github.com/<org>/certificates")
2app_identifier("com.example.app")
3username("[email protected]")

CI Lane'inde Kullanım

match, imzalanmış build'i oluşturan adımdan önce çağrılmalıdır — dokümantasyon bunu açıkça "match, gym ile build almadan önce çağrılmalı" diye belirtir. Bu pipeline'da IPA'yı fastlane'in gym aksiyonu değil workflow'daki flutter build ipa adımı ürettiği için match ayrı bir signing lane'inde durur ve workflow'da build adımından önce çağrılır:

ruby
1lane :signing do
2 match(type: "appstore", readonly: true)
3end

CI'da readonly: true kullanmak, pipeline'ın yanlışlıkla yeni sertifika üretmesini engeller — bu ayrım özellikle çoklu geliştirici + CI kombinasyonunda kritik.

TestFlight ve Play Internal Testing Yüklemesi

iOS: pilot ile TestFlight

Build imzalandıktan sonra sıra dağıtımda. fastlane'in pilot aksiyonu (diğer adıyla upload_to_testflight) build'i TestFlight'a yükler; resmi dokümantasyon API key yönteminin avantajları arasında 2FA gerekmemesini ve Apple ID'ye göre daha iyi performansı sayar:

ruby
1lane :release do
2 pilot(
3 ipa: Dir[File.expand_path("../../build/ios/ipa/*.ipa")].first,
4 api_key_path: "./fastlane/api_key.json",
5 skip_waiting_for_build_processing: true
6 )
7end

ipa parametresi yüklenecek dosyanın yolunu verir: lane yeniden build almaz, workflow'daki flutter build ipa adımının build/ios/ipa/ altına ürettiği IPA'yı yükler (Fastfile'daki Ruby kodu fastlane/ klasöründen çalıştığı için yol iki üst dizinden başlar). skip_waiting_for_build_processing: true parametresi, fastlane'in build'in Apple tarafında işlenmesini bekleyip beklememesini kontrol eder; CI job'unun build yüklendikten sonra dakikalarca askıda kalmasını istemiyorsan bu değeri true bırakman, job'un yüklemeyi tetikleyip hemen bitmesini sağlar — ancak bu durumda distribute_external çalışmaz, build otomatik olarak tester'lara dağıtılmaz.

Android: supply ile Play Internal Testing

Android tarafında karşılığı upload_to_play_store (diğer adıyla supply) aksiyonudur. Dokümantasyona göre varsayılan track seçenekleri production, beta, alpha, internal'dır — CI'dan her push'ta doğrudan production'a değil, internal track'e yüklemek doğru alışkanlıktır:

ruby
1platform :android do
2 lane :release do
3 upload_to_play_store(
4 track: "internal",
5 json_key: ENV["PLAY_STORE_JSON_KEY"]
6 )
7 end
8end

json_key parametresi, Google Cloud servis hesabına ait kimlik dosyasının yolunu (veya CI'da ortam değişkeninden gelen içeriğini) gösterir. Bu iki lane'i aynı Fastfile'da platform :ios do ... end ve platform :android do ... end bloklarıyla ayırman, tek dosyadan iki platformu da yönetmeni sağlar.

Sürüm Numarası ve Build Number Otomasyonu

Flutter'da sürüm bilgisi pubspec.yaml içinde tek satırda tutulur; resmi dokümantasyona göre format {version}+{build-number} şeklindedir:

yaml
1version: 1.0.0+1

Burada 1.0.0 kullanıcıya görünen sürümdür (iOS'ta CFBundleShortVersionString), 1 ise build numarasıdır (CFBundleVersion). CI'da her build'de bu değeri elle güncellemek yerine, build numarasını GitHub Actions'ın kendi run sayacından üretip komut satırından override edebilirsin — dokümantasyon bu override'ı açıkça destekler:

yaml
1- name: Build IPA
2 run: |
3 flutter build ipa \
4 --build-name=1.2.0 \
5 --build-number=${{ github.run_number }}

github.run_number, o workflow'un o depoda kaçıncı kez çalıştığını veren, GitHub'ın otomatik sağladığı bir bağlam değişkenidir; her push'ta artan, tekilliği garanti eden bir build numarası ister. --build-name değerini ise genelde pubspec.yaml'daki gibi elle veya bir git tag'inden okuyarak veriyorsun — anlamlı sürüm numaralandırması (semver) hâlâ insan kararı gerektirir, otomatikleştirilen kısım yalnızca build numarasıdır.

Secret Yönetimi ve Güvenlik

MATCH_PASSWORD, App Store Connect API key, Play Store service account JSON'ı — bunların hiçbiri repoya commit edilmemeli. GitHub'ın resmi rehberi, secret eklemeyi Settings → Secrets and variables → Actions → New repository secret üzerinden veya CLI ile tarif eder:

bash
1gh secret set MATCH_PASSWORD

Workflow YAML'ında secret'a secrets context'i üzerinden erişilir:

yaml
1env:
2 MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}

Kritik bir güvenlik detayı: GitHub'ın dokümantasyonu, fork'tan gelen pull request'lerde GITHUB_TOKEN dışındaki secret'ların runner'a hiç geçirilmediğini açıkça belirtir. Bu, dışarıdan biri PR açıp workflow dosyasını değiştirerek secret'ları console'a yazdırmaya çalışsa bile başarısız olacağı anlamına gelir — ama bu koruma yalnızca fork PR'ları için geçerlidir, aynı repo içindeki branch'lerden açılan PR'lar secret'lara erişebilir. Flutter'ın kendi CI rehberi de test script'lerinde secret değerlerini console'a "re-echo" etmemen gerektiğini vurgular; set -x gibi debug modlarını fastlane lane'lerinde açık bırakmak, log'lara secret sızdırmanın en sık nedenidir.

Flavor'lara Göre Çoklu Build

Fastfile'da Flavor Parametresi

Dev/staging/prod flavor'ların her biri farklı bundle ID, farklı imzalama ve farklı App Store Connect/Play Console kaydı gerektirir. Bunu tek Fastfile'da yönetmenin yolu, flavor adını parametre olarak geçirmektir. flutter build ipa komutuna verdiğin --flavor bayrağı ve -t ile belirttiğin entry-point dosyası (lib/main_dev.dart, lib/main_prod.dart gibi), Flutter'ın flavor'lara özgü build almak için kullandığı standart yapıdır; her flavor kendi Info.plist/bundle ID ayarına Xcode scheme'i üzerinden bağlanır:

ruby
1lane :release do |options|
2 flavor = options[:flavor] || "prod"
3 match(type: "appstore", app_identifier: "com.example.app.#{flavor}", readonly: true)
4 sh("flutter build ipa --flavor #{flavor} --release " \
5 "-t lib/main_#{flavor}.dart")
6 pilot(
7 ipa: Dir[File.expand_path("../../build/ios/ipa/*.ipa")].first,
8 api_key_path: "./fastlane/api_key.json"
9 )
10end

Matrix Build ile Paralel Flavor'lar

GitHub Actions tarafında bunu matrix build ile her flavor için ayrı job çalıştırarak tetikleyebilirsin:

yaml
1strategy:
2 matrix:
3 flavor: [dev, staging, prod]
4steps:
5 - run: bundle exec fastlane release flavor:${{ matrix.flavor }}

Bu yapı, üç flavor'ı tek workflow dosyasında, paralel job'lar olarak çalıştırır — her biri kendi match app_identifier'ıyla kendi sertifikasını çeker, birbirine karışmaz. Flavor sayısı arttıkça matrix'e yeni satır eklemek, Fastfile'ı kopyalamaktan çok daha az bakım yükü getirir.

Yaygın 5 CI Hatası

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

Bu rehberi baştan sona uyguladıysan, kendi projende bir Flutter CI/CD pipeline'ı kurmuş olmalısın. Aşağıdaki checklist, projene entegre etmeden önce gözden geçirmen gereken adımları, makalede geçen sırayla özetliyor — her maddeyi işaretleyerek eksik bıraktığın bir adım olup olmadığını kontrol edebilirsin.

  • match'i readonly olmadan çalıştırmak: CI'da readonly: true vermezsen, her build yeni sertifika üretmeye çalışabilir ve Apple'ın sertifika limitine takılırsın; sertifikaları yalnızca lokalde, elle üretip CI'ya salt-okunur bıraktır.
  • Secret'ları workflow dosyasına düz metin yazmak: env: MATCH_PASSWORD: "gercek-sifre" gibi bir satır, repo geçmişine kalıcı olarak işler; her zaman secrets context'i kullan.
  • Test ve build'i tek job'da birleştirmek: Bir widget testi flaky çıktığında bütün macOS runner dakikan (build dahil) boşa gider; test'i ayrı ve ucuz bir runner'da tut.
  • Build numarasını elle güncellemeyi unutmak: flutter build ipa her seferinde aynı build number ile giderse App Store Connect yüklemeyi reddeder; CI'da bunu otomatikleştirmek (bkz. yukarıdaki bölüm) bu hatayı kalıcı olarak ortadan kaldırır.
  • Cache key'ini yanlış kurmak: key'i statik bir string yaparsan (örn. sadece pub-cache), pubspec.lock değişse bile eski bağımlılıklar servis edilir; key'i her zaman hashFiles('**/pubspec.lock') gibi içerik-duyarlı bir değere bağla.

SSS

Flutter uygulaması GitHub Actions ile nasıl derlenir?

Reponun .github/workflows/ klasörüne bir YAML dosyası eklersin; içinde subosito/flutter-action gibi bir topluluk aksiyonuyla Flutter SDK'sını kurar, ardından flutter pub get ve platforma göre flutter build appbundle (Android) veya flutter build ipa (iOS, macOS runner gerektirir) komutlarını çalıştırırsın. Bu makaledeki GitHub Actions Workflow İskeleti bölümünde tam bir örnek var.

Flutter'da TestFlight'a otomatik yükleme nasıl yapılır?

fastlane'in pilot aksiyonu (upload_to_testflight) bunu yapar. IPA'yı imzalayıp ürettikten (match + flutter build ipa) sonra Fastfile'ında pilot(ipa: "...", api_key_path: "...") çağırman yeterli; App Store Connect API key kullanmak 2FA gerekliliğini ortadan kaldırır.

CI'da iOS imzalama sertifikaları nasıl yönetilir?

fastlane match ile. Sertifikalar ve provisioning profile'lar şifrelenip bir git deposunda, Google Cloud'da veya S3'te saklanır; CI, match(readonly: true) çağrısıyla bu sertifikaları salt-okunur şekilde çeker, yeni sertifika üretmez.

Flutter için ücretsiz CI seçenekleri neler?

GitHub Actions, public repolar için sınırsız, private repolar için aylık belirli bir dakika kotasıyla ücretsizdir; ancak macOS runner dakikaları Linux runner'lara göre kat kat daha yüksek bir çarpanla kotadan düşer, bu yüzden bu makaledeki gibi build/deploy adımlarını yalnızca gerektiğinde macOS'a taşımak bütçeyi korur.

Android tarafında internal testing yüklemesi nasıl otomatikleştirilir?

fastlane'in upload_to_play_store (supply) aksiyonuyla; track: "internal" parametresi build'i doğrudan production yerine Play Console'daki Internal Testing track'ine yükler, json_key parametresiyle Google Cloud servis hesabı kimlik bilgisini geçirirsin.

Güncelleme (Eylül 2026)

Bu rehber Şubat 2025'te yazıldı; o tarihten bu yana Apple'ın App Store Connect yükleme gereksinimlerinde iki değişiklik pipeline'ını doğrudan etkiliyor. Apple'ın resmi "Upcoming requirements" sayfasına göre, 28 Nisan 2026'dan itibaren App Store Connect'e yüklenen uygulamaların Xcode 26 veya üzeri ile, iOS 26/iPadOS 26/tvOS 26/visionOS 26/watchOS 26 SDK'sı kullanılarak derlenmiş olması zorunlu hale geldi. Bu, macos-14 gibi eski runner image'ları kullanan workflow'ların artık uygun Xcode sürümünü barındıran daha güncel bir macOS runner'a (örneğin macos-15 veya sonrası) geçmesi gerektiği anlamına geliyor; GitHub'ın runner-images deposundaki image güncellemelerini takip etmek bu geçişte referans noktan olmalı.

Aynı sayfa, 9 Eylül 2026'dan itibaren App Store Connect'e yüklenen iOS/iPadOS uygulamalarının en az iOS 13'ü hedeflemesi gerektiğini de netleştirdi — yani deployment target'ını bunun altında tutamazsın. Projenin Xcode ayarlarındaki iOS Deployment Target alanını ve Flutter'ın kendi desteklediği minimum sürümü (resmi Supported deployment platforms sayfasından kontrol edebilirsin) bu iki gereksinimle uyumlu tutman gerekiyor. Fastfile veya CI workflow'unda başka bir değişiklik gerekmiyor; asıl etkilenen, build job'unun çalıştığı runner image'ı ve Xcode sürümü.

Sonradan yayımlanan ilgili yazılar:

Sonuç

Flutter CI/CD GitHub Actions ve Fastlane ikilisiyle kurulan bir pipeline, dört basit prensibe dayanıyor: aşamaları ayır (analyze/test ucuz runner'da, build/deploy macOS'ta), sertifikaları match ile paylaş, secret'ları hiçbir zaman repoya yazma, build numarasını otomatikleştir. Bu dört prensibi uyguladığında elle build alıp yükleme sürecinin tamamı ortadan kalkıyor ve her push, kendi kendine test edilip dağıtılan bir sürüm adayına dönüşüyor.

Native iOS tarafında aynı ikilinin nasıl kurulduğunu görmek istersen iOS CI/CD Pipeline: GitHub Actions ve Fastlane rehberine bakabilirsin — orada Swift/Xcode projesi özelinde aynı match+gym+pilot akışı işleniyor; bu makale ise Flutter'ın iki platformu (iOS+Android) tek Fastfile'dan yönetme kısmına odaklanıyor.

React Native'den geçiş düşünüyorsan React Native vs Flutter Karşılaştırması karar vermene yardımcı olabilir.

Kaynaklar

Etiketler

#Flutter#CI/CD#GitHub Actions#Fastlane#DevOps#TestFlight#Google Play
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

12+ yıllık deneyime sahip Lead Mobile Engineer. Swift, SwiftUI, Kotlin ve Flutter ile iOS, Android ve cross-platform mimarilerde uzman. Performanslı ve kullanıcı dostu mobil uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

İlgili İçerik