Kenapa Saya Upload App ke Play Store & App Store dari Laptop Sendiri, Bukan dari GitHub Actions


Sebelum mulai, satu disclaimer penting: artikel ini buat teman-teman yang punya environment Mac di lokal. Kalau nggak punya Mac, skip aja artikel ini — build iOS emang butuh Mac, jadi ceritanya nggak akan relevan. 😄
Nah, buat yang masih di sini: saya mau cerita kenapa saya akhirnya berhenti pakai automation kayak GitHub Actions dan Codemagic buat rilis aplikasi mobile, dan balik ke cara yang kelihatannya "jadul" — build dan upload langsung dari laptop sendiri. Spoiler: ini bukan karena saya anti-otomasi. Justru sebaliknya, deployment saya sekarang lebih otomatis dari sebelumnya — bedanya, otomasinya jalan di mesin yang saya kontrol 100%.
Janji manis CI/CD buat rilis mobile
Kita semua tahu pitch-nya: push tag, terus GitHub Actions / Codemagic / Bitrise yang build dan upload ke store. Nggak perlu nyentuh apa-apa, "it just works". Buat tim besar dengan banyak developer, ini masuk akal banget — build-nya reproducible, nggak tergantung laptop siapa-siapa.
Saya juga dulu percaya itu. Sampai kena masalah yang sama berulang-ulang, dan akhirnya sadar: buat solo developer atau tim kecil yang punya Mac, harga yang dibayar buat "kenyamanan" itu ternyata mahal.
Masalah saya: upgrade versi = CI meledak
Ini pola yang keulang terus di saya:
- Saya upgrade Flutter, atau naikin versi library, atau Xcode update di lokal.
- Di lokal:
flutter buildjalan mulus, app jalan normal, semua hijau. ✅ - Push ke CI... merah. ❌
Kenapa? Karena environment CI itu bukan environment saya. Versi Flutter di runner beda, versi Xcode di image-nya beda, versi CocoaPods beda, Java buat Gradle beda. Tiap kali saya upgrade sesuatu di lokal, saya harus "ngejar" konfigurasinya di YAML: update versi di workflow, ganti image, tungguin queue, build 20 menit, gagal lagi karena ternyata ada satu tool lagi yang versinya nggak cocok, ulang lagi.
Dan ini bukan kejadian sekali-dua kali. Framework dan library itu selalu rilis versi baru buat benerin sesuatu — itu memang kerjaannya mereka. Artinya siklus "lokal jalan, CI rusak, benerin YAML" itu bukan insiden, tapi biaya rutin yang harus saya bayar tiap beberapa minggu. Debug pipeline yang feedback loop-nya 15–20 menit per percobaan itu salah satu aktivitas paling nyebelin di dunia programming. 😩
Terus saya mikir: yang saya mau kan sederhana — binary yang saya tes di lokal, itu juga yang sampai ke user. Kalau build-nya dari mesin yang sama dengan tempat saya develop, masalah "beda environment" itu hilang total. Bukan dikurangi, tapi hilang. Config di mesin saya 100% persis kayak yang saya pakai sehari-hari. Kalau kodenya jalan bagus di lokal, ya build rilisnya juga bakal jalan bagus — karena itu literally mesin dan toolchain yang sama.
Solusinya: otomasi, tapi di lokal
Yang saya buang itu CI-nya, bukan otomasinya. Di project mobile saya, ada folder scripts/ berisi beberapa shell script:
scripts/
├── deploy_all.sh # rilis ke SEMUA store sekali jalan
├── deploy_playstore.sh # Google Play
├── deploy_appstore.sh # iOS App Store (via TestFlight)
└── deploy_macos.sh # Mac App Store
Rilis full ke tiga store itu satu perintah:
./scripts/deploy_all.sh
Script-nya nanya konfirmasi dulu, nunjukin versi yang mau dirilis (dibaca otomatis dari pubspec.yaml), terus jalanin build + upload buat tiap platform berurutan. Ada flag -y buat skip konfirmasi dan -s buat skip platform tertentu (misal -s macos kalau cuma mau rilis mobile).
Yang dilakukan tiap script
Contoh alur yang versi iOS (disederhanakan):
# 1. Guard rails dulu — gagal cepat kalau ada yang nggak beres
[[ -f "$ENV_FILE" ]] || { echo "ERROR: env file not found"; exit 1; }
case "$(xcode-select -p)" in
*Xcode.app*) ;;
*) echo "ERROR: xcode-select points to CommandLineTools"; exit 1 ;;
esac
if [[ -n "$(git status --porcelain)" ]]; then
echo "WARNING: uncommitted changes in working tree."
fi
# 2. Build bersih
flutter clean
flutter pub get
(cd ios && pod install)
flutter build ipa --release
# 3. Upload ke App Store Connect
xcrun altool --upload-app -f "$IPA" -t ios \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"
Versi Android-nya mirip: flutter build appbundle --release, terus upload pakai fastlane ke Google Play. Yang saya suka, script Play Store-nya punya opsi track dan staged rollout:
# rilis ke internal track dulu
./scripts/deploy_playstore.sh -t internal
# atau langsung production tapi rollout 10% dulu
./scripts/deploy_playstore.sh -r 0.1
Perhatiin bagian guard rails-nya — itu pelajaran dari pengalaman:
- Cek env file & API key ada sebelum mulai, biar nggak build 10 menit terus gagal di langkah upload.
- Cek
xcode-selectnunjuk ke Xcode beneran, bukan CommandLineTools (ini pernah bikin saya bingung setengah jam 🙃). - Warning kalau working tree kotor — biar sadar kalau mau rilis kode yang belum di-commit.
- Semua kredensial (API key App Store Connect, service account Google Play) hidup di file env lokal yang di-gitignore, nggak pernah nyentuh repo, nggak pernah jadi "secrets" yang harus di-manage di dashboard pihak ketiga.
Poin terakhir itu bonus yang jarang diomongin: dengan deploy lokal, kredensial store saya nggak pernah keluar dari mesin saya. Nggak ada service account nginep di server CI orang lain.
Bonus favorit saya: AI Agent lokal yang ngurus semuanya
Nah ini bagian yang bikin saya makin betah. Karena semua deployment cuma shell script di lokal, AI agent di laptop saya bisa jalanin seluruh prosesnya.
Sekarang alur rilis saya kira-kira gini: saya bilang ke agent "rilis versi baru". Agent yang:
- Ngecek perubahan sejak rilis terakhir dari git log.
- Nulis release notes / copywriting "what's new" — dan hasilnya bagus, natural, nggak template banget. Saya nggak perlu mikirin copywriting sama sekali. ✍️
- Naikin versi di
pubspec.yaml. - Jalanin
./scripts/deploy_all.shdan mantau output-nya. - Kalau ada error, dia bisa langsung baca log-nya di mesin yang sama dan benerin.
Coba bandingin sama debugging CI: agent (atau saya) harus buka dashboard web, baca log dari runner yang environment-nya nggak bisa disentuh, nebak-nebak, push commit "fix ci" ke-17, tunggu queue... Di lokal, feedback loop-nya detik, bukan puluhan menit. Agent bisa iterate langsung sampai beres.
Otomasinya sama — bahkan lebih pinter — tapi jalan di environment yang saya pegang penuh.
Biar adil: CI tetap ada tempatnya
Saya nggak bilang CI/CD buat mobile itu jelek buat semua orang. Kalau kondisi kalian kayak gini, CI masih pilihan yang bener:
- Tim besar — banyak orang yang harus bisa rilis, nggak boleh tergantung satu laptop.
- Nggak punya Mac — Codemagic dkk. nyediain Mac runner, dan itu satu-satunya jalan buat build iOS. (Makanya disclaimer di awal tadi. 😄)
- Butuh audit trail formal — beberapa perusahaan wajib build dari environment yang tersertifikasi.
Tapi kalau kalian solo developer atau tim kecil, punya Mac, dan udah capek benerin YAML tiap kali ada versi baru framework — pertimbangin deh balik ke lokal. Bukan berarti mundur ke jaman manual, tapi pindahin otomasinya ke mesin sendiri.
Kesimpulan
- Masalah utama CI buat saya: environment-nya bukan environment saya. Tiap upgrade framework/library di lokal = sesi maintenance YAML yang messy dan lama.
- Deploy dari lokal ngasih kontrol penuh dan paritas config 100%: kode yang jalan bagus di lokal, ya itu juga yang keupload.
- "Manual" bukan berarti nggak otomatis — semua dibungkus shell script, satu perintah buat rilis ke tiga store sekaligus, lengkap dengan guard rails.
- Bonus terbesar: AI agent lokal bisa megang seluruh proses rilis, termasuk nulis release notes, karena semuanya cuma script di mesin sendiri.
- Kredensial store nggak pernah keluar dari laptop.
Kalau teman-teman punya cerita perang sama CI mobile — atau justru punya setup CI yang awet nggak pernah rusak — cerita dong, saya penasaran. 🚀