Belajar GitHub Actions - Continuous Deployment (CD) ke Server via SSH, Rsync, dan Ansible
Episode 13 of 21

Belajar GitHub Actions - Continuous Deployment (CD) ke Server via SSH, Rsync, dan Ansible

Episode ini membahas deployment otomatis ke server Linux mulai dari menyiapkan private key SSH di secrets, mengeksekusi perintah remote dengan ssh-action, sinkronisasi file dengan rsync, hingga menjalankan playbook Ansible langsung dari pipeline.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Pendahuluan

Di episode 12 kita berhasil membangun dan mengirim image Docker ke registry. Tetapi image yang menganggur di registry tidak berarti apa-apa — hasil masakan yang bagus harus sampai ke meja. Episode 13 ini mengantarkannya: Continuous Deployment (CD) — deploy otomatis dari pipeline ke server produksi.

Fokusnya server Linux/VPS klasik, yang sampai hari ini masih menjadi tulang punggung banyak aplikasi. Kita mulai dari menyiapkan private key SSH, eksekusi perintah remote dengan appleboy/ssh-action, sinkronisasi file dengan Rsync/SCP, hingga manajemen konfigurasi yang idempoten dengan Ansible.

Pembahasan Utama

Menyiapkan Private Key SSH di Secrets

Deploy via SSH butuh kredensial: sepasang kunci ed25519. Kunci publik diletakkan di file authorized_keys di server, sedangkan kunci privat disimpan sebagai secret repository — tidak pernah di-commit.

Buat pasangan kunci SSH di mesin lokal
ssh-keygen -t ed25519 -C "github-actions-deploy@devvnull"
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@server.example.com

ssh-copy-id menyalin kunci publik ke server dan menguji loginnya. Kemudian isi ~/.ssh/id_ed25519 disimpan ke Settings > Secrets sebagai SERVER_SSH_KEY.

Warning

Kunci privat adalah master key ke server produksi — jangan pernah menaruhnya di repository, di Dockerfile, atau di log. Batasi risikonya: buat akun deploy khusus dengan izin seminimal mungkin, tambahkan batasan di authorized_keys (misalnya from= untuk membatasi IP sumber), dan batasi port SSH dengan firewall.

Remote Execution via SSH: appleboy/ssh-action

Cara paling langsung: workflow mengeksekusi perintah di server melalui SSH. Action appleboy/ssh-action membungkus koneksi SSH dan menjalankan skrip remote:

Deploy dengan eksekusi remote SSH
name: Deploy Production
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          port: 22
          script: |
            cd /srv/app
            git pull origin main
            npm ci --omit=dev
            systemctl restart app

Skrip di script dijalankan di server: menarik kode terbaru, menginstal dependensi produksi, lalu me-restart service systemd. Karena skrip ini ditulis statis di workflow (bukan hasil interpolasi data user), ia aman dari script injection yang kita bahas di episode 9. Nilai-nilai dari secrets ditangani action ini langsung, tidak pernah muncul di log.

Sinkronisasi File dengan Rsync dan SCP

Terkadang yang perlu dikirim bukan perintah, melainkan file — misalnya hasil build frontend dari job sebelumnya. Pilihan pertama: appleboy/scp-action, yang mengunggah file lewat koneksi yang sama:

Upload hasil build dengan SCP
- uses: appleboy/scp-action@v0.1.7
  with:
    host: ${{ secrets.SERVER_HOST }}
    username: ${{ secrets.SERVER_USER }}
    key: ${{ secrets.SERVER_SSH_KEY }}
    source: "dist/*"
    target: "/srv/app/dist"
    strip_components: 1

source dan target menentukan file yang dikirim dan tujuan di server; strip_components menghilangkan direktori induk agar isi dist/ langsung jatuh ke dist/ server.

Untuk proyek dengan banyak file, rsync lebih efisien karena hanya mentransfer perbedaan. Ia bisa dipanggil langsung dari step bash memakai OpenSSH:

Sinkronisasi rsync via OpenSSH
rsync -az --delete \
  -e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \
  dist/ deploy@server.example.com:/srv/app/dist/

-a mempertahankan atribut file, -z mengompresi transfer, dan --delete menghapus file di server yang tidak ada lagi di sumber — sehingga server selalu menjadi cermin persis hasil build. Kunci privat perlu disiapkan dulu di runner, misalnya dari secret.

Deployment dengan Ansible

Untuk satu server, skrip SSH sederhana cukup. Tetapi begitu aplikasi berjalan di beberapa server dengan konfigurasi yang harus konsisten, skrip one-off mulai rapuh. Di sinilah Ansible berperan: tool manajemen konfigurasi yang idempoten — menjalankan playbook berulang kali menghasilkan kondisi yang sama, tanpa efek samping ganda.

Ansible berjalan dari mesin kontrol (runner), terhubung ke server via SSH, dan tidak butuh agent terpasang di server. Dari workflow, kita siapkan Python dan Ansible lalu jalankan playbook:

Menjalankan playbook Ansible dari workflow
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install ansible
      - name: Siapkan kunci SSH
        env:
          SSH_KEY: ${{ secrets.SERVER_SSH_KEY }}
        run: |
          mkdir -p ~/.ssh
          printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
      - name: Jalankan playbook
        run: ansible-playbook -i inventory/prod.yml playbooks/deploy.yml
        env:
          ANSIBLE_HOST_KEY_CHECKING: "False"

Playbook dan inventory disimpan di repository — konfigurasi server pun terversi seperti kode. Contoh playbook sederhana yang memperbarui kode, memasang dependensi, dan me-restart service:

Potongan playbook deploy
- name: Deploy aplikasi
  hosts: web
  become: true
  tasks:
    - name: Perbarui kode dari git
      ansible.builtin.git:
        repo: https://github.com/devvnull/app.git
        dest: /srv/app
        version: main
    - name: Pasang dependensi
      ansible.builtin.command: npm ci --omit=dev
      args:
        chdir: /srv/app
    - name: Restart service
      ansible.builtin.systemd:
        name: app
        state: restarted

Keunggulan idempotensi terasa saat deploy gagal di tengah jalan: kalian cukup me-restart workflow, dan Ansible hanya menyelesaikan bagian yang belum selesai — tidak melakukan ulang semuanya secara membabi buta.

Kesalahan Umum

KesalahanGejalaSolusi
Private key di-commit ke repositoryAkses server bisa direbut siapa punPutar kunci, simpan hanya sebagai secret
Host key verification errorKoneksi SSH ditolakSetel ANSIBLE_HOST_KEY_CHECKING untuk Ansible
Path source SCP salahFile tidak terunggah atau tercecerPeriksa path relatif dan strip_components
Lupa menginstal AnsiblePerintah ansible tidak ditemukanTambahkan setup-python + pip install ansible
Deploy ke server tanpa rollbackError langsung melanda produksiSimpan versi rilis dan siapkan rollback

Penutup

Deployment ke server kini menjadi bagian dari pipeline, bukan ritual manual:

  • Private key SSH disimpan sebagai secret dan diletakkan sebagai kunci publik di server; kunci privat tidak pernah menyentuh repository.
  • appleboy/ssh-action mengeksekusi perintah remote; scp-action dan rsync menyalin file hasil build.
  • Ansible menangani konfigurasi multi-server yang idempoten, dijalankan langsung dari workflow dengan setup-python + pip install ansible.
  • Semua langkah terversi dan tervalidasi lewat pull request seperti kode biasa.

Di episode 14 berikutnya, kita naik kelas: Continuous Deployment ke Cloud & Kubernetes — deploy ke Cloud Run, Vercel, dan AWS Lambda, lalu ke cluster Kubernetes dengan kubectl dan Helm. Dari mengelola satu server, menjadi mengelola seluruh platform!

Belajar GitHub Actions - Continuous Deployment (CD) ke Server via SSH, Rsync, dan Ansible | Belajar GitHub Actions