Belajar GitLab CI/CD - Continuous Deployment (CD) ke Linux Server via SSH / Ansible
Episode 15 of 21

Belajar GitLab CI/CD - Continuous Deployment (CD) ke Linux Server via SSH / Ansible

Deployment ke VPS sering dianggap menyeramkan karena melibatkan SSH, kunci, dan mesin produksi. Episode ini menunjukkan alur CD yang aman dan berulang: menyiapkan kunci SSH sebagai variabel file, mengonfigurasi ssh-agent dan ssh-keyscan di pipeline, menjalankan perintah remote dan sinkronisasi rsync, lalu mengotomasi semuanya dengan playbook Ansible.

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

Pendahuluan

Di episode 14 sebelumnya kita membahas environment dan deployment gates — target tempat aplikasi dikirim, riwayat deployment, dan persetujuan manusia. Sekarang tibalah bagian yang paling dinanti: mengirim aplikasi secara nyata ke server. Tidak semua perusahaan berjalan di Kubernetes; sebagian besar memulai dari sebuah VPS Linux dengan Nginx, yang paling simpel adalah menjalankan deployment lewat SSH.

Di episode 6 kita sudah membangun image Docker, dan di episode 7 mengelola artefak build. Episode ini menggabungkan semuanya ke target paling klasik dalam dunia DevOps: server Linux. Kalian akan belajar menyimpan kunci SSH dengan aman sebagai File type variable di GitLab, mengonfigurasi ssh-agent dan ssh-keyscan di dalam pipeline, mengeksekusi perintah remote serta sinkronisasi file dengan rsync, dan terakhir mengotomasi deployment penuh menggunakan playbook Ansible.

Ini adalah pola yang langsung bisa kalian pakai di pekerjaan nyata — dan justru di sinilah banyak pipeline pemula gagal, biasanya karena perintah SSH yang meminta interaksi (prompt host key atau passphrase) sehingga pipeline menggantung selamanya. Mari kita bangun alur yang benar dari awal.

Pembahasan Utama

Arsitektur CD ke Server Linux

Skenario kita sederhana namun nyata: aplikasi web dibangun di pipeline, lalu hasilnya (folder dist/) dikirim ke server produksi myapp.example.com dan service-nya dinyalakan ulang — pipeline men-build artefak, job deployment mengirimnya lewat SSH, dan remote command menjalankan reload. Dua bagian yang wajib dipersiapkan sebelum pipeline bisa berjalan: kunci SSH dan host key server.

Menyiapkan SSH_PRIVATE_KEY sebagai File Type Variable

Pertama, generate pasangan kunci di mesin lokal. Kunci ed25519 direkomendasikan — lebih pendek, lebih cepat, dan modern:

Generate kunci SSH untuk deployment
ssh-keygen -t ed25519 -C "gitlab-cicd-deploy" -f ~/.ssh/gitlab_deploy_key -N ""

Public key (~/.ssh/gitlab_deploy_key.pub) ditambahkan ke ~/.ssh/authorized_keys pada user deployment di server target. Kunci privat (~/.ssh/gitlab_deploy_key) disimpan ke GitLab sebagai File type variable:

  1. Buka Settings → CI/CD → Variables, lalu klik Add variable.
  2. Isi key SSH_PRIVATE_KEY, pilih Type: File, lalu tempel isi kunci privat.
  3. Aktifkan flag Masked jika memungkinkan dan Protected bila kunci hanya dipakai di branch utama.

Note

Untuk File type variable, nilai yang disimpan GitLab ditulis ke sebuah file di runner, dan variabel SSH_PRIVATE_KEY memuat path ke file tersebut — bukan isinya. Karena itu ssh-add bisa langsung memakainya tanpa menyalin isi ke variabel shell. Variabel tambahan seperti DEPLOY_HOST dan DEPLOY_USER bisa disimpan sebagai regular variables.

Warning

Jangan pernah men-generate kunci tanpa passphrase (-N "") lalu menyebarkannya ke sembarang tempat. Kunci deployment adalah kunci untuk server produksi — simpan hanya sebagai protected variable, beri user deployment di server hanya akses ke direktori yang dibutuhkan, dan gunakan kunci terpisah untuk tujuan berbeda.

Setup SSH Agent & ssh-keyscan di before_script

Masalah klasik ketika menjalankan SSH dari pipeline: perintah SSH meminta konfirmasi host key (Are you sure you want to continue connecting?) yang tidak bisa dijawab di CI, sehingga job menggantung hingga timeout. Solusinya adalah ssh-keyscan — mengambil host key server dan menaruhnya ke ~/.ssh/known_hosts sebelum SSH dipanggil.

Kunci privat juga sebaiknya dimuat ke ssh-agent — daemon yang menyimpan kunci di memori, sehingga ssh dan rsync tidak perlu membaca ulang file kunci setiap koneksi. Setup lengkapnya diletakkan di before_script agar semua job deployment memakainya:

Setup SSH agent & known_hosts di before_script
variables:
  DEPLOY_HOST: myapp.example.com
  DEPLOY_USER: deploy
 
.deploy-setup: &deploy-setup
  before_script:
    - eval "$(ssh-agent -s)"
    - ssh-add "$SSH_PRIVATE_KEY"
    - mkdir -p ~/.ssh
    - ssh-keyscan "$DEPLOY_HOST" >> ~/.ssh/known_hosts

Perhatikan detail penting: ssh-keyscan menambahkan host key ke known_hosts, sehingga koneksi SSH berjalan tanpa interaksi. Menggunakan eval "$(ssh-agent -s)" memastikan agent berjalan di shell job ini, dan ssh-add memuat kunci ke memori agent.

Eksekusi Deployment: SSH dan Rsync

Dengan setup di atas, dua pola eksekusi paling umum adalah:

  1. Remote commands via SSH — menjalankan perintah di server, misalnya reload service atau menjalankan migration.
  2. File sync via rsync — menyinkronkan artefak build dari runner ke server.

Contoh job deployment lengkap yang menggabungkan keduanya:

Deploy dengan rsync + remote command
deploy-production:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client rsync
    - eval "$(ssh-agent -s)"
    - ssh-add "$SSH_PRIVATE_KEY"
    - mkdir -p ~/.ssh
    - ssh-keyscan "$DEPLOY_HOST" >> ~/.ssh/known_hosts
  environment:
    name: production
    url: https://myapp.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  script:
    - rsync -avz --delete dist/ "$DEPLOY_USER@$DEPLOY_HOST:/var/www/myapp/"
    - ssh "$DEPLOY_USER@$DEPLOY_HOST" "sudo systemctl reload nginx"

Dua hal yang perlu diperhatikan di job ini:

  • --delete pada rsync membuat direktori di server persis sama dengan artefak — file yang tidak ada di build ikut dihapus. Ini mencegah file usang menumpuk.
  • Kombinasi rules + when: manual mengingatkan kita pada episode 14: deployment produksi menunggu persetujuan manusia dan hanya berjalan di branch main.

Tip

Untuk kecepatan, tambahkan opsi rsync seperti --compress dan --partial. Untuk keamanan, jangan pernah menjalankan job deployment sebagai root di server — gunakan user deploy dengan sudo terbatas hanya pada perintah yang dibutuhkan (misal systemctl reload nginx).

Automasi Deployment dengan Ansible dari GitLab CI

Ketika deployment sudah lebih dari sekadar menyalin file — misal butuh mengelola beberapa server, mengubah konfigurasi Nginx, atau menjalankan langkah kondisional — script shell yang panjang mulai sulit dipelihara. Di sinilah Ansible masuk: playbook mendeklarasikan keadaan akhir server, dan GitLab CI cukup memanggil ansible-playbook.

Buat playbook dan inventory di repository:

Inventory Ansible (inventory/production.ini)
[production]
myapp ansible_host=myapp.example.com ansible_user=deploy
Playbook Ansible (deploy.yml)
- name: Deploy aplikasi ke production
  hosts: production
  become: true
  tasks:
    - name: Pastikan direktori aplikasi ada
      file:
        path: /var/www/myapp
        state: directory
 
    - name: Sinkronkan artefak build ke server
      synchronize:
        src: dist/
        dest: /var/www/myapp/
 
    - name: Reload Nginx
      systemd:
        name: nginx
        state: reloaded

Modul synchronize di Ansible memanfaatkan rsync di balik layar, sehingga pola sinkronisasi yang sama tetap berlaku. Job CI-nya menjadi sangat bersih:

Job deployment berbasis Ansible
deploy-ansible:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client ansible
    - eval "$(ssh-agent -s)"
    - ssh-add "$SSH_PRIVATE_KEY"
    - mkdir -p ~/.ssh
    - ssh-keyscan "$DEPLOY_HOST" >> ~/.ssh/known_hosts
  environment:
    name: production
    url: https://myapp.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  script:
    - ansible-playbook -i inventory/production.ini deploy.yml

Seluruh logika deployment kini tinggal di playbook Ansible — bisa diuji lokal, bisa di-versioning, dan tidak terkunci di dalam YAML pipeline. Job GitLab hanya bertugas: menyiapkan SSH, lalu menyerahkan eksekusi ke Ansible.

Kesalahan Umum dalam CD via SSH

KesalahanGejalaSolusi
Tidak ada ssh-keyscanJob menggantung saat prompt host keySeed known_hosts sebelum koneksi
Kunci dengan passphraseJob meminta interaksi yang tak bisa dijawabGenerate tanpa passphrase untuk CI
Mengirim kunci privat sebagai regular variableIsi kunci terlihat di log jika echoGunakan File type variable + Masked
rsync tanpa --deleteFile usang menumpuk di serverTambahkan --delete untuk sinkron penuh
Menjalankan SSH sebagai rootRisiko keamanan besarGunakan user khusus dengan sudo terbatas
Playbook Ansible tanpa uji lokalError baru muncul di produksiJalankan ansible-playbook --check dulu

Important

Selalu uji koneksi dengan mode check sebelum deployment sungguhan. Ansible menyediakan ansible-playbook --check untuk dry-run, dan perintah SSH bisa diuji manual dengan ssh -o StrictHostKeyChecking=no pada mesin developer. Semakin banyak yang diuji sebelum produksi, semakin sedikit yang meledak setelah produksi.

Penutup

Pada episode ini kita telah membahas alur Continuous Deployment ke Linux server melalui SSH: menyiapkan pasangan kunci ed25519 dan menyimpannya sebagai File type variable SSH_PRIVATE_KEY di GitLab; mengonfigurasi ssh-agent dan ssh-keyscan di before_script agar koneksi berjalan tanpa interaksi; mengeksekusi deployment dengan remote commands via SSH dan sinkronisasi artefak dengan rsync; serta mengotomasi seluruh deployment dengan playbook Ansible yang dipanggil langsung dari pipeline.

Inti dari episode ini: deployment yang andal adalah deployment yang bisa diulang tanpa kejutan. Dengan kunci yang disimpan aman, setup SSH yang deterministik, dan playbook yang mendeklarasikan keadaan akhir, men-deploy ke server menjadi tindakan yang biasa — bukan momen yang menegangkan.

Di episode 16 selanjutnya kita membawa deployment ke level berikutnya: Continuous Deployment ke Kubernetes — menghubungkan GitLab ke cluster secara aman dengan GitLab Agent for Kubernetes, men-deploy manifest dan Helm chart dengan kubectl dan helm upgrade --install, serta memperkenalkan GitOps dengan ArgoCD dan Flux. Sampai jumpa!