GitHub Actions

GitHub Actions #

GitHub Actions adalah platform CI/CD yang terintegrasi langsung dengan repository GitHub. Untuk tim yang sudah menggunakan GitHub, integrasi ini sangat alami — workflow didefinisikan sebagai file YAML di direktori .github/workflows/, dipicu oleh event Git (push, pull_request, merge), dan berjalan di runner yang disediakan GitHub atau runner self-hosted milik sendiri. Artikel ini membahas pola integrasi Ansible dengan GitHub Actions yang komprehensif dan aman, dengan fokus pada struktur workflow yang scalable untuk tim yang mengelola banyak role dan banyak environment.

Anatomi GitHub Actions untuk Ansible #

Sebelum masuk ke workflow spesifik, pahami dulu struktur dasarnya. GitHub Actions punya empat konsep yang harus kita kuasai: event (apa yang memicu workflow), job (unit kerja yang berjalan di runner sendiri), step (perintah individual dalam job), dan action (potongan kode reusable). Untuk Ansible, kebanyakan job adalah kumpulan step yang memanggil ansible-playbook atau ansible-lint.

flowchart LR
    subgraph Trigger["Event"]
        E1["push ke main"]
        E2["pull_request"]
        E3["workflow_call"]
        E4["schedule cron"]
    end

    subgraph Job1["Job: lint"]
        J1S1["actions/checkout@v4"]
        J1S2["setup-python@v5"]
        J1S3["install ansible + ansible-lint"]
        J1S4["ansible-lint --profile production"]
        J1S1 --> J1S2 --> J1S3 --> J1S4
    end

    subgraph Job2["Job: molecule-test"]
        J2S1["matrix: common, nginx, postgresql, docker"]
        J2S2["molecule test untuk setiap role"]
        J1S4 --> J2S1 --> J2S2
    end

    subgraph Job3["Job: build-image"]
        J3S1["docker/metadata-action"]
        J3S2["docker/build-push-action"]
        J3S3["outputs: image_tag"]
        J2S2 --> J3S1 --> J3S2 --> J3S3
    end

    subgraph Job4["Job: deploy-staging"]
        J4S1["setup Ansible"]
        J4S2["setup SSH key dari secrets"]
        J4S3["ansible-playbook deploy.yml"]
        J4S4["smoke test staging"]
        J3S3 --> J4S1 --> J4S2 --> J4S3 --> J4S4
    end

    subgraph Job5["Job: deploy-production"]
        J5S1["approval gate"]
        J5S2["setup SSH + vault"]
        J5S3["ansible-playbook deploy.yml"]
        J5S4["health check"]
        J4S4 --> J5S1 --> J5S2 --> J5S3 --> J5S4
    end

    E1 --> Job1
    E2 --> Job1
    E3 --> Job1
    E4 --> Job3

Diagram di atas menunjukkan pola yang akan kita bangun: trigger event memicu lint job, hasilnya jadi gate untuk molecule test job, lalu build, lalu deploy-staging, dan akhirnya deploy-production yang memerlukan approval. Perhatikan arah panah — setiap needs: di YAML adalah satu panah dependency. Job tanpa needs: berjalan paralel; job dengan needs: menunggu predecessor-nya selesai.

Selalu pisahkan job berdasarkan fungsi, bukan berdasarkan environment. Job lint tidak peduli di environment mana deploy akan terjadi — ia hanya validasi kode. Job deploy-staging tidak peduli lint pass atau tidak — ia hanya butuh artifact. Pemisahan ini membuat workflow lebih mudah di-reuse dan di-debug.

Workflow Dasar: Lint dan Syntax Check #

Setiap pull request harus melewati validasi sebelum bisa di-merge. Workflow lint adalah gate pertama yang paling murah dijalankan — kalau ini gagal, tidak perlu lanjut ke test atau build.

# .github/workflows/validate.yml
name: Validate Ansible

on:
  pull_request:
    paths:
      - '**.yml'
      - '**.yaml'
      - '**.j2'
      - 'roles/**'
      - 'playbooks/**'
      - 'inventory/**'

jobs:
  lint:
    name: Ansible Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: pip-${{ hashFiles('requirements.txt', 'requirements-dev.txt') }}
          restore-keys: pip-

      - name: Install Ansible dan lint
        run: |
          pip install ansible ansible-lint
          ansible-galaxy install -r requirements.yml          

      - name: Syntax check semua playbook
        run: |
          for playbook in playbooks/*.yml; do
            echo "Checking: $playbook"
            ansible-playbook "$playbook" --syntax-check \
              -i inventory/staging/ \
              -e @tests/test-vars.yml
          done          

      - name: Jalankan ansible-lint
        run: ansible-lint --profile production

Tiga hal penting di workflow ini: filter paths memastikan workflow hanya trigger saat file Ansible berubah (tidak jalan jika kita hanya mengedit README), cache pip mempercepat iterasi (install Ansible + ansible-lint butuh 1-2 menit tanpa cache, 10-15 detik dengan cache), dan syntax check manual untuk semua playbook memastikan YAML-nya valid sebelum ansible-lint bahkan dijalankan.


Molecule Test dengan Matrix Parallel #

Molecule test untuk satu role butuh waktu 3-5 menit. Jika kita memiliki 10 role, test sequential butuh 30-50 menit. Dengan matrix, semuanya jalan paralel dan selesai dalam 5-7 menit total.

# .github/workflows/molecule.yml
name: Molecule Test

on:
  pull_request:
    paths:
      - 'roles/**'

jobs:
  molecule:
    name: Test Role — ${{ matrix.role }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        role:
          - common
          - nginx
          - postgresql
          - docker
      fail-fast: false    # Lanjutkan test role lain meski satu gagal

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: molecule-${{ matrix.role }}-${{ hashFiles('**/requirements*.txt') }}

      - name: Install test dependencies
        run: |
          pip install ansible molecule molecule-plugins[docker] pytest-testinfra
          ansible-galaxy install -r requirements.yml          

      - name: Jalankan Molecule test
        run: |
          cd roles/${{ matrix.role }}
          molecule test          
        env:
          PY_COLORS: '1'
          ANSIBLE_FORCE_COLOR: '1'
          MOLECULE_DISTRO: ubuntu2204

      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: molecule-logs-${{ matrix.role }}
          path: roles/${{ matrix.role }}/molecule/default/*.log

Perhatikan fail-fast: false — tanpa flag ini, kalau satu role gagal, GitHub akan cancel semua job matrix yang masih berjalan. Untuk Molecule, kita ingin tetap melihat hasil role lain supaya mengetahui apakah masalahnya menyerang seluruh sistem (semua role gagal dengan error yang sama) atau spesifik (hanya role ini yang bermasalah). Cache key yang unik per role mencegah role A menggunakan dependency cache dari role B yang mungkin sudah di-update.

Jangan lupa if: failure() di step upload-artifact. Tanpa kondisi ini, artifact akan di-upload setiap kali — membanjiri storage dan menambah waktu job. Hanya upload saat test gagal, supaya developer bisa download log tanpa harus re-run workflow untuk reproduce.

Deployment Multi-Environment dengan Approval #

Workflow deploy adalah tempat di mana semua komponen pipeline bertemu. Build job menghasilkan image, deploy-staging job menarik image itu ke staging, dan deploy-production job mem-promote-nya ke production — dengan approval manual di antara keduanya.

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]
    tags: ['v*.*.*']

jobs:
  build:
    name: Build dan Push Image
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.tag.outputs.tag }}
    steps:
      - uses: actions/checkout@v4

      - name: Generate tag
        id: tag
        run: echo "tag=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT

      - name: Login ke registry
        uses: docker/login-action@v3
        with:
          registry: registry.company.com
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build dan push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: |
            registry.company.com/myapp:${{ steps.tag.outputs.tag }}
            registry.company.com/myapp:latest            

  deploy-staging:
    name: Deploy ke Staging
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Setup Ansible
        run: pip install ansible && ansible-galaxy install -r requirements.yml

      - name: Setup credentials
        run: |
          install -d -m 700 ~/.ssh
          echo "${{ secrets.STAGING_SSH_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          echo "${{ secrets.VAULT_PASS_STAGING }}" > /tmp/.vault_pass
          chmod 600 /tmp/.vault_pass          

      - name: Deploy ke staging
        run: |
          ansible-playbook -i inventory/staging/ playbooks/deploy.yml \
            -e "app_version=${{ needs.build.outputs.image_tag }}" \
            --vault-password-file /tmp/.vault_pass \
            --private-key ~/.ssh/deploy_key          

      - name: Smoke test staging
        run: |
          sleep 15
          curl -f https://staging.company.com/health
          curl -f https://staging.company.com/api/version          

      - name: Cleanup credentials
        if: always()
        run: rm -f ~/.ssh/deploy_key /tmp/.vault_pass

  deploy-production:
    name: Deploy ke Production
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    environment:
      name: production       # Set required reviewers di GitHub Settings → Environments
      url: https://app.company.com
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4

      - name: Setup Ansible
        run: pip install ansible && ansible-galaxy install -r requirements.yml

      - name: Setup credentials
        run: |
          install -d -m 700 ~/.ssh
          echo "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          echo "${{ secrets.VAULT_PASS_PROD }}" > /tmp/.vault_pass
          chmod 600 /tmp/.vault_pass          

      - name: Deploy ke production
        run: |
          ansible-playbook -i inventory/production/ playbooks/deploy.yml \
            -e "app_version=${{ needs.build.outputs.image_tag }}" \
            --vault-password-file /tmp/.vault_pass \
            --private-key ~/.ssh/deploy_key          

      - name: Verifikasi production
        run: |
          sleep 30
          curl -f https://app.company.com/health          

      - name: Cleanup
        if: always()
        run: rm -f ~/.ssh/deploy_key /tmp/.vault_pass

Tiga hal yang sering terlewat di workflow di atas: outputs.image_tag di build job diteruskan ke deploy job lewat needs.build.outputs.image_tag — ini menjamin image yang di-deploy ke staging sama persis dengan yang di-deploy ke production. if: always() di step cleanup memastikan credential dihapus meski job gagal (kalau tidak ada kondisi ini, SSH key bisa tertinggal di runner). Environment production di-set dengan required reviewers di GitHub Settings, jadi deploy ke production selalu menunggu approval dari orang yang ditunjuk.

Approval di GitHub Environments punya dua mode: required reviewers (deploy menunggu N orang menyetujui) dan wait timer (deploy otomatis setelah X menit). Kombinasi keduanya cocok untuk skenario di mana deploy ke production ingin di-deliberate tapi tidak boleh di-block indefinitely — set wait timer 60 menit, kalau tidak ada yang approve dalam waktu itu, pipeline mati dan harus di-trigger ulang.

Trigger Strategy: Push vs PR vs Schedule #

Pemilihan trigger yang tepat menentukan feedback loop developer. Trigger yang terlalu sering membuat runner rebutan dan biaya membengkak; trigger yang terlalu jarang membuat bug ketahuan terlambat.

Trigger Kapan Pakai Contoh Catatan
pull_request Validasi kode sebelum merge lint, syntax check, unit test, molecule Jalan di fork tidak punya akses ke secret; pakai pull_request_target hati-hati
push ke branch tertentu Deploy otomatis per branch push ke main → deploy staging Hindari push ke ** — bisa trigger ribuan workflow
push ke tag Release deployment tag v*.*.* → deploy production Cocok untuk versioning semantik
workflow_run Chain ke workflow lain CD workflow tunggu CI selesai Cara resmi untuk integrasi CI → CD tanpa coupling langsung
schedule Pemeliharaan berkala nightly security scan, dependency update Pakai cron expression; jalan di commit main terakhir
workflow_dispatch Trigger manual dari UI/CLI deploy emergency, re-run dengan input Bisa diberi input kustom untuk fleksibilitas
repository_dispatch Trigger dari API eksternal event dari monitoring tool, alert manager Butuh konfigurasi webhook di repo settings
flowchart TD
    A{"Apakah ingin memvalidasi<br/>kode?"} -- Ya --> B["pull_request"]
    A -- Tidak --> C{"Apakah ingin men-deploy<br/>secara otomatis?"}
    C -- Ya --> D{"Berdasarkan branch atau tag?"}
    D -- Branch --> E["push ke branch spesifik"]
    D -- Tag --> F["push ke tag v*"]
    C -- Tidak --> G{"Apakah membutuhkan chain<br/>dari workflow lain?"}
    G -- Ya --> H["workflow_run"]
    G -- Tidak --> I{"Apakah ada jadwal<br/>tertentu?"}
    I -- Ya --> J["schedule cron"]
    I -- Tidak --> K{"Apakah memicu dari<br/>eksternal atau UI?"}
    K -- UI/CLI --> L["workflow_dispatch"]
    K -- API eksternal --> M["repository_dispatch"]
Untuk Ansible, kombinasi yang biasanya paling efektif: pull_request untuk lint + molecule, push ke main untuk deploy staging, dan push ke tag v*.*.* untuk deploy production (di luar approval gate). Schedule weekly untuk dependency update scan. Tambahkan workflow_dispatch untuk emergency hotfix deployment yang tidak menunggu pipeline otomatis.

Reusable Workflow #

Untuk organisasi yang punya banyak repository dengan pola deployment mirip, reusable workflow adalah pengubah permainan. Tulis satu workflow, pakai di mana-mana.

# .github/workflows/reusable-deploy.yml (di shared repository)
name: Reusable Deploy

on:
  workflow_call:           # Bisa dipanggil dari workflow lain
    inputs:
      environment:
        required: true
        type: string
      image_tag:
        required: true
        type: string
      playbook:
        required: false
        type: string
        default: playbooks/deploy.yml
    secrets:
      ssh_key:
        required: true
      vault_password:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: |
          pip install ansible && ansible-galaxy install -r requirements.yml
          echo "${{ secrets.ssh_key }}" > /tmp/deploy_key
          echo "${{ secrets.vault_password }}" > /tmp/.vault_pass
          chmod 600 /tmp/deploy_key /tmp/.vault_pass
          ansible-playbook -i inventory/${{ inputs.environment }}/ \
            ${{ inputs.playbook }} \
            -e "app_version=${{ inputs.image_tag }}" \
            --vault-password-file /tmp/.vault_pass \
            --private-key /tmp/deploy_key
          rm -f /tmp/deploy_key /tmp/.vault_pass          
# Di repository aplikasi — panggil reusable workflow
jobs:
  deploy-staging:
    uses: company/shared-workflows/.github/workflows/reusable-deploy.yml@main
    with:
      environment: staging
      image_tag: ${{ needs.build.outputs.tag }}
    secrets:
      ssh_key: ${{ secrets.STAGING_SSH_KEY }}
      vault_password: ${{ secrets.VAULT_PASS_STAGING }}

Perhatikan secrets: inherit di caller — semua secret dari caller tersedia di reusable workflow sebagai input. Ini menghindari hardcoding nama secret di shared workflow (yang harus tahu nama secret tiap aplikasi). Caller cukup mapping: secret lokal STAGING_SSH_KEY → input ssh_key di shared workflow. Tag @main (atau SHA spesifik untuk production-grade) menentukan versi shared workflow yang dipakai.

Jangan pin reusable workflow ke branch main di repository shared kalau tim lain akan pakai. Tag SHA lebih aman: company/shared-workflows/.github/workflows/reusable-deploy.yml@a1b2c3d4. Dengan SHA, perubahan di shared workflow tidak langsung break semua caller; perlu update SHA secara eksplisit.

Self-Hosted Runner untuk Akses Private Network #

GitHub-hosted runner berjalan di infrastruktur GitHub yang tidak punya akses ke jaringan internal kita. Untuk melakukan deployment ke managed node di VPC private, kita membutuhkan self-hosted runner yang berjalan di dalam jaringan yang sama.

# roles/github-runner/tasks/main.yml
---
- name: Download GitHub Actions runner
  get_url:
    url: "https://github.com/actions/runner/releases/download/v{{ runner_version }}/actions-runner-linux-x64-{{ runner_version }}.tar.gz"
    dest: /home/github-runner/actions-runner.tar.gz

- name: Extract runner
  unarchive:
    src: /home/github-runner/actions-runner.tar.gz
    dest: /home/github-runner/
    remote_src: true

- name: Konfigurasi runner
  command: >
    /home/github-runner/config.sh
    --url https://github.com/{{ github_org }}
    --token {{ vault_runner_token }}
    --name {{ inventory_hostname }}
    --labels self-hosted,production-network
    --unattended    
  become_user: github-runner

- name: Install dan jalankan runner sebagai service
  command: /home/github-runner/svc.sh install
  become: true

Perhatikan label self-hosted,production-network — label ini adalah cara workflow menargetkan runner spesifik. Di workflow, tambahkan runs-on: [self-hosted, production-network] agar job hanya jalan di runner dengan label tersebut. Ini mencegah job tanpa sengaja jalan di GitHub-hosted runner (yang tidak punya akses ke network internal).

GitHub-Hosted vs Self-Hosted Runner #

Aspek GitHub-Hosted Self-Hosted
Biaya Gratis untuk public repo, quota menit untuk private Hanya biaya infrastruktur sendiri (VM, network)
Network access Internet only, tidak bisa ke private VPC Akses penuh ke jaringan internal kita
Tools pre-installed Lengkap (Docker, Node, Python, dsb. versi standar) Hanya yang kita instal sendiri
Performance Standar, kadang antri saat peak Konsisten, tidak ada antrian
Customization Terbatas (ada hosted runner image customization untuk Enterprise) Penuh: OS, tool, hardware
Security boundary Isolasi penuh dari infrastruktur kita Runner kita = trusted compute; jangan jalankan job dari fork PR tanpa filter
Maintenance GitHub yang maintain Kita yang melakukan pemeliharaan (OS update, security patch)
Cocok untuk public repo, testing, open source, workflow tanpa akses internal Deploy ke on-prem, private VPC, integrasi dengan internal tool

Anti-Pattern yang Harus Dihindari #

Tiga anti-pattern yang paling sering muncul di workflow GitHub Actions untuk Ansible:

1. Secret di Echo Langsung ke Log #

# ANTI-PATTERN: secret di-spread ke environment tanpa filter
jobs:
  deploy:
    steps:
      - name: Setup credentials
        run: |
          echo "VAULT_PASS=${{ secrets.VAULT_PASS_PROD }}" >> $GITHUB_ENV
          echo "SSH_KEY=${{ secrets.PROD_SSH_KEY }}" >> $GITHUB_ENV
          ansible-playbook -i inventory/prod/ deploy.yml          

      # ANTI-PATTERN: secret disimpan di GITHUB_ENV dan otomatis
      # di-mask di log... jika ditulis dengan benar.
      # Tapi kalau ada step berikutnya yang echo "koneksi ke $SSH_KEY"
      # atau print env, GitHub mungkin tidak bisa mask semuanya.
# BENAR: secret langsung ditulis ke file, tidak pernah di-echo
jobs:
  deploy:
    steps:
      - name: Setup credentials
        run: |
          mkdir -p ~/.ssh
          printf '%s' "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          printf '%s' "${{ secrets.VAULT_PASS_PROD }}" > /tmp/.vault_pass
          chmod 600 /tmp/.vault_pass          

      - name: Deploy
        run: |
          ansible-playbook -i inventory/prod/ deploy.yml \
            --vault-password-file /tmp/.vault_pass \
            --private-key ~/.ssh/deploy_key          

      - name: Cleanup
        if: always()
        run: rm -f ~/.ssh/deploy_key /tmp/.vault_pass

# BENAR: secret hanya ditulis sebagai file dengan permission 600.
# Tidak pernah di-print, tidak pernah di-set sebagai env var
# yang bisa bocor lewat step lain. Cleanup dengan if: always()
# memastikan file dihapus meski job gagal.

2. Pin Action ke Branch main #

# ANTI-PATTERN: pakai action langsung dari branch main
jobs:
  build:
    steps:
      - uses: actions/checkout@main         # Bisa berubah sewaktu-waktu
      - uses: actions/setup-python@main     # Breaking change bisa muncul besok
      - uses: docker/build-push-action@main # Supply chain attack bisa undetected
# BENAR: pin action ke tag atau SHA immutable
jobs:
  build:
    steps:
      - uses: actions/checkout@v4                       # Tag stabil
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # SHA
      - uses: actions/setup-python@v5
      - uses: docker/build-push-action@v5

# BENAR: SHA adalah gold standard untuk security-sensitive
# workflow. Tag bisa di-force-push atau di-override oleh
# maintainer; SHA adalah cryptographic commitment ke kode
# tertentu dan tidak bisa diubah tanpa GitHub noticing.

3. Workflow Tidak Bisa Di-Reuse #

# ANTI-PATTERN: copy-paste workflow di setiap repository
# File yang sama ada di 8 repository dengan perbedaan kecil
# di environment name, secret name, dan playbook path

# repo-a/.github/workflows/deploy.yml
on:
  push:
    branches: [main]
jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - run: |
          echo "${{ secrets.PROD_SSH_KEY_A }}" > /tmp/key
          ansible-playbook -i inventory/prod/ deploy.yml \
            -e "app_version=$GITHUB_SHA" --private-key /tmp/key
          rm -f /tmp/key          
# BENAR: reusable workflow + caller per repo
# shared-workflows/.github/workflows/deploy-ansible.yml
on:
  workflow_call:
    inputs:
      environment: {required: true, type: string}
      image_tag: {required: true, type: string}
    secrets:
      ssh_key: {required: true}
      vault_password: {required: true}
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - run: |
          echo "${{ secrets.ssh_key }}" > /tmp/key
          echo "${{ secrets.vault_password }}" > /tmp/.vault_pass
          chmod 600 /tmp/key /tmp/.vault_pass
          ansible-playbook -i inventory/${{ inputs.environment }}/ \
            deploy.yml -e "app_version=${{ inputs.image_tag }}" \
            --vault-password-file /tmp/.vault_pass \
            --private-key /tmp/key
          rm -f /tmp/key /tmp/.vault_pass          

# repo-a/.github/workflows/deploy.yml — cuma 6 baris!
jobs:
  deploy:
    uses: org/shared-workflows/.github/workflows/deploy-ansible.yml@v1
    with:
      environment: production
      image_tag: ${{ needs.build.outputs.tag }}
    secrets:
      ssh_key: ${{ secrets.PROD_SSH_KEY_A }}
      vault_password: ${{ secrets.VAULT_PASS_A }}

# BENAR: satu tempat untuk update deployment logic. Bug fix di
# shared workflow langsung bisa di-roll out ke semua caller dengan
# update tag version. Tidak perlu sinkronisasi manual 8 repository.

Ringkasan #

  • Pisahkan workflow: validate.yml untuk PR, molecule.yml untuk test role, deploy.yml untuk deployment — satu workflow fokus pada satu tujuan.
  • environment dengan required reviewers di GitHub Settings adalah cara termudah untuk memerlukan approval sebelum deploy ke production.
  • Gunakan matrix untuk menjalankan Molecule test semua role secara paralel — jauh lebih cepat dari test satu per satu. fail-fast: false supaya semua role tetap di-test meski satu gagal.
  • needs: + outputs: untuk meneruskan nilai antar job — image tag yang dihasilkan build job harus tersedia di deploy job via needs.build.outputs.image_tag.
  • Reusable workflow (workflow_call) untuk berbagi konfigurasi deployment antar repository — satu perubahan di shared workflow langsung berlaku untuk semua aplikasi. Pin ke SHA untuk security.
  • Self-hosted runner untuk akses ke managed node di private network — runner berjalan di dalam jaringan yang sama dengan server target.
  • Pin action ke SHA untuk security-sensitive workflow — tag bisa berubah, SHA adalah cryptographic commitment.
  • Cleanup credential dengan rm -f dan if: always() di setiap job yang menggunakan secret — pastikan tidak ada credential tertinggal meski job gagal.

← Sebelumnya: Pipeline Design   Berikutnya: GitLab CI →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact