Every mobile developer knows: manual building and publishing eats up to 5 hours a week. On large projects, a code signing error can delay a release by a day. GitHub Actions automates this pipeline: after a push, code is tested, built, signed, and published without human intervention. iOS requires a macOS runner (e.g., macos-14 on Apple Silicon), while Android runs on standard Linux. Free macOS minutes are billed at 10x—on active projects, the limit runs out quickly. A self-hosted Mac mini in the office solves the cost problem but adds administration overhead. On one project, we cut release time from 3 hours to 30 minutes after implementing the pipeline described below.
Why CI/CD Is Critical for Mobile Apps
Without CI/CD, every release becomes a risk: signing errors, missed tests, wrong configs. Automation ensures every push meets quality standards. After CI/CD adoption, the team can release updates daily, and testing becomes an integral part of the process.
iOS: Why Code Signing Is a Headache
GitHub provides free macOS runners (macos-14, Apple Silicon). macOS minutes are billed at 10x the Linux rate—during active development, the free limit is quickly exhausted. A self-hosted macOS runner on a Mac mini in the office solves the cost issue but adds administration.
Code signing on GitHub Actions is done via fastlane match or importing a certificate from secrets:
- name: Import certificate run: | echo "${{ secrets.DISTRIBUTION_CERTIFICATE_P12 }}" | base64 --decode > cert.p12 security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain security import cert.p12 -k build.keychain -P "${{ secrets.CERT_PASSWORD }}" -T /usr/bin/codesign security set-keychain-settings -lut 21600 build.keychain security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain security list-keychains -d user -s build.keychain login.keychain This manual approach works but is fragile when certificates are updated. In production, use fastlane match readonly: true with MATCH_PASSWORD in secrets. Common mistakes: expired certificate (update provisioning profile yearly), keychain password not passed in secrets, fastlane version mismatch (pin in Gemfile.lock).
How to Set Up Code Signing on GitHub Actions?
Use fastlane match with a separate repository for certificates. Add MATCH_PASSWORD, MATCH_GIT_BASIC_AUTHORIZATION to secrets and set readonly: true. The workflow imports profiles automatically. This tried-and-tested method ensures a 100% success rate for signing.
Full iOS Workflow
name: iOS CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: macos-14 steps: - uses: actions/checkout@v4 - name: Select Xcode run: sudo xcode-select -s /Applications/Xcode_16.0.app - name: Cache CocoaPods uses: actions/cache@v4 with: path: Pods key: ${{ runner.os }}-pods-${{ hashFiles('Podfile.lock') }} - name: Install pods run: bundle exec pod install - name: Run tests run: | bundle exec fastlane scan \ --scheme "MyApp" \ --device "iPhone 16" \ --code-coverage true \ --output-files "test-results.xml" - name: Upload test results uses: actions/upload-artifact@v4 with: name: test-results path: test-results.xml if-no-files-found: error deploy-beta: needs: test runs-on: macos-14 if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: bundler-cache: true - name: Deploy to TestFlight env: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} APP_STORE_CONNECT_API_KEY: ${{ secrets.ASC_API_KEY }} run: bundle exec fastlane release needs: test — deploy-beta runs only if tests pass. if: github.ref == 'refs/heads/main' — deploy only from main.
Android: How to Simplify the Pipeline
jobs: android-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Cache Gradle uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Build and test run: ./gradlew test assembleRelease - name: Sign APK uses: r0adkll/sign-android-release@v1 with: releaseDirectory: app/build/outputs/apk/release signingKeyBase64: ${{ secrets.SIGNING_KEY }} alias: ${{ secrets.KEY_ALIAS }} keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.KEY_PASSWORD }} - name: Upload to Firebase App Distribution uses: wzieba/Firebase-Distribution-Github-Action@v1 with: appId: ${{ secrets.FIREBASE_APP_ID }} token: ${{ secrets.FIREBASE_TOKEN }} groups: qa-team file: app/build/outputs/apk/release/app-release-signed.apk Linux runner for Android — free unlimited minutes (on public repos). Gradle cache saves 3–5 minutes per run. Overall, Android builds are 2x faster than iOS due to cheaper runners and simpler build process.
How to Speed Up the Build?
Caching dependencies is the primary method. For iOS, use actions/cache for Pods or SPM. For Android, for Gradle cache. Also limit steps to only needed platforms and run tests in parallel.
How to Test on Multiple Devices?
strategy: matrix: device: ["iPhone 15", "iPhone SE (3rd generation)", "iPad Pro (12.9-inch)"] jobs: test: runs-on: macos-14 steps: - name: Run tests on ${{ matrix.device }} run: xcodebuild test -scheme MyApp -destination "platform=iOS Simulator,name=${{ matrix.device }}" Runs tests on three devices in parallel — total time doesn't increase, coverage expands.
What Does Matrix Testing Give?
The device matrix catches bugs specific to certain models and iOS versions. Without it, you risk missing errors that only appear on older devices. Parallel matrix jobs don't slow down the pipeline — they all run simultaneously. This approach is 4x better than sequential testing for catching regressions.
Table: CI/CD Comparison for iOS and Android
| Parameter | iOS | Android |
|---|---|---|
| Runner | macOS (macos-14) | Linux (ubuntu-latest) |
| Signing | code signing (fastlane match) | Signing APK (keystore) |
| Deploy | TestFlight / App Store | Google Play / Firebase |
| Caching | CocoaPods / SPM | Gradle cache |
| Tests | XCTest | JUnit / Espresso |
Additional Table: Approximate Execution Times
| Stage | iOS | Android |
|---|---|---|
| Build | 15–25 min | 10–20 min |
| Tests | 10–15 min | 5–10 min |
| Signing | 2–5 min | 1–2 min |
| Deploy | 5–10 min | 3–7 min |
What's Included in CI/CD Setup
Our team will prepare:
- Working workflows for iOS and Android
- Code signing setup with fastlane match
- Dependency caching for faster builds
- Matrix testing on multiple devices
- Integration with TestFlight, Google Play, or Firebase
- Detailed maintenance documentation
- Team training (1–2 hours)
Our experience: 8+ years in mobile development, over 20 CI/CD implementations, and 5 years on the market. We know how to avoid common mistakes and maximize pipeline speed. For deeper details, check the GitHub Actions and fastlane match documentation.
Timeline and Pricing
Basic workflows (test + build) for one platform — 3–5 days, starting at $500. Full configuration with code signing, device matrix, caching, deployment — 1–2 weeks, typically $2,000–$4,000. Pricing is calculated individually — we tailor the solution to your project and budget.
Ready to automate releases? Contact us for a consultation. Order a pipeline implementation and free up your developers' time. Guaranteed ROI within 3 months.







