Lewati ke konten utama
Semua artikel
PemulaFondasi

Linux & Terminal untuk Developer

Kuasai command line, navigasi filesystem, permissions, SSH, dan tools terminal yang dipakai sehari-hari oleh backend developer.

31 menit baca
Bagikan

Instagram & WhatsApp Story tidak punya share-link langsung — unduh gambarnya, lalu unggah manual sebagai Story.


"Terminal bukan alat yang menakutkan. Itu adalah alat yang memberimu kendali penuh atas komputer — dan server."


Tentang Artikel Ini

Hampir semua server di dunia menjalankan Linux. Docker container pakai Linux. VPS yang kamu deploy ke production pakai Linux. CI/CD pipeline berjalan di Linux. Tapi banyak developer, terutama yang terbiasa dengan Windows atau Mac dengan GUI, merasa asing ketika harus berinteraksi dengan server melalui terminal.

Saya sendiri dulu termasuk yang menghindari terminal selama mungkin — sampai akhirnya kepepet harus mengelola VPS tanpa GUI sama sekali, dan mau tidak mau belajar sambil jalan. Artikel ini saya tulis supaya kamu tidak perlu melalui fase "kepepet" itu dulu. Kita mulai dari dasar: navigasi filesystem, membaca dan menulis file, memahami permissions, lalu ke hal-hal yang dipakai sehari-hari: SSH ke server remote, mengelola proses, mencari teks di log, sampai menulis script sederhana untuk otomasi.

Tidak ada yang perlu diinstall — semua yang ada di artikel ini bisa langsung dicoba di Terminal (Mac/Linux) atau WSL (Windows).

Prasyarat: Tidak ada. Tapi disarankan sudah membaca artikel #00 (Cara Kerja HTTP) untuk konteks mengapa kamu perlu ini.


Daftar Isi

  1. Kenapa Developer Harus Bisa Terminal?
  2. Navigasi Filesystem
  3. Membaca, Menulis & Mencari File
  4. Permissions & Ownership
  5. Proses & Background Jobs
  6. SSH: Masuk ke Server Remote
  7. Package Management & Setup Environment
  8. Tools Sehari-hari untuk Backend Developer
  9. Shell Script & Otomasi

Bab 1: Kenapa Developer Harus Bisa Terminal?

GUI vs CLI: Bukan Pilihan, Tapi Pelengkap

GUI (Graphical User Interface) bagus untuk eksplorasi dan produktivitas sehari-hari — saya sendiri masih pakai VS Code, bukan vim penuh di terminal, untuk menulis kode sehari-hari. Tapi begitu masuk ke beberapa area kerja tertentu, CLI (Command Line Interface) menang telak, dan ini bukan soal gaya, melainkan soal kebutuhan.

Yang paling terasa buat saya adalah otomasi — kamu bisa menyimpan serangkaian perintah dalam sebuah script dan menjalankannya kapanpun, berkali-kali, tanpa error ketik seperti kalau dilakukan manual. Deploy dengan satu perintah, bukan 12 langkah klik. Dari situ semuanya ikut masuk akal: server production tidak punya layar dan mouse, jadi remote access lewat terminal jadi satu-satunya cara mengelola server via SSH. Perintah seperti rm -rf node_modules && npm install juga punya konsistensi yang tidak dimiliki GUI — instruksi yang sama persis di mana saja, sementara "klik kanan di panel sebelah kiri, pilih properties..." sudah bisa berbeda di versi aplikasi yang berbeda. Setelah terbiasa, kamu juga akan merasakan sendiri soal kecepatan — perintah terminal jauh lebih cepat daripada navigasi menu; rename 1000 file dalam satu perintah dibanding klik satu per satu bukan perumpamaan yang dilebih-lebihkan. Dan yang paling sering saya sadari belakangan: hampir semua README, tutorial, dan dokumentasi teknis ditulis dalam bentuk perintah terminal — kalau kamu belum nyaman dengan terminal, kamu akan kesulitan mengikuti banyak tutorial, sesederhana apapun isinya.

Membuka Terminal

Mac: Cmd + Space → ketik "Terminal" → Enter. Atau pasang iTerm2 (gratis, lebih powerful dengan split pane, search, dll).

Linux (Ubuntu/Debian): Ctrl + Alt + T

Windows: Install WSL (Windows Subsystem for Linux) — jalankan Ubuntu langsung di Windows tanpa VM. Buka Microsoft Store, cari "Ubuntu 22.04", install. Buka "Ubuntu" dari Start Menu, ikuti setup awal.

Shell: Bash vs Zsh

Setiap perintah yang kamu ketik sebenarnya dibaca dan dijalankan oleh sebuah program perantara — itu yang disebut shell. Kamu tidak perlu pusing memilih; dua yang paling sering kamu temui sudah cukup:

Bash (Bourne Again Shell) — default di hampir semua distro Linux, dan pilihan paling aman kalau kamu menulis script yang harus jalan di server mana pun, karena hampir dipastikan tersedia.

Zsh (Z Shell) — default di Mac sejak macOS Catalina. Saya pribadi lebih nyaman pakai ini untuk kerja interaktif sehari-hari: autocomplete-nya lebih pintar, dan ekosistem plugin seperti Oh My Zsh bikin terminal terasa lebih "hidup".

Untuk artikel ini, semua perintah bekerja di keduanya. Config file:

  • Bash: ~/.bashrc (Linux) atau ~/.bash_profile (Mac)
  • Zsh: ~/.zshrc

Anatomi Command

perintah [opsi] [argumen]
   │        │       │
   │        │       └─ Target (file, direktori, URL, dll)
   │        └─ Modifier yang mengubah perilaku (dimulai - atau --)
   └─ Program yang dijalankan
ls -la /home/rinaldy
│  │    └─ argumen: direktori
│  └─ opsi: -l (long format) dan -a (semua termasuk tersembunyi)
└─ perintah: list directory

Minta bantuan:

man ls          # manual page lengkap (navigasi: space/b, cari: /kata, keluar: q)
ls --help       # ringkasan cepat

Bab 2: Navigasi Filesystem

Struktur Direktori Linux

Hal pertama yang terasa asing buat saya waktu pindah dari Windows adalah tidak ada drive C atau D — Linux cuma pakai satu pohon direktori tunggal yang berakar di / (root), dan semua partisi atau drive lain di-mount sebagai subdirektori di bawahnya. Begini susunan yang paling sering kamu temui:

/
├── home/           ← direktori home semua user non-root
│   └── rinaldy/    ← home directory kamu (~)
│       ├── .bashrc         ← config bash (file tersembunyi = diawali .)
│       ├── .ssh/           ← SSH keys dan config
│       └── projects/
├── etc/            ← file konfigurasi sistem (nginx.conf, hosts, dll)
├── var/            ← data yang berubah: log, database, cache
│   ├── log/        ← log sistem dan aplikasi
│   └── www/        ← file web server (konvensi umum)
├── usr/
│   ├── bin/        ← executable program yang diinstall
│   └── local/      ← program yang diinstall secara lokal
├── opt/            ← software third-party (Node.js, dsb)
├── tmp/            ← file sementara (dihapus saat reboot)
└── root/           ← home directory user root (superuser)

Tilde ~ adalah shortcut untuk home directory kamu — /home/rinaldy (Linux) atau /Users/rinaldy (Mac).

Perintah Navigasi

Begitu paham struktur di atas, empat perintah ini yang akan paling sering kamu ketik untuk berpindah-pindah:

pwd                     # Print Working Directory — kamu sedang di mana?
# /home/rinaldy/projects
 
ls                      # list isi direktori saat ini
ls /etc                 # list direktori lain
ls -l                   # long format: permission, owner, size, tanggal
ls -a                   # tampilkan file tersembunyi (diawali .)
ls -la                  # kombinasi: long + all
ls -lh                  # -h: ukuran human-readable (KB, MB, GB)
ls -lt                  # sort by modification time (terbaru dulu)
ls -lS                  # sort by size (terbesar dulu)
 
cd /home/rinaldy        # pindah ke absolute path
cd projects             # pindah ke relative path (subdirektori)
cd ..                   # naik satu level
cd ../..                # naik dua level
cd ~                    # langsung ke home directory
cd -                    # kembali ke direktori sebelumnya (toggle)

Membuat & Mengelola Direktori dan File

Operasi paling dasar — dan yang paling sering kamu pakai tanpa sadar begitu terbiasa:

mkdir myproject                # buat direktori
mkdir -p api/src/controllers   # buat nested direktori sekaligus
 
cp file.txt backup.txt         # copy file
cp -r src/ dst/                # copy direktori (rekursif)
cp -rp src/ dst/               # copy dengan preserve permissions + timestamps
 
mv old-name.txt new-name.txt   # rename file
mv file.txt ~/Documents/       # pindahkan ke direktori lain
mv *.log /var/log/archive/     # pindahkan semua .log ke arsip
 
rm file.txt                    # hapus file
rm -r myproject/               # hapus direktori rekursif
rm -rf myproject/              # hapus paksa tanpa konfirmasi (hati-hati!)
rm -i file.txt                 # -i: minta konfirmasi sebelum hapus
 
touch newfile.txt              # buat file kosong, atau update timestamp file yang ada
 
# Buat symbolic link (shortcut)
ln -s /var/log/nginx/access.log ~/nginx-access.log

Peringatan: rm tidak ada Recycle Bin. File yang dihapus langsung hilang permanen. Selalu verifikasi path sebelum rm -rf.

Absolute vs Relative Path

Ini konsep yang sering bikin bingung di awal, tapi begitu kepakai berkali-kali langsung jadi refleks. Intinya: absolute path selalu dimulai dari / dan berlaku sama persis di mana pun kamu berada, sementara relative path tergantung posisi kamu sekarang.

# Absolute — mulai dari root /, berlaku dari mana saja
cd /home/rinaldy/projects/myapi
cat /etc/nginx/nginx.conf
 
# Relative — relatif dari posisi saat ini
# Jika kamu di /home/rinaldy:
cd projects/myapi          # = /home/rinaldy/projects/myapi
cd ../Documents            # = /home/rinaldy/Documents
cat ../../etc/hosts        # = /etc/hosts (naik dua level dulu)

Bab 3: Membaca, Menulis & Mencari File

Membaca File

Ini kemungkinan besar perintah yang paling sering kamu ketik setelah cd dan ls. Beberapa pilihan, tergantung seberapa besar file dan apa yang mau kamu lihat:

cat file.txt               # tampilkan semua isi file sekaligus
cat -n file.txt            # dengan nomor baris
less file.txt              # tampilkan per halaman (Space: next, b: back, q: quit, /: cari)
more file.txt              # seperti less tapi lebih sederhana
 
head file.txt              # 10 baris pertama
head -n 20 file.txt        # 20 baris pertama
tail file.txt              # 10 baris terakhir
tail -n 50 app.log         # 50 baris terakhir
tail -f app.log            # follow: tampilkan baris baru real-time (Ctrl+C untuk stop)
tail -F app.log            # follow: reconnect jika file dirotasi (lebih robust)

tail -f adalah perintah yang paling sering dipakai developer saat monitoring log aplikasi yang sedang berjalan.

Menulis File

Kadang kamu tidak perlu buka editor sama sekali — cukup redirect output langsung ke file:

echo "Hello World"              # print ke terminal
echo "Hello" > file.txt         # tulis ke file (timpa jika sudah ada)
echo "Baris baru" >> file.txt   # append ke akhir file
 
# Heredoc: tulis multi-line ke file
cat > config.json << 'EOF'
{
  "host": "localhost",
  "port": 5432,
  "database": "myapp"
}
EOF

Text Editor di Terminal

nano — ini yang saya sarankan kalau kamu baru mulai. Shortcut-nya selalu ditampilkan di bagian bawah layar, jadi kamu tidak perlu menghafal apa-apa dari awal:

nano server.js
 
# Shortcut (^ = Ctrl):
# ^O   → Write Out (save)
# ^X   → Exit
# ^K   → Cut baris
# ^U   → Uncut (paste)
# ^W   → Where Is (cari)
# ^G   → Get Help

vim — kurva belajarnya jauh lebih curam, dan jujur saja butuh waktu sebelum terasa nyaman. Tapi begitu terbiasa, saya pribadi jadi jarang balik ke editor lain untuk edit cepat di server:

vim server.js
 
# Mode:
# Normal (default) — untuk navigasi dan perintah
# Insert           — untuk mengetik teks (tekan i)
# Visual           — untuk seleksi teks (tekan v)
 
# Perintah Normal mode:
# i        → masuk Insert mode sebelum cursor
# a        → masuk Insert mode setelah cursor
# Esc      → kembali ke Normal mode
# :w       → save
# :q       → quit
# :wq      → save + quit
# :q!      → quit paksa tanpa save
# /kata    → cari ke depan
# n        → next search result
# dd       → hapus baris
# yy       → copy (yank) baris
# p        → paste
# u        → undo
# Ctrl+r   → redo
# gg       → pergi ke baris pertama
# G        → pergi ke baris terakhir
# :42      → pergi ke baris 42

Pipe: Sambungkan Perintah

Ini salah satu konsep yang begitu "klik" akan mengubah cara kamu kerja di terminal selamanya. Pipe | menyambungkan output satu perintah jadi input perintah berikutnya, jadi kamu bisa merangkai beberapa perintah sederhana jadi satu alur pemrosesan:

cat app.log | grep "ERROR"              # filter baris error
ls -la | sort -k5 -n                    # list file, sort by size (kolom 5)
cat access.log | wc -l                  # hitung jumlah baris log
ps aux | grep node | grep -v grep       # cari proses Node.js
cat big-file.txt | head -100 | tail -10 # baris 91-100

Mencari File dan Konten

Dua perintah ini yang paling sering saya andalkan begitu project sudah membesar dan susah lagi cari sesuatu cuma dengan mata:

# find: cari file berdasarkan nama/tipe/ukuran/waktu
find . -name "*.js"                    # semua file .js dari sini
find . -name "*.js" -not -path "*/node_modules/*"  # exclude node_modules
find /var/log -name "*.log" -mtime -1  # log yang dimodifikasi dalam 1 hari terakhir
find . -type d -name "dist"            # hanya cari direktori bernama dist
find . -size +10M                      # file lebih besar dari 10MB
find . -empty                          # file kosong
find . -name "*.tmp" -delete           # cari dan hapus sekaligus
 
# grep: cari teks di dalam file
grep "ERROR" app.log                   # baris yang mengandung "ERROR"
grep -i "error" app.log                # case insensitive
grep -n "TODO" src/server.js           # tampilkan nomor baris
grep -r "db.query" ./src               # rekursif di semua file dalam ./src
grep -v "DEBUG" app.log                # tampilkan baris yang TIDAK mengandung DEBUG
grep -c "404" access.log               # hitung jumlah match
grep -l "process.env" ./src/**/*.js    # hanya tampilkan nama file yang match
grep -A 3 -B 2 "FATAL" app.log         # 2 baris sebelum dan 3 baris setelah match
 
# Kombinasi powerful
grep "ERROR" app.log | grep "2026-06-29" | wc -l    # jumlah error hari ini
grep -r "TODO\|FIXME\|HACK" ./src --include="*.js"   # cari technical debt

Bab 4: Permissions & Ownership

Membaca Permission

Setiap kali kamu jalankan ls -l, ada satu kolom yang sering diabaikan padahal isinya penting — kombinasi karakter di paling kiri itu yang menentukan siapa boleh apa terhadap file:

-rwxr-xr--  1  rinaldy  staff  4096  Jun 29 10:00  server.js
│└──┴──┴──     │         │
│  │  │  └─ Others (semua user lain): r-- = read only
│  │  └──── Group (anggota group staff): r-x = read + execute
│  └─────── Owner (rinaldy): rwx = read + write + execute
└────────── File type: - = file, d = directory, l = symlink

Cuma ada tiga jenis izin yang perlu kamu pahami, dan begitu hafal, membaca kombinasi rwx jadi otomatis:

  • r (4) — baca isi file / list isi direktori
  • w (2) — tulis/ubah file / buat file di direktori
  • x (1) — jalankan file sebagai program / masuk ke direktori dengan cd

chmod

Cara yang paling saya sarankan untuk dihafal: notasi oktal, tinggal jumlahkan nilai r+w+x untuk tiap level. Awalnya kelihatan seperti kode rahasia, tapi setelah beberapa kali dipakai jadi kebiasaan tangan.

chmod 755 script.sh      # rwxr-xr-x: owner semua, group + others read+execute
chmod 644 config.json    # rw-r--r--: owner read+write, lainnya read only
chmod 600 .env           # rw-------: hanya owner yang bisa baca/tulis (sensitif!)
chmod 400 id_rsa         # r--------: SSH private key, read only untuk owner
chmod 777 /tmp/shared    # rwxrwxrwx: semua bisa semua (hindari kecuali perlu)
 
chmod +x deploy.sh       # tambah execute untuk semua
chmod -w config.prod.js  # hapus write permission untuk semua (jadikan read-only)
chmod u+x,g-w file.txt   # owner: tambah execute; group: hapus write
 
chmod -R 755 public/     # rekursif untuk seluruh direktori
OktalPermissionContoh penggunaan
700rwx------direktori privat owner
755rwxr-xr-xscript yang bisa dijalankan semua
644rw-r--r--file konfigurasi, HTML, CSS
600rw-------.env, private config
400r--------SSH private key

chown

Kalau chmod mengatur apa yang boleh dilakukan, chown mengatur siapa pemiliknya — ini yang sering kamu butuhkan setelah deploy, ketika file ownership-nya masih milik user lain:

chown rinaldy file.txt                    # ubah owner
chown rinaldy:developers file.txt         # ubah owner dan group
chown -R www-data:www-data /var/www/html  # rekursif (untuk web server)

sudo

sudo (superuser do) menjalankan perintah dengan privilege root. Gunakan hanya saat memang diperlukan — saya sendiri punya kebiasaan membaca ulang perintah yang mau saya jalankan dengan sudo sebelum menekan Enter, karena begitu privilege root terlibat, tidak ada lagi pagar yang menahan kalau ternyata ada yang keliru.

sudo apt update                    # update package list
sudo apt install nginx             # install software
sudo systemctl restart nginx       # restart service
sudo nano /etc/nginx/nginx.conf    # edit config sistem
 
# Jangan lakukan ini kecuali kamu tahu persis apa yang terjadi:
# sudo rm -rf /          ← menghapus seluruh sistem!
# sudo chmod -R 777 /    ← semua orang bisa akses segalanya

Bab 5: Proses & Background Jobs

Melihat Proses

Server yang sehat bukan berarti tidak ada masalah, tapi kamu tahu persis apa yang sedang berjalan di dalamnya. Dua perintah ini yang saya buka duluan setiap kali curiga ada yang tidak beres:

ps aux                           # semua proses
ps aux | grep node               # filter proses Node.js
ps aux | grep -v grep | grep node  # exclude baris grep itu sendiri
 
top                              # interactive monitor (q: quit, k: kill, h: help)
htop                             # lebih bagus — warna, mouse, tree view (apt install htop)

Outputnya memang padat, tapi kamu cuma perlu hafal beberapa kolom ini untuk mulai terbiasa membacanya:

  • PID — Process ID, angka unik
  • %CPU — persentase CPU
  • %MEM — persentase RAM
  • VSZ/RSS — virtual/resident memory usage
  • STAT — status: R(running), S(sleeping), Z(zombie), T(stopped)
  • COMMAND — perintah yang menjalankan proses

Mengelola Proses

Menemukan proses yang bermasalah baru setengah pekerjaan — langkah berikutnya adalah menghentikannya dengan cara yang benar:

# Hentikan proses
kill 1234           # SIGTERM: minta berhenti dengan baik (bisa di-ignore)
kill -15 1234       # sama dengan kill, SIGTERM
kill -9 1234        # SIGKILL: paksa berhenti, tidak bisa di-ignore
killall node        # hentikan semua proses bernama "node"
pkill -f server.js  # hentikan proses yang command-nya mengandung "server.js"
 
# Shortcut di terminal
Ctrl+C              # kirim SIGINT ke proses foreground (interrupt)
Ctrl+Z              # suspend proses (kirim ke background, bisa dilanjutkan)
Ctrl+D              # kirim EOF (tutup input, atau logout dari shell)

Kebiasaan yang saya pegang: selalu coba kill (SIGTERM) dulu supaya proses sempat membereskan diri — menutup koneksi database, menyelesaikan request yang sedang jalan, dan semacamnya. Baru kalau setelah beberapa detik masih membandel, kill -9 jadi pilihan terakhir.

Background Jobs

Kadang kamu perlu terminal yang sama tetap bisa dipakai sementara satu proses tetap berjalan di belakang layar. Ini caranya:

node server.js         # foreground: terminal diblokir
node server.js &       # background: terminal tetap bisa dipakai
                       # shell print PID: [1] 12345
 
jobs                   # lihat semua background jobs di session ini
fg                     # bawa job terakhir ke foreground
fg %1                  # bawa job #1 ke foreground
bg                     # lanjutkan job suspended di background
bg %2                  # lanjutkan job #2
 
# nohup: proses tetap jalan setelah logout
nohup node server.js > app.log 2>&1 &
disown                 # lepaskan proses dari shell (tidak terpengaruh logout)

systemctl: Service Management

Untuk service yang memang seharusnya selalu hidup — nginx, database, aplikasi kamu sendiri — jangan andalkan nohup atau background job manual. Pakai systemctl, supaya proses otomatis restart kalau crash dan otomatis jalan lagi setelah server reboot:

sudo systemctl start nginx          # start
sudo systemctl stop nginx           # stop
sudo systemctl restart nginx        # stop + start
sudo systemctl reload nginx         # reload config tanpa stop (kalau didukung)
sudo systemctl status nginx         # status + log terbaru
sudo systemctl enable nginx         # auto-start saat boot
sudo systemctl disable nginx        # jangan auto-start
sudo systemctl is-active nginx      # cek apakah sedang jalan
 
# Lihat log service via journalctl
sudo journalctl -u nginx -f         # follow log nginx
sudo journalctl -u myapp --since "1 hour ago"
sudo journalctl -u myapp -n 100     # 100 baris terakhir

Bab 6: SSH: Masuk ke Server Remote

Dasar SSH

Begitu kamu punya server sendiri, SSH adalah pintu masuk utamamu ke sana — semua perintah, semua isi terminal yang kamu lihat di layar, dikirim lewat koneksi yang terenkripsi antara laptop dan server. Default port-nya 22, meskipun banyak yang mengubahnya demi keamanan tambahan.

ssh username@ip-address
ssh rinaldy@103.28.12.45
ssh rinaldy@103.28.12.45 -p 2222          # port custom
ssh -v rinaldy@103.28.12.45               # verbose: debug koneksi

Jangan kaget kalau pertama kali konek ke server baru, SSH menampilkan sederet fingerprint dan minta konfirmasi — itu bukan error, cuma cara SSH memastikan kamu benar-benar terhubung ke server yang dimaksud, bukan ke pihak lain yang menyamar. Ketik yes, dan fingerprint itu akan disimpan di ~/.ssh/known_hosts supaya tidak ditanya lagi di koneksi berikutnya.

SSH Key Authentication

Saya berhenti pakai password untuk SSH sejak lama — selain rentan kena brute force, ada saja bot yang mencoba-coba login ke server manapun yang portnya terbuka. SSH key pair jauh lebih aman, dan begitu terpasang, login pun jadi tidak perlu ketik password sama sekali.

Buat key pair:

# Buat key pair (ed25519 lebih modern dari RSA)
ssh-keygen -t ed25519 -C "rinaldy@backendlabs.id"
# Simpan di: ~/.ssh/id_ed25519 (default, tekan Enter)
# Passphrase: opsional tapi disarankan untuk keamanan ekstra
 
# Hasilnya:
# ~/.ssh/id_ed25519      ← PRIVATE KEY — jangan pernah share atau commit!
# ~/.ssh/id_ed25519.pub  ← Public key — ini yang dikopikan ke server

Kopikan public key ke server:

# Cara otomatis (paling mudah)
ssh-copy-id rinaldy@103.28.12.45
 
# Cara manual (jika ssh-copy-id tidak tersedia)
cat ~/.ssh/id_ed25519.pub | ssh rinaldy@103.28.12.45 \
  "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

Test:

ssh rinaldy@103.28.12.45   # harus bisa masuk tanpa password

SSH Config: Shortcut

Kalau kamu punya lebih dari satu server, mengetik ssh user@ip-panjang -p port berulang-ulang cepat terasa melelahkan. Solusinya, simpan semua detail koneksi di satu file:

# ~/.ssh/config
Host backendlabs
    HostName 103.28.12.45
    User rinaldy
    Port 22
    IdentityFile ~/.ssh/id_ed25519
 
Host staging
    HostName 103.28.12.99
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_staging
ssh backendlabs    # = ssh rinaldy@103.28.12.45
ssh staging        # = ssh deploy@103.28.12.99 -p 2222

Transfer File dengan scp dan rsync

Untuk mengirim file ke atau dari server, kamu tidak perlu FTP client terpisah — cukup manfaatkan koneksi SSH yang sudah ada:

# scp: copy via SSH (simpel)
scp file.txt rinaldy@103.28.12.45:/home/rinaldy/    # upload
scp rinaldy@103.28.12.45:/var/log/app.log ./         # download
scp -r ./dist rinaldy@103.28.12.45:/var/www/myapp/   # upload direktori
 
# rsync: sync file, hanya kirim yang berubah (lebih efisien)
rsync -avz ./dist/ rinaldy@103.28.12.45:/var/www/myapp/
rsync -avz --delete ./dist/ rinaldy@103.28.12.45:/var/www/  # hapus file yang tidak ada di source
rsync -avzn ./dist/ server:/var/www/  # -n: dry run, lihat dulu apa yang akan berubah

SSH Tunneling (Port Forwarding)

Ini trik yang lumayan sering saya pakai: kadang kamu perlu mengakses database atau service lain di server yang sengaja tidak di-expose ke internet demi keamanan. SSH tunneling memungkinkan kamu "meminjam" koneksi SSH yang sudah terenkripsi untuk mengakses service itu seolah-olah jalan di laptop sendiri:

# Local port forwarding: akses database server dari laptop
ssh -L 5433:localhost:5432 rinaldy@103.28.12.45
# Sekarang di laptop: psql -h localhost -p 5433 -U postgres
# → terhubung ke PostgreSQL di server melalui tunnel
 
# Background tunnel
ssh -fNL 5433:localhost:5432 rinaldy@103.28.12.45
# -f: fork to background, -N: no command (hanya forward)

Bab 7: Package Management & Setup Environment

apt: Package Manager Linux (Debian/Ubuntu)

Di server Linux, apt adalah cara standar install software — tidak perlu download installer atau klik-klik seperti di desktop:

sudo apt update                      # update daftar package yang tersedia
sudo apt upgrade                     # upgrade semua package yang sudah diinstall
sudo apt install nginx               # install package
sudo apt install git curl wget       # install beberapa sekaligus
sudo apt remove nginx                # hapus package
sudo apt autoremove                  # hapus package yang tidak diperlukan
apt search postgresql                # cari package
apt show nodejs                      # info detail package
dpkg -l | grep nginx                 # cek apakah package terinstall

Homebrew: Package Manager Mac

Di Mac, Homebrew berperan seperti apt — jadi cara paling praktis buat install tools development tanpa harus cari-cari installer satu per satu:

brew update                          # update Homebrew sendiri
brew install postgresql@16           # install
brew uninstall postgresql@16         # hapus
brew upgrade                         # upgrade semua
brew search redis                    # cari
brew info node                       # info
brew list                            # semua yang terinstall
brew services start postgresql@16    # jalankan sebagai service (auto-start)
brew services stop postgresql@16     # stop service
brew services list                   # list services

nvm: Kelola Versi Node.js

Ini salah satu tools yang akhirnya wajib saya pasang di laptop manapun — begitu kamu kerja di lebih dari satu project, dan masing-masing butuh versi Node.js yang beda, install Node.js langsung tanpa nvm cuma akan bikin pusing sendiri:

# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# Restart terminal atau source ~/.bashrc
 
# Pakai nvm
nvm install 20              # install Node.js v20 (LTS)
nvm install 18              # install Node.js v18
nvm use 20                  # pakai v20 untuk session ini
nvm alias default 20        # set v20 sebagai default
nvm ls                      # list versi yang terinstall
nvm current                 # versi yang sedang aktif
 
# .nvmrc: tentukan versi per project
echo "20" > .nvmrc
nvm use                     # baca dari .nvmrc

Bash Profile: Setup Environment

File ini dijalankan otomatis setiap kali kamu buka terminal baru — ~/.bashrc untuk Bash, ~/.zshrc untuk Zsh. Saya anggap ini tempat "rumah" untuk semua kebiasaan terminal saya sendiri: alias yang sering dipakai, environment variable, sampai fungsi custom kecil-kecilan.

# ~/.bashrc atau ~/.zshrc
 
# Alias: shortcut untuk perintah yang sering dipakai
alias ll='ls -la'
alias la='ls -A'
alias ..='cd ..'
alias ...='cd ../..'
alias gs='git status'
alias gp='git push'
alias gl='git log --oneline -20'
alias dc='docker compose'
alias dce='docker compose exec'
 
# Environment variables default
export EDITOR='nano'                    # editor default
export LANG='en_US.UTF-8'
export NODE_ENV='development'
 
# Tambahkan direktori ke PATH
export PATH="$HOME/.local/bin:$PATH"
export PATH="/usr/local/go/bin:$PATH"   # Go
export PATH="$HOME/.cargo/bin:$PATH"    # Rust
 
# Fungsi custom
mkcd() {
    mkdir -p "$1" && cd "$1"
}
 
# Contoh: buat direktori sekaligus masuk ke dalamnya
# mkcd projects/new-api

Setelah edit .bashrc atau .zshrc, terapkan perubahan tanpa restart terminal:

source ~/.bashrc    # atau
source ~/.zshrc

Bab 8: Tools Sehari-hari untuk Backend Developer

grep & Variasi

Sudah kita bahas dasarnya di Bab 3 — tapi ada satu tools tambahan yang layak kamu kenal kalau sering bekerja dengan codebase besar:

# ripgrep (rg): grep yang jauh lebih cepat, sudah exclude .git dan node_modules
# Install: apt install ripgrep / brew install ripgrep
rg "console.log" ./src          # cari di semua file source
rg -t js "TODO"                 # hanya file .js
rg --stats "error" app.log      # statistik match

awk: Proses Kolom Data

Setiap kali output sebuah perintah berbentuk kolom-kolom, dan kamu cuma butuh satu atau dua kolom saja, awk adalah jawabannya. Butuh waktu untuk terbiasa dengan sintaksnya, tapi begitu kepakai, susah untuk tidak pakai lagi:

# Print kolom tertentu dari output ls -l
ls -l | awk '{print $5, $9}'        # size dan filename
# Output: 4096 Documents
 
# Hitung total ukuran file dari ls -l
ls -l *.log | awk '{sum += $5} END {print sum, "bytes"}'
 
# Filter access log dan ambil IP address (kolom pertama)
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
 
# Proses CSV sederhana
echo "name,age,city" > data.csv
echo "Rinaldy,28,Jakarta" >> data.csv
awk -F',' '{print $1, "lives in", $3}' data.csv
# Output: name lives in city
#         Rinaldy lives in Jakarta

sed: Ubah Teks

sed (stream editor) ini yang saya andalkan tiap kali harus mengubah teks di banyak file sekaligus, tanpa harus buka satu-satu di editor:

# Ganti semua occurance "localhost" dengan IP server
sed 's/localhost/103.28.12.45/g' config.template.js > config.js
 
# Edit file langsung (-i: in-place)
sed -i 's/NODE_ENV=development/NODE_ENV=production/g' .env
 
# Hapus baris yang mengandung kata tertentu
sed '/DEBUG/d' app.log > clean.log
 
# Tampilkan hanya baris tertentu
sed -n '10,20p' app.log         # baris 10 sampai 20
 
# Hapus baris kosong
sed '/^$/d' messy.txt

Networking Tools

Ketika sesuatu tidak bisa diakses — API tidak merespons, koneksi ke server putus — tools ini yang saya pakai untuk melacak di lapisan mana masalahnya:

# ping: cek apakah host bisa dijangkau
ping google.com                 # ping terus-menerus (Ctrl+C untuk stop)
ping -c 5 google.com            # hanya 5 kali
ping -c 1 103.28.12.45          # cek koneksi ke server
 
# curl: HTTP requests (detail di artikel #00)
curl -I https://backendlabs.id  # hanya headers (HEAD request)
curl -s https://ipapi.co/json/ | python3 -m json.tool  # IP info
 
# netstat / ss: lihat koneksi jaringan aktif
ss -tlnp                        # TCP listening, numeric, dengan PID
ss -tlnp | grep :3000           # siapa yang listen di port 3000?
netstat -tlnp                   # alternatif (mungkin perlu install net-tools)
 
# lsof: lihat file/port yang sedang dipakai
lsof -i :3000                   # proses apa yang pakai port 3000?
lsof -i :5432                   # cek PostgreSQL jalan di port 5432
lsof -p 1234                    # semua file yang dibuka oleh PID 1234
 
# nmap: scan port (perlu install)
nmap -p 80,443,3000 103.28.12.45  # cek port mana yang terbuka

Disk & Memory

Server yang tiba-tiba lambat atau berhenti merespons seringkali bukan soal kode, tapi disk yang penuh atau RAM yang habis. Cek dulu sebelum curiga ke tempat lain:

df -h                           # free disk space semua partisi (human-readable)
df -h /var                      # hanya partisi /var
 
du -sh /var/log                 # total ukuran direktori
du -sh *                        # ukuran semua item di sini
du -sh * | sort -rh | head -10  # 10 terbesar
du -sh /var/lib/docker          # ukuran data Docker
 
free -h                         # RAM: total, used, free, cached
 
# Temukan file besar yang memakan disk
find /var/log -name "*.log" -size +100M    # log lebih dari 100MB
find / -xdev -size +1G 2>/dev/null        # file lebih dari 1GB

tar & Kompresi

Untuk backup atau kirim kumpulan file sekaligus, kompres dulu jadi satu archive — lebih cepat ditransfer dan lebih rapi:

# Buat archive
tar -czf backup.tar.gz ./myapp             # gzip (cepat)
tar -cjf backup.tar.bz2 ./myapp            # bzip2 (lebih kecil, lebih lambat)
tar -cJf backup.tar.xz ./myapp             # xz (terkecil, paling lambat)
 
# Lihat isi tanpa ekstrak
tar -tzf backup.tar.gz
 
# Ekstrak
tar -xzf backup.tar.gz                     # di direktori saat ini
tar -xzf backup.tar.gz -C /tmp/restore/   # ke direktori tertentu
 
# Zip (lebih umum di ecosystem Windows)
zip -r archive.zip ./myapp
unzip archive.zip -d /tmp/extracted/

Bab 9: Shell Script & Otomasi

Struktur Dasar Script

Sampai di sini, kamu sudah bisa menjalankan perintah satu-satu di terminal. Langkah berikutnya adalah menyimpan rangkaian perintah itu jadi satu script, supaya bisa dijalankan berulang kali tanpa risiko salah ketik. Setiap script bash yang layak dipakai serius, menurut saya, minimal punya dua baris ini:

#!/bin/bash
# Shebang di baris pertama: tentukan interpreter
 
# Aktifkan mode strict (sangat direkomendasikan untuk semua script)
set -euo pipefail
# -e: exit jika ada perintah yang gagal
# -u: error jika variable tidak terdefinisi
# -o pipefail: pipeline gagal jika salah satu perintahnya gagal
 
# Variabel
APP_NAME="myapi"
APP_DIR="/var/www/${APP_NAME}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)    # capture output perintah dengan $()
 
echo "Starting deployment of ${APP_NAME} at ${TIMESTAMP}"

Simpan sebagai deploy.sh, beri izin eksekusi:

chmod +x deploy.sh
./deploy.sh

Variabel dan Input

Script yang berguna jarang sekali hardcode semuanya — biasanya perlu menerima input dari orang yang menjalankannya:

#!/bin/bash
set -euo pipefail
 
# Positional arguments
APP_ENV=${1:-development}    # $1 = argumen pertama, default "development"
APP_PORT=${2:-3000}          # $2 = argumen kedua, default 3000
echo "Running in ${APP_ENV} on port ${APP_PORT}"
 
# Special variables
echo "Script name: $0"
echo "All args: $@"
echo "Arg count: $#"
echo "Last exit code: $?"    # harus cek setelah setiap perintah
echo "Current PID: $$"
 
# Read dari user
read -p "Are you sure? (y/N): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
    echo "Cancelled."
    exit 1
fi

Kondisional

Sama seperti bahasa pemrograman lain, script bash juga butuh percabangan logika — dan sintaksnya memang sedikit berbeda sampai kamu terbiasa:

#!/bin/bash
set -euo pipefail
 
ENV=${1:-development}
LOG_FILE="/var/log/myapp.log"
 
# If-elif-else
if [[ "$ENV" == "production" ]]; then
    echo "Building for production..."
    npm run build
    NODE_ENV=production pm2 restart myapp
elif [[ "$ENV" == "staging" ]]; then
    echo "Building for staging..."
    npm run build
    pm2 restart myapp-staging
else
    echo "Starting development server..."
    npm run dev
fi
 
# Cek apakah file ada
if [[ -f "$LOG_FILE" ]]; then
    echo "Log file exists: $LOG_FILE"
fi
 
# Cek apakah direktori ada
if [[ ! -d "/var/www/myapp" ]]; then
    echo "Directory not found! Creating..."
    mkdir -p /var/www/myapp
fi
 
# Cek apakah command tersedia
if ! command -v node &> /dev/null; then
    echo "Node.js not found! Please install it first."
    exit 1
fi

Loop

Ini yang bikin script terasa benar-benar berguna — mengulang satu aksi untuk banyak item sekaligus, tanpa harus menulis ulang perintah yang sama berkali-kali:

#!/bin/bash
set -euo pipefail
 
# Loop array
SERVICES=("nginx" "postgresql" "redis")
for SERVICE in "${SERVICES[@]}"; do
    if systemctl is-active --quiet "$SERVICE"; then
        echo "✓ ${SERVICE} is running"
    else
        echo "✗ ${SERVICE} is NOT running"
        sudo systemctl start "$SERVICE"
    fi
done
 
# Loop dengan counter
for i in {1..10}; do
    echo "Attempt $i of 10..."
    curl -sf http://localhost:3000/health && break
    sleep 3
done
 
# Loop file
for log in /var/log/myapp/*.log; do
    SIZE=$(du -sh "$log" | cut -f1)
    echo "${log}: ${SIZE}"
done
 
# While loop
RETRIES=0
while ! curl -sf http://localhost:3000/health; do
    RETRIES=$((RETRIES + 1))
    if [[ $RETRIES -ge 10 ]]; then
        echo "Health check failed after 10 retries!"
        exit 1
    fi
    echo "Waiting for server... (attempt $RETRIES)"
    sleep 2
done
echo "Server is healthy!"

Fungsi

Begitu script kamu mulai memanjang, mengelompokkan logika yang berulang ke dalam fungsi bikin script jauh lebih mudah dibaca — dan lebih mudah di-debug kalau ada yang salah:

#!/bin/bash
set -euo pipefail
 
# Definisi fungsi
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
 
check_service() {
    local SERVICE=$1    # local: variabel hanya ada di dalam fungsi
    if systemctl is-active --quiet "$SERVICE"; then
        log "✓ ${SERVICE} running"
        return 0        # return value (exit code): 0 = success
    else
        log "✗ ${SERVICE} NOT running"
        return 1
    fi
}
 
backup_db() {
    local DB_NAME=$1
    local OUTPUT_DIR=${2:-/tmp/backups}
    local TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    local OUTPUT_FILE="${OUTPUT_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"
 
    mkdir -p "$OUTPUT_DIR"
    log "Backing up ${DB_NAME} to ${OUTPUT_FILE}..."
    pg_dump -U postgres "$DB_NAME" | gzip > "$OUTPUT_FILE"
    log "Backup complete: $(du -sh "$OUTPUT_FILE" | cut -f1)"
}
 
# Panggil fungsi
log "Deployment started"
check_service nginx || sudo systemctl start nginx
backup_db "myapp_production" "/var/backups/db"
log "Done"

Deploy Script Lengkap

Sekarang mari gabungkan semua yang sudah dibahas jadi satu contoh yang mendekati apa yang benar-benar saya pakai di production — variabel, pengecekan dependency, kondisional, sampai loop untuk health check:

#!/bin/bash
set -euo pipefail
 
APP="backendlabs"
APP_DIR="/var/www/${APP}"
REPO="git@github.com:rinaldy/backendlabs.git"
BRANCH=${1:-main}
 
log() { echo "[$(date '+%H:%M:%S')] $*"; }
die() { echo "ERROR: $*" >&2; exit 1; }
 
log "Starting deployment of ${APP} (branch: ${BRANCH})"
 
# Cek dependencies
command -v node  || die "Node.js not found"
command -v npm   || die "npm not found"
command -v pm2   || die "pm2 not found"
 
# Pull code terbaru
log "Pulling latest code..."
cd "$APP_DIR"
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"
 
# Install dependencies (hanya yang berubah)
log "Installing dependencies..."
npm ci --only=production
 
# Run migrations
log "Running database migrations..."
npm run migrate:up
 
# Restart aplikasi
log "Restarting application..."
pm2 restart "$APP" --update-env
 
# Health check
log "Waiting for health check..."
for i in {1..10}; do
    if curl -sf http://localhost:3000/health; then
        log "✓ Health check passed!"
        break
    fi
    [[ $i -eq 10 ]] && die "Health check failed after 10 attempts!"
    sleep 3
done
 
log "✓ Deployment complete!"

cron: Jadwalkan Script

Script deploy di atas masih perlu kamu jalankan manual. Untuk tugas yang harus jalan sendiri tanpa ada orang yang mengetik apa-apa — backup tengah malam, cleanup file temp, pengecekan berkala — itu tugasnya cron. Dia menjalankan perintah otomatis sesuai jadwal yang kamu tentukan, jalan di latar belakang selama server hidup.

crontab -e    # edit jadwal (otomatis pilih editor)
crontab -l    # lihat jadwal saat ini
crontab -r    # hapus semua jadwal (hati-hati!)

Format: menit jam tanggal bulan hari_minggu perintah

# Backup database setiap hari jam 2 pagi
0 2 * * * /home/rinaldy/scripts/backup.sh >> /var/log/backup.log 2>&1
 
# Rotate log setiap Minggu jam 3 pagi
0 3 * * 0 /home/rinaldy/scripts/rotate-logs.sh
 
# Cek disk usage setiap jam, kirim alert jika > 80%
0 * * * * df -h / | awk 'NR==2{if ($5+0 > 80) print "DISK ALERT: "$5}' | mail -s "Disk Alert" admin@email.com
 
# Cleanup file temp setiap hari jam 1 pagi
0 1 * * * find /tmp -type f -mtime +7 -delete
 
# Setiap 5 menit
*/5 * * * * /path/to/script.sh
 
# Hanya hari kerja (Senin-Jumat) jam 9 pagi
0 9 * * 1-5 /path/to/weekday-report.sh

Gunakan crontab.guru untuk membantu menulis dan memvalidasi jadwal cron.

Beberapa kebiasaan yang saya pegang supaya cron job tidak jadi sumber masalah yang susah dilacak:

  • Selalu gunakan absolute path untuk perintah dan file
  • Redirect output ke log file: >> /var/log/myscript.log 2>&1
  • Test script secara manual sebelum dijadwalkan
  • Tambahkan set -euo pipefail di script yang dijadwalkan

Penutup

Terminal yang dulu terasa asing sekarang (semoga) terasa seperti alat yang bisa kamu andalkan. Dari ls dan cd yang paling dasar, sampai SSH ke server production dan deploy script otomatis — semua ini akan kamu pakai nyaris setiap hari.

Saran saya soal cara melatihnya: jangan menghafal daftar perintah. Ambil satu hal yang biasanya kamu lakukan lewat GUI — menyalin file ke server, mengecek ukuran folder, mencari teks di dalam project — dan paksakan diri mengerjakannya di terminal minggu ini, walau awalnya lebih lambat. Perintah yang kamu cari sendiri karena sedang butuh akan menempel; perintah yang kamu baca di daftar tidak.

Checklist Pemahaman