Tema
Tutorial GitHub Actions untuk Flutter Web CI/CD
Dokumen ini menjelaskan cara kerja GitHub Actions, terutama file workflow:
text
.github/workflows/main.ymlWorkflow ini dipakai untuk build Flutter Web dan deploy otomatis ke server melalui SSH/SCP.
1. Konsep Dasar GitHub Actions
GitHub Actions adalah fitur CI/CD dari GitHub. CI/CD berarti proses otomatis untuk:
- mengambil source code,
- menjalankan install dependency,
- menjalankan build atau test,
- menyimpan hasil build,
- melakukan deploy ke server.
Komponen utama GitHub Actions:
| Komponen | Fungsi |
|---|---|
workflow | File YAML yang berisi proses otomatis. Disimpan di .github/workflows/. |
event | Pemicu workflow, misalnya push, pull_request, atau manual trigger. |
job | Kumpulan step yang berjalan di runner. |
step | Satu langkah kerja, bisa menjalankan command atau action siap pakai. |
runner | Mesin tempat job berjalan, misalnya ubuntu-latest. |
action | Plugin reusable, misalnya actions/checkout, upload-artifact, atau scp-action. |
secret | Nilai rahasia seperti SSH key, host server, username, dan target folder. |
2. Struktur Workflow
Workflow ini bernama:
yaml
name: Flutter Web CI/CDWorkflow akan berjalan ketika ada push ke branch:
yaml
on:
push:
branches:
- main
- productionArtinya:
- Push ke
mainakan build dan deploy ke staging. - Push ke
productionakan build dan deploy ke production. - Push ke branch lain tidak menjalankan workflow ini.
3. Concurrency
Bagian ini mencegah deploy bertumpuk untuk branch yang sama:
yaml
concurrency:
group: deploy-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: trueJika ada push baru saat workflow sebelumnya masih berjalan, workflow lama akan dibatalkan dan workflow terbaru yang dipakai.
4. Job build
Job pertama adalah build:
yaml
jobs:
build:
runs-on: ubuntu-latestJob ini berjalan di runner Linux milik GitHub.
Checkout Repository
yaml
- name: Checkout
uses: actions/checkout@v4Step ini mengambil source code repository ke runner.
Ambil Nama Branch
yaml
- name: Get branch name
id: get_branch
run: echo "branch=${GITHUB_REF##*/}" >> $GITHUB_OUTPUTStep ini mengambil nama branch dari GITHUB_REF, lalu menyimpannya sebagai output bernama branch.
Output ini dipakai oleh job deploy untuk menentukan apakah harus deploy staging atau production.
Setup Flutter
yaml
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.1'Step ini menginstall Flutter versi 3.38.1 pada runner.
Pastikan versi Flutter di workflow cocok dengan pubspec.yaml. tutorial ini memakai:
yaml
environment:
sdk: ^3.10.0Install Dependency
yaml
- name: Install dependencies
run: flutter pub getStep ini mengambil package Dart/Flutter dari pubspec.yaml.
Build Staging
yaml
- name: Build Staging
if: github.ref == 'refs/heads/main'
run: flutter build web --release --base-href /app-staging/Step ini hanya berjalan untuk branch main.
--base-href /app-staging/ berarti aplikasi staging akan diakses dari subfolder:
text
https://domain.com/app-staging/Jika path staging di server berbeda, nilai --base-href harus ikut disesuaikan.
Build Production
yaml
- name: Build Production
if: github.ref == 'refs/heads/production'
run: flutter build web --release --base-href /Step ini hanya berjalan untuk branch production.
--base-href / berarti aplikasi production akan diakses dari root domain:
text
https://domain.com/Upload Artifact
yaml
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: web-build-${{ steps.get_branch.outputs.branch }}
path: build/web/Artifact adalah hasil build yang disimpan sementara oleh GitHub Actions.
Nama artifact akan menjadi:
web-build-mainuntuk branchmainweb-build-productionuntuk branchproduction
Folder yang disimpan adalah:
text
build/web/5. Job deploy-staging
Job ini hanya berjalan jika branch yang dibuild adalah main:
yaml
deploy-staging:
if: needs.build.outputs.branch == 'main'
needs: buildneeds: build berarti deploy staging baru berjalan setelah job build selesai dengan sukses.
Download Build
yaml
- name: Download build
uses: actions/download-artifact@v4
with:
name: web-build-mainStep ini mengambil artifact hasil build staging.
Clean Target Folder
yaml
- name: Clean target folder
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
script: |
TARGET="${{ secrets.TARGET_STAGING }}"
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; then
echo "TARGET_STAGING is empty or unsafe"
exit 1
fi
rm -rf "$TARGET"/*Step ini login ke server lewat SSH dan menghapus isi folder staging.
Guard ini penting:
sh
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; thenTujuannya agar workflow berhenti jika TARGET_STAGING kosong atau bernilai /.
Deploy to Staging
yaml
- name: Deploy to Staging
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
source: "*"
target: ${{ secrets.TARGET_STAGING }}Step ini mengupload semua file hasil build ke folder staging di server.
6. Job deploy-production
Job ini hanya berjalan jika branch yang dibuild adalah production:
yaml
deploy-production:
if: needs.build.outputs.branch == 'production'
needs: buildDownload Build
yaml
- name: Download build
uses: actions/download-artifact@v4
with:
name: web-build-productionStep ini mengambil artifact production.
Clean Target Folder
yaml
- name: Clean target folder
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
script: |
TARGET="${{ secrets.TARGET_PRODUCTION }}"
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; then
echo "TARGET_PRODUCTION is empty or unsafe"
exit 1
fi
cd "$TARGET"
rm -rf assets
rm -f index.html main.dart.js flutter.js flutter_bootstrap.js flutter_service_worker.js manifest.json version.jsonStep ini membersihkan file Flutter Web lama di folder production.
Production tidak menghapus semua isi folder. Yang dihapus hanya file/folder Flutter Web yang umum:
assetsindex.htmlmain.dart.jsflutter.jsflutter_bootstrap.jsflutter_service_worker.jsmanifest.jsonversion.json
Ini berguna jika folder production berisi file lain yang tidak boleh ikut terhapus.
Deploy to Production
yaml
- name: Deploy to Production
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
source: "*"
target: ${{ secrets.TARGET_PRODUCTION }}Step ini mengupload hasil build production ke server.
7. Secrets yang Harus Dibuat
Buka repository GitHub, lalu masuk ke:
text
Settings -> Secrets and variables -> Actions -> New repository secretBuat secrets berikut:
| Secret | Isi |
|---|---|
HOST | IP atau domain server, contoh 123.123.123.123. |
USERNAME | Username SSH di server, contoh ubuntu, root, atau user deploy khusus. |
SSH_KEY | Private key SSH untuk login ke server. |
PORT | Port SSH, biasanya 22. |
TARGET_STAGING | Path folder staging di server, contoh /var/www/html/app-staging. |
TARGET_PRODUCTION | Path folder production di server, contoh /var/www/html. |
Catatan:
- Jangan isi
TARGET_STAGINGatauTARGET_PRODUCTIONdengan/. - Jangan memakai private key akun pribadi jika bisa memakai deploy key/user khusus.
- Pastikan public key dari
SSH_KEYsudah ada di~/.ssh/authorized_keyspada server. - Pastikan user SSH punya izin tulis ke folder target.
8. Cara Menjalankan Deploy
Deploy ke Staging
Push ke branch main:
sh
git checkout main
git add .
git commit -m "Update aplikasi"
git push origin mainSetelah push, GitHub Actions akan:
- Build Flutter Web dengan base href
/app-staging/. - Upload artifact
web-build-main. - Bersihkan folder staging di server.
- Upload build baru ke folder staging.
Deploy ke Production
Push ke branch production:
sh
git checkout production
git merge main
git push origin productionSetelah push, GitHub Actions akan:
- Build Flutter Web dengan base href
/. - Upload artifact
web-build-production. - Bersihkan file Flutter Web lama di folder production.
- Upload build baru ke folder production.
9. Cara Melihat Hasil Workflow
Di GitHub:
- Buka repository.
- Klik tab
Actions. - Pilih workflow
Flutter Web CI/CD. - Klik run terbaru.
- Buka job
build,deploy-staging, ataudeploy-production. - Baca log step yang gagal jika ada error.
10. Troubleshooting
flutter pub get gagal
Kemungkinan penyebab:
- Dependency di
pubspec.yamltidak kompatibel. - Flutter version tidak cocok dengan Dart SDK requirement.
- Package di pub.dev sedang gagal diakses.
Solusi:
sh
flutter pub get
flutter analyzeJalankan lokal dulu untuk memastikan error bisa direproduksi.
flutter build web gagal
Kemungkinan penyebab:
- Ada error compile Dart.
- Ada asset yang tidak ditemukan.
- Ada package yang tidak mendukung web.
Solusi lokal:
sh
flutter build web --releaseUntuk staging, test juga dengan:
sh
flutter build web --release --base-href /app-staging/SSH gagal login
Kemungkinan penyebab:
HOST,USERNAME,PORT, atauSSH_KEYsalah.- Public key belum ditambahkan ke server.
- Server menolak login user tersebut.
- Firewall menutup port SSH.
Cek dari komputer lokal:
sh
ssh -p 22 username@hostSCP berhasil tapi file tidak muncul di web
Kemungkinan penyebab:
targetsalah folder.- Web server memakai document root berbeda.
- Permission file/folder tidak bisa dibaca web server.
- Cache browser atau service worker masih memakai build lama.
Solusi:
- Pastikan
TARGET_STAGINGdanTARGET_PRODUCTIONsesuai document root. - Hard refresh browser.
- Jika perlu, clear site data untuk domain aplikasi.
Staging blank page
Kemungkinan besar --base-href tidak cocok dengan URL.
Jika URL staging adalah:
text
https://domain.com/app-staging/Maka build harus:
sh
flutter build web --release --base-href /app-staging/Jika URL staging adalah subdomain:
text
https://staging.domain.com/Maka build harus:
sh
flutter build web --release --base-href /11. Menambah CI Check
Workflow sekarang fokus pada build dan deploy. Jika ingin lebih ketat, tambahkan step ini setelah flutter pub get:
yaml
- name: Analyze
run: flutter analyze
- name: Test
run: flutter testDengan begitu deploy hanya berjalan jika analyze dan test lolos.
12. Checklist Sebelum Deploy
Sebelum push ke main atau production, cek:
flutter pub getsukses.flutter build web --releasesukses.- Secret GitHub sudah lengkap.
- Folder target server benar.
- User SSH punya izin tulis.
--base-hrefsesuai URL deploy.- Branch yang dipush benar:
mainuntuk staging,productionuntuk production.
13. Kode YAML Lengkap (Hasil Akhir)
Berikut isi lengkap file .github/workflows/main.yml yang dipakai tutorial ini:
yaml
name: Flutter Web CI/CD
concurrency:
group: deploy-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- main
- production
jobs:
build:
runs-on: ubuntu-latest
outputs:
branch: ${{ steps.get_branch.outputs.branch }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get branch name
id: get_branch
run: echo "branch=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.1'
- name: Install dependencies
run: flutter pub get
# STAGING BUILD
- name: Build Staging
if: github.ref == 'refs/heads/main'
run: flutter build web --release --base-href /app-staging/
# PRODUCTION BUILD
- name: Build Production
if: github.ref == 'refs/heads/production'
run: flutter build web --release --base-href /
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: web-build-${{ steps.get_branch.outputs.branch }}
path: build/web/
deploy-staging:
if: needs.build.outputs.branch == 'main'
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build
uses: actions/download-artifact@v4
with:
name: web-build-main
- name: Clean target folder
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
script: |
TARGET="${{ secrets.TARGET_STAGING }}"
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; then
echo "TARGET_STAGING is empty or unsafe"
exit 1
fi
rm -rf "$TARGET"/*
- name: Deploy to Staging
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
source: "*"
target: ${{ secrets.TARGET_STAGING }}
deploy-production:
if: needs.build.outputs.branch == 'production'
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build
uses: actions/download-artifact@v4
with:
name: web-build-production
- name: Clean target folder
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
script: |
TARGET="${{ secrets.TARGET_PRODUCTION }}"
if [ -z "$TARGET" ] || [ "$TARGET" = "/" ]; then
echo "TARGET_PRODUCTION is empty or unsafe"
exit 1
fi
cd "$TARGET"
rm -rf assets
rm -f index.html main.dart.js flutter.js flutter_bootstrap.js flutter_service_worker.js manifest.json version.json
- name: Deploy to Production
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
source: "*"
target: ${{ secrets.TARGET_PRODUCTION }}14. Referensi Resmi
- GitHub Actions workflows: https://docs.github.com/en/actions/concepts/workflows-and-actions/workflows
- Workflow syntax: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- Secrets in GitHub Actions: https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets
- Workflow artifacts: https://docs.github.com/en/actions/tutorials/store-and-share-data