iOS release manuel yapılırsa: build, archive, dSYM upload, TestFlight, metadata update, screenshot, release notes, submit. Hepsi ayrı click. Bir release 3-4 saat. Yanlış yapıldığında ise 1-2 gün kaybedebiliyorsun.
Fastlane ile tüm bu steps tek komutla çalışıyor. 12 app’imde kullandığım pipeline’ı paylaşacağım.
Fastlane nedir, kısaca
Fastlane Ruby-based iOS/Android automation tool. Fastfile’da “lane” tanımlıyorsun, her lane bir iş akışı.
Install:
brew install fastlane
# veya
gem install fastlaneFastfile (project root’ta fastlane/Fastfile):
platform :ios do
lane :beta do
# TestFlight release
end
lane :release do
# App Store release
end
endKomut: fastlane beta veya fastlane release.
Temel beta pipeline’ım
Fastfile’ımdaki beta lane:
lane :beta do
# 1. Git check
ensure_git_status_clean
# 2. Certificates
match(type: "appstore", readonly: true)
# 3. Build
build_app(
scheme: "MyApp",
configuration: "Release",
export_method: "app-store",
output_directory: "./builds",
clean: true
)
# 4. TestFlight upload
upload_to_testflight(
skip_waiting_for_build_processing: true,
changelog: File.read("./CHANGELOG.md")
)
# 5. dSYM upload
upload_symbols_to_crashlytics(
dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH],
gsp_path: "./GoogleService-Info.plist"
)
# 6. Slack notification
slack(
message: "Beta build uploaded to TestFlight",
success: true
)
# 7. Git tag
add_git_tag(tag: "beta-#{get_build_number}")
push_git_tags
endBu 7 step manuel yapsan 2-3 saat. Fastlane ile fastlane beta tek komut, 10-15 dakika.
Production release pipeline
lane :release do
# Pre-release checks
ensure_git_branch(branch: "main")
ensure_git_status_clean
# Version bump (minor)
increment_version_number(bump_type: "minor")
increment_build_number
# Build
match(type: "appstore", readonly: true)
build_app(scheme: "MyApp", configuration: "Release")
# Metadata update (App Store Connect)
deliver(
submit_for_review: false,
automatic_release: false,
force: true,
metadata_path: "./fastlane/metadata",
screenshots_path: "./fastlane/screenshots"
)
# Crashlytics dSYM
upload_symbols_to_crashlytics(
dsym_path: lane_context[SharedValues::DSYM_OUTPUT_PATH]
)
# Commit version changes
version = get_version_number(target: "MyApp")
build = get_build_number
commit_version_bump(message: "Release #{version} (#{build})")
add_git_tag(tag: "v#{version}")
push_to_git_remote
# Slack notification
slack(
message: "New version #{version} submitted to App Store",
channel: "#releases"
)
endBu pipeline ile her production release 20-30 dakika. Yanlış yapma riski neredeyse yok.
Match: code signing otomasyon
Fastlane’in en güçlü tool’u match. Certificate + provisioning profile management automated.
Setup:
fastlane match initGit repo seçiyorsun (private), match certificates’ı oraya şifreli save ediyor. Team’inin tüm developer’ları aynı cert’e erişiyor.
Usage (Fastfile):
match(type: "appstore", readonly: true)readonly: true yeni cert üretmiyor, mevcut kullanıyor. CI’da her zaman readonly.
Match olmadan certificate management her developer için manuel, onboarding 1-2 saat. Match ile 5 dakika.
Metadata management
App Store Connect’e her release metadata (description, keywords, what’s new, screenshots) girmek zaman alıyor.
Fastlane deliver bu işi otomatize ediyor:
fastlane/metadata/
en-US/
description.txt
keywords.txt
release_notes.txt
tr-TR/
description.txt
...
[36 dilde]Her dil için text file. fastlane deliver → App Store Connect’e upload.
Yeni versiyon release notes’u güncelleyip fastlane beta çalıştırıyorsun. Metadata tüm dillerde update.
Screenshots automation (snapshot)
App Store’da 36 dil * 6 screenshot * 3 device = 648 screenshot. Manuel imkansız.
Fastlane snapshot:
- UI test target oluştur
- Snapfile config: desteklenen device’lar, diller
- UI test’ler ekranları capture ediyor
fastlane snapshot→ 648 screenshot generated
# Snapfile
devices([
"iPhone 15 Pro",
"iPhone SE (3rd generation)",
"iPad Pro 12.9-inch"
])
languages([
"en-US", "tr-TR", "de-DE", ... # 36 dil
])1-2 saatlik iş. Haftada çalıştırıyorum, release’den önce güncel screenshot’lar hazır.
CI/CD integration
Fastlane local’de çalışıyor ama CI/CD’de automate et:
GitHub Actions example:
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*']
jobs:
release:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.1'
bundler-cache: true
- name: Run Fastlane
env:
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
run: bundle exec fastlane releaseGit tag push edince (git tag v2.3.0 && git push origin v2.3.0) otomatik release pipeline çalışıyor.
Secrets management
Fastlane’in kullandığı secret’lar:
- Apple ID password / App Store Connect API key
- Match password (cert decryption)
- GitHub API token (git push için)
- Firebase API key (Crashlytics için)
- Slack webhook URL
Bu secrets environment variable olarak geçiyor:
env FASTLANE_PASSWORD=xxx MATCH_PASSWORD=yyy fastlane betaCI’da GitHub Secrets, local’de .env file (gitignore’de).
Don’t:
– Fastfile içinde secret hard-code etme
– Secret’ları shell history’e bırakma
– Git’te secrets.yml tutma
Do:
– .env.default dummy values ile committed, .env actual values gitignored
– CI’da secrets management (GitHub Secrets, AWS Secrets Manager)
– Rotation disciplin: quarterly secret rotation
Error handling
Fastlane pipeline fail olunca ne olacak?
error do |lane, exception|
slack(
message: "Fastlane fail: #{exception.message}",
success: false,
channel: "#alerts"
)
enderror do block’u fail’de çalışıyor. Slack alert gönderiyor, team immediately biliyor.
Ayrıca rollback logic:
lane :release do
# ... pipeline steps ...
rescue => e
# Version'ı revert et
git_revert
UI.user_error!("Release failed: #{e}")
endYarım deploy’ları prevent ediyor.
Plugin ecosystem
Fastlane 400+ plugin. Most popular:
- fastlane-plugin-firebase_app_distribution: Firebase test distribution
- fastlane-plugin-sentry: Sentry dSYM upload
- fastlane-plugin-versioning: Advanced version bumping
- fastlane-plugin-badge: Icon’a badge ekle (beta, staging)
- fastlane-plugin-changelog: Changelog generation from git
Install:
fastlane add_plugin sentryCommon pitfalls
1. Fastfile çok uzun. 500 satır Fastfile unreadable. Lanes’i modular yap, shared logic’i method’lara çıkart.
2. Version bump’ı hatalı. Build number monotonic artmalı. Git tag’lerle sync discipline gerekli.
3. Metadata’da typo. App Store Connect reject ediyor. Pre-upload validation:
fastlane run deliver --metadata_only --submit_for_review false4. Expire olmuş certificates. Match readonly kullanıyor ama cert expire olmuşsa fail. Quarterly check et.
5. TestFlight beta expiry. 90 gün sonra build’ler expire. Lanes’e expiry warning logic ekle.
Bundle versioning
Gem’leri Gemfile’da lock:
# Gemfile
source "https://rubygems.org"
gem "fastlane", "~> 2.215.0"Lockfile commit’le. Team member’lar ve CI aynı Fastlane version kullanıyor, breaking change’ler surprise olmuyor.
Continuous improvement
Pipeline zaman içinde evolve ediyor. Benim 12 app’teki refinement’lar:
- Initial: basic build + TestFlight
- +1 month: dSYM upload automation
- +2 months: Slack notifications
- +3 months: Automated screenshots (snapshot)
- +6 months: Multi-environment pipelines (staging, production)
- +12 months: Complex metadata management
Every release’den sonra pipeline’a eklenecek bir şey buluyorum. Continuous improvement mindset.
Sonuç
Fastlane iOS release automation’ın altın standardı. Initial setup 1-2 günlük iş ama ondan sonra her release 3-4 saatten 20-30 dakikaya düşüyor.
Match code signing, deliver metadata, snapshot screenshots, CI/CD integration. Hepsi production-ready.
Solo developer için overkill değil. 12 app’lik portfolyomu tek başıma yönetmek Fastlane olmadan imkansız. Senin portföyün 2-3 app olsa bile yatırım karşılanıyor.