CI/CD untuk Mobile — Mobile

CI/CD untuk Mobile App CI/CD di mobile lebih menantang dari web — build native butuh macOS (iOS), signing certificates, dan submit ke store. Untungnya EAS…

CI/CD adalah robot yang memeriksa setiap perubahan kode secara otomatis (CI, continuous integration), lalu membuat dan mengirim aplikasinya secara otomatis (CD, continuous delivery). Bayangin meja QC di laundry Bu Rini. Setiap keranjang dihitung dan dicek nodanya sebelum disetrika: itu CI. Cucian yang lolos dibungkus, notanya dicap dan diberi nomor urut, lalu kurir mengantarnya: itu CD. Di aplikasi Laundry Bu Rini, keranjangnya adalah pull request (PR), ceknya typecheck, lint, dan Jest, nomor urutnya versionCode, capnya signing, dan kurirnya EAS Submit.

🧪 Tebakan cepat: Dalam satu PR kamu menambah library native expo-haptics dan mengubah teks di OrderRow. Setelah merge, kamu menjalankan eas update --channel preview. HP tester yang memasang build preview minggu lalu menerima update itu? Jawabannya ada di bagian "EAS Update: channel dan runtimeVersion".

Kenapa CI/CD mobile lebih ribet dari web

Di web, deploy selesai begitu file baru ada di server. Di mobile, tiga hal membuat pipeline-nya sendiri lebih berat:

Di luar pipeline masih ada review toko dan versi lama yang tetap terpasang di HP pelanggan, tapi tiga hal tadi yang membuat CI/CD mobile beda. Alat yang populer: EAS Build (build di server Expo, termasuk iOS tanpa Mac milikmu), fastlane (otomasi signing, build, dan upload), Bitrise (layanan CI khusus mobile), dan GitHub Actions (dengan runner macOS kalau mau build iOS sendiri). Di sini GitHub Actions dipakai untuk cek, dan EAS untuk build, update, dan submit.

Langkah 1: cek di setiap pull request

# .github/workflows/cek.yml
name: Cek
on:
  pull_request:
  push:
    branches: [main]
jobs:
  cek:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npx tsc --noEmit
      - run: npx expo lint
      - run: npm run test:ci

Pertama kali dijalankan di project ini, Jest ikut mengambil test Detox di e2e/:

$ npx jest --ci
FAIL e2e/order.test.js
  ● Pesanan saya › membuka detail order #13
    ReferenceError: device is not defined
Tests:       1 failed, 1 passed, 2 total

device cuma ada kalau Detox yang menjalankan. Tambahkan "testPathIgnorePatterns": ["/node_modules/", "/e2e/"] di bagian jest pada package.json, dan hasilnya Tests: 1 passed, 1 total dalam 1,98 detik.

Profil build di eas.json

{
  "cli": { "version": ">= 24.12.1", "appVersionSource": "remote" },
  "build": {
    "development": { "developmentClient": true, "distribution": "internal",
                     "environment": "development", "env": { "APP_VARIANT": "development" } },
    "preview": { "distribution": "internal", "channel": "preview",
                 "environment": "preview", "android": { "buildType": "apk" } },
    "production": { "channel": "production", "environment": "production", "autoIncrement": true }
  },
  "submit": { "production": { "android": { "track": "internal" } } }
}

EAS Update: channel dan runtimeVersion

Setiap build membawa tiga label: platform, channel, dan runtimeVersion (identitas lapisan native-nya). Update cuma dipasang di build yang platform dan runtimeVersion-nya sama persis, lewat channel build itu: eas update --channel preview --environment preview (--environment wajib untuk project SDK 55 ke atas). runtimeVersion diisi policy di app.config.ts, setelah npx expo install expo-updates:

runtimeVersion: { policy: 'fingerprint' },

Diukur di project ini dengan npx expo-updates fingerprint:generate --platform android (jalan tanpa login; eas fingerprint:generate minta login):

Perubahan Fingerprint Android Yang dibutuhkan
build preview minggu lalu af774548…
status di OrderRow jadi huruf besar af774548… (sama) EAS Update
npx expo install expo-haptics 563c34c6… build baru
teks cameraPermission di app.json dfd1c3a1… build baru
track submit di eas.json 4be0bcb4… build baru

Update dari tebakan tadi dibuat untuk runtime 563c34c6…, sedangkan HP tester menjalankan af774548…, jadi HP itu gak memasangnya. Itu jawaban tebakannya. Justru ini melindungi: dengan runtimeVersion tetap "1.0.0", HP tester akan memuat JavaScript yang memanggil kode native yang gak ada, dan expo-updates harus mundur ke update sebelumnya. Fingerprint iOS dihitung terpisah (85371baa…), dan eas.json serta script di package.json ikut dihitung.

Langkah 2: build atau update, dipilih otomatis

EAS Workflows adalah CI milik Expo, jalan di server Expo setelah repo GitHub dihubungkan ke project EAS. Job fingerprint dan get-build-nya memilih jalannya:

# .eas/workflows/kirim-preview.yml
on:
  push:
    branches: ['main']
jobs:
  fingerprint:
    type: fingerprint
    environment: preview
  cari_build_android:
    needs: [fingerprint]
    type: get-build
    params:
      fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
      profile: preview
  build_android:
    needs: [cari_build_android]
    if: ${{ !needs.cari_build_android.outputs.build_id }}
    type: build
    params: { platform: android, profile: preview }
  update_android:
    needs: [cari_build_android]
    if: ${{ needs.cari_build_android.outputs.build_id }}
    type: update
    environment: preview
    params: { channel: preview, platform: android }
  # iOS: empat job yang sama dengan ios_fingerprint_hash dan platform: ios

Langkah 3: rilis ke TestFlight dan Play internal

# .github/workflows/rilis.yml
name: Rilis
on:
  push:
    tags: ['v*']
jobs:
  rilis:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with: { node-version: 24, cache: npm }
      - uses: expo/expo-github-action@v9
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: npm ci
      - run: eas build --platform all --profile production --auto-submit --non-interactive --no-wait

Tanpa Expo (React Native bare atau aplikasi native), jalur yang umum adalah fastlane (2.241.0): lane berisi match untuk sertifikat, gradle atau build_app untuk build, lalu upload_to_play_store dan upload_to_testflight, dengan runner macOS untuk iOS.

Rahasia: token, keystore, dan variabel

Maestro di CI: berapa biayanya

Flow buka-order-13.yaml dari pelajaran E2E butuh aplikasi yang sudah di-build dan emulator yang menyala. Harga per 11 Oktober 2026:

Pilihan Caranya Biaya
EAS Workflows, job maestro (masih alpha) build profil E2E (APK, simulator iOS), lalu Maestro di server EAS build dihitung kuota, lalu menit workflow: Linux 0,018 dolar, macOS 0,075 dolar; paket Free sampai 60 menit
GitHub Actions dengan emulator Android reactivecircus/android-emulator-runner@v2 di ubuntu-latest repo publik gratis, private 0,006 dolar per menit (simulator iOS di runner macOS: 0,062)
Maestro Cloud perangkat di server Maestro, paralel 250 dolar per perangkat per bulan

Misalnya 40 PR sebulan dan 15 menit per run Android (angka contoh, ukur run-mu sendiri): 600 menit × 0,006 = 3,6 dolar di GitHub, 600 × 0,018 = 10,8 dolar di EAS Workflows, dan 250 dolar tetap di Maestro Cloud. Untuk Bu Rini: flow Android di setiap PR yang menyentuh layar order, iOS sekali sehari.

⚠️ Jebakan yang sering ketemu

TL;DR: CI/CD mobile lebih ribet dari web karena build native lama, build iOS butuh macOS, dan signing serta provisioning harus dijaga; alat yang populer: EAS Build, fastlane, Bitrise, dan GitHub Actions. Untuk Laundry Bu Rini: GitHub Actions (actions/checkout@v7, actions/setup-node@v7, Node 24) menjalankan tsc --noEmit, expo lint, dan jest --ci di setiap PR tanpa rahasia, dengan e2e/ dikeluarkan dari Jest. eas.json punya profil development, preview (internal, APK, channel preview), dan production (autoIncrement, appVersionSource: "remote"). Update cuma dipasang di build dengan platform dan runtimeVersion yang sama; policy fingerprint mengubah runtime begitu bagian native berubah (library native, app.json, bahkan eas.json), dan EAS Workflows (fingerprint, get-build) memilih build baru atau update. Tag v* menjalankan eas build --profile production --auto-submit lewat expo/expo-github-action@v9 ke TestFlight dan track internal. EXPO_TOKEN di GitHub Secrets, keystore di EAS credentials, variabel dengan eas env:set. Maestro di CI: emulator Android di GitHub paling murah, Maestro Cloud 250 dolar per perangkat per bulan; tanpa Expo, fastlane.

Yang akan kamu pelajari