Deployment Tradisional #
Sebelum Docker, Kubernetes, dan teknologi cloud-native menjadi arus utama, hampir semua tim software menjalankan aplikasi mereka dengan cara yang sama: login ke server, upload kode, restart service, berdoa. Cara itu bukan buruk — ia bekerja untuk waktu yang lama. Tapi seiring aplikasi tumbuh, cara itu mulai menunjukkan retakan yang tidak bisa ditutup dengan usaha manusia.
Artikel ini membahas deployment tradisional secara mendalam: apa itu, bagaimana alurnya, metode apa saja yang umum dipakai, dan masalah klasik yang melahirkannya Docker. Tujuannya bukan untuk merendahkan pendekatan ini, tapi untuk memberimu konteks historis yang membuat nilai Docker terasa nyata. Tanpa konteks itu, Docker terlihat seperti tool keren tanpa alasan kuat untuk dipelajari.
Jika kamu sudah cukup memahami Docker, kamu boleh skip artikel ini. Tapi jika kamu ingin benar-benar memahami mengapa industri software berpindah dari VM ke container, artikel ini adalah pondasinya.
Apa Itu Deployment Tradisional? #
Deployment tradisional adalah istilah payung untuk pendekatan menjalankan aplikasi langsung di atas sistem operasi server (fisik atau virtual), tanpa isolasi runtime yang ketat. Aplikasi, dependency, dan konfigurasinya di-install langsung ke host.
Karakteristik utamanya:
- Aplikasi berjalan di atas OS host, berbagi resource dengan aplikasi lain di server yang sama.
- Dependency di-install secara global di OS host (
apt install,yum install,npm install -g). - Konfigurasi disimpan di file sistem host (
/etc,~/.env, environment variable global). - Deployment biasanya dilakukan secara manual atau semi-otomatis lewat SSH, script, atau tool konfigurasi.
flowchart TB
subgraph Server["Server Fisik / VM"]
OS[Host OS]
R[Runtime global: Java, Node, PHP]
L[Library global]
App1[App A]
App2[App B]
App3[App C]
C[Config global]
OS --> R --> L
L --> App1
L --> App2
L --> App3
OS --> C
end
User[User] --> Server
Pada gambar di atas, tiga aplikasi berbagi runtime, library, dan konfigurasi yang sama. Jika App A butuh Python 3.8 dan App B butuh Python 3.12, administrator harus memilih satu (dan App lainnya akan bermasalah) atau mengelola dua versi Python secara manual.
Arsitektur Umum #
Arsitektur deployment tradisional pada level paling sederhana:
flowchart LR
U[Pengguna] --> LB[Load Balancer]
LB --> S1[Server 1]
LB --> S2[Server 2]
LB --> S3[Server 3]
subgraph S1["Server 1"]
OS1[OS]
RT1[Runtime]
APP1[Aplikasi]
end
subgraph S2["Server 2"]
OS2[OS]
RT2[Runtime]
APP2[Aplikasi]
end
subgraph S3["Server 3"]
OS3[OS]
RT3[Runtime]
APP3[Aplikasi]
end
Setiap server menjalankan salinan aplikasi yang sama. Load balancer membagi traffic ke server yang tersedia. Untuk scaling, administrator menambah server baru, meng-install runtime dan dependency yang sama, lalu deploy kode.
Masalah mulai terasa saat:
- Jumlah server bertambah (10, 20, 100) — konfigurasi manual jadi mustahil.
- Aplikasi punya banyak service dengan dependency berbeda — tabrakan runtime tak terhindarkan.
- Tim developer dan tim ops terpisah — kode yang jalan di laptop tidak jalan di server mereka.
Metode Deployment yang Umum Digunakan #
Deployment tradisional bukan satu metode tunggal. Ia berkembang dari yang paling sederhana (manual) sampai yang mendekati modern (config management dan VM image). Berikut empat metode yang paling sering dijumpai.
Manual Deployment via SSH #
Metode paling awal dan paling sederhana. Masih banyak tim kecil yang melakukannya.
Alur umum:
- Developer build aplikasi di laptop, hasilkan binary atau arsip.
- Administrator login ke server via SSH.
- Upload file aplikasi menggunakan
scp,rsync, atau FTP. - Install atau update dependency secara manual.
- Restart service menggunakan
systemctlatauservice. - Verifikasi bahwa aplikasi hidup, biasanya dengan
curl localhost.
# Contoh alur manual di terminal engineer
scp ./build/app.tar.gz user@server:/opt/app/
ssh user@server "cd /opt/app && tar -xzf app.tar.gz && systemctl restart app"
Ciri khas pendekatan ini:
- Sangat bergantung pada manusia — setiap langkah rawan lupa atau salah ketik.
- Tidak ada jejak audit yang jelas — hanya
.bash_historyserver. - Tidak bisa diulang dengan presisi — server yang berbeda mungkin punya kondisi yang berbeda.
- Tidak scalable — bayangkan deploy ke 50 server dengan cara ini.
Pada era 2000-an, pendekatan ini lazim. Hari ini, untuk aplikasi production, ia dianggap antipattern.
Shell Script Deployment #
Untuk mengurangi kesalahan manual, tim mulai menulis script deployment — file bash yang mengotomasi langkah-langkah di atas.
#!/bin/bash
# Contoh deploy.sh sederhana
set -e
APP_NAME="webapp"
REMOTE_HOST="[email protected]"
REMOTE_DIR="/opt/webapp"
LOCAL_BUILD="./build/webapp.tar.gz"
echo "==> Uploading build..."
scp "$LOCAL_BUILD" "$REMOTE_HOST:$REMOTE_DIR/"
echo "==> Extracting and restarting..."
ssh "$REMOTE_HOST" << 'EOF'
cd /opt/webapp
tar -xzf webapp.tar.gz
sudo systemctl restart webapp
sleep 2
curl -sf http://localhost/health || exit 1
EOF
echo "==> Deploy selesai."
Script seperti ini lebih baik dari manual, tapi punya kelemahan:
- OS-specific — script untuk Ubuntu tidak jalan di CentOS.
- Tidak idempotent — menjalankan script dua kali bisa menghasilkan state yang berbeda.
- Sulit diuji — kamu tidak bisa benar-benar yakin script ini akan berhasil tanpa menjalankan di server sungguhan.
- Tidak ada rollback otomatis — kalau deploy gagal, script tidak tahu harus kembali ke versi sebelumnya.
Configuration Management #
Untuk menjawab kelemahan shell script, muncullah configuration management tools: Ansible, Chef, Puppet, SaltStack. Mereka memperkenalkan konsep Infrastructure as Code — server dikonfigurasi dari file deklaratif yang bisa di-review, di-version control, dan diulang.
Contoh sederhana dengan Ansible:
# playbook.yml
- hosts: web
become: true
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
- name: Copy app config
copy:
src: ./nginx.conf
dest: /etc/nginx/nginx.conf
mode: 0644
- name: Restart Nginx
service:
name: nginx
state: restarted
Kelebihan dibanding shell script:
- Idempotent — menjalankan playbook 10 kali menghasilkan state akhir yang sama.
- Cross-platform — Ansible bisa mengelola server Linux, FreeBSD, dan Windows.
- Deklaratif — kamu deklarasikan apa yang kamu mau, Ansible yang mencari bagaimana caranya.
- Audit-friendly — playbook bisa di-review, di-test, dan di-version control.
Kekurangannya:
- Agent-based (Chef, Puppet) butuh software di setiap server.
- Learning curve cukup tinggi untuk playbook yang kompleks.
- Dependency tetap di-install global — Ansible tidak mengisolasi aplikasi, hanya mengelola konfigurasinya.
VM Image-Based Deployment #
Pendekatan lain yang populer: bundel aplikasi dan OS jadi satu image, lalu deploy image itu ke server atau cloud.
Contoh paling umum:
- Amazon Machine Image (AMI) — image OS + aplikasi di AWS EC2.
- Vagrant Box — image VM untuk development lokal.
- Packer + QEMU/VMware — tool untuk membangun image VM otomatis.
Alurnya: buat satu image “golden” yang berisi OS + runtime + aplikasi. Dari image itu, spawn banyak instance VM di cloud. Setiap instance identik pada saat boot pertama.
flowchart LR
P[Packer build] --> I[VM Image golden]
I --> V1[VM Instance 1]
I --> V2[VM Instance 2]
I --> V3[VM Instance 3]
V1 --> LB[Load Balancer]
V2 --> LB
V3 --> LB
Kelebihan:
- Konsistensi tinggi — semua instance identik dengan image.
- Cocok untuk environment yang butuh konfigurasi OS khusus.
Kekurangan:
- Ukuran image sangat besar — GB, bukan MB.
- Build lambat — image VM butuh waktu menit untuk dibuat.
- Sulit update — kalau aplikasi berubah, image harus di-rebuild dari awal atau di-patch manual.
- Tidak fleksibel — konfigurasi per-server sulit tanpa image baru.
Catatan evolusi: VM image adalah pendekatan yang lebih “cloud-friendly” dibanding manual atau shell script. Ia menjawab masalah provisioning dengan baik, tapi tidak menjawab masalah dependency isolation dan immutable artifact. Justru kelemahan VM image itulah yang akhirnya memicu adopsi container image — image yang kecil, cepat di-build, dan portabel.
Dependency Management — Sumber Masalah Terbesar #
Jika kamu hanya boleh mengingat satu hal dari artikel ini, ingat ini: dependency management adalah sumber masalah terbesar deployment tradisional.
Skenario yang nyaris universal di setiap tim engineering:
flowchart TB
subgraph Server["Satu server produksi"]
A[App A: butuh Python 3.8 + libX 1.2]
B[App B: butuh Python 3.11 + libX 2.0]
C[App C: butuh Python 2.7 + libX 1.0]
end
Server --> Conflict[Konflik dependency]
Tiga aplikasi, tiga versi Python, tiga versi library yang sama. Administrator harus memilih:
- Install salah satu versi global → aplikasi lain akan gagal.
- Install semua versi side-by-side → mengotori sistem dengan
python3.8,python3.11,python2.7yang membingungkan. - Gunakan tool isolasi per-bahasa (virtualenv, nvm) → membantu, tapi terbatas pada satu bahasa.
Per-bahasa solusinya:
| Bahasa | Tool Isolasi | Keterbatasan |
|---|---|---|
| Python | virtualenv, venv, pyenv |
Hanya untuk Python, bukan untuk system library |
| Node.js | nvm |
Hanya untuk Node, bukan untuk native module |
| Ruby | rbenv, rvm |
Hanya untuk Ruby, berat di sistem |
| Java | Multiple JDK install | Versi JDK harus konsisten dengan tool build |
Masalah fundamental: isolasi per-bahasa tidak mengisolasi sistem. Aplikasi Python yang butuh library C tertentu (misalnya lxml dengan binary binding) tetap akan bentrok dengan library C versi lain di OS.
Konfigurasi Environment yang Berantakan #
Selain dependency, konfigurasi environment juga mimpi buruk deployment tradisional. Setiap environment — development, staging, production — punya kondisi sendiri yang nyaris mustahil disamakan persis.
flowchart LR
Dev[Laptop Developer] -.->|perbedaan kecil| Staging
Staging -.->|perbedaan besar| Prod[Production]
Dev --> |"Works on my machine"| WOMM[Works on my machine]
Contoh masalah yang sering muncul:
- Environment variable — diset di laptop dengan
export, di production ditulis di/etc/environment, di CI diambil dari secret manager. Tidak ada format standar. - Database connection — di dev pakai SQLite lokal, di staging pakai PostgreSQL, di production pakai RDS dengan IAM. String koneksi berbeda, query yang jalan di dev error di production.
- Secret — password database ada di
.envdi laptop developer, di file konfigurasi server, di Vault, di AWS Secrets Manager. Format dan cara akses berbeda. - File path —
/home/dev/appdi laptop,/opt/appdi server. Hardcoded path sering membuat script rusak saat dipindah.
Istilah yang muncul dari kekacauan ini: environment drift. Setiap environment “menyimpang” dari yang lain seiring waktu karena perubahan manual yang tidak terdokumentasi.
Scaling yang Tidak Elastis #
Scaling di deployment tradisional bersifat manual dan lambat. Tidak ada cara untuk menambah kapasitas dalam hitungan detik — yang ada adalah urutan langkah yang makan waktu.
sequenceDiagram
participant Op as Operator
participant Cloud as Cloud Console
participant Prov as Provisioning
participant Config as Config Manager
participant App as App
Op->>Cloud: Launch VM baru
Cloud->>Prov: VM booting (5-10 menit)
Prov->>Config: Apply playbook
Config->>App: Install runtime
Config->>App: Copy app code
Config->>App: Restart service
App-->>Op: Healthy
Op->>Cloud: Add to load balancer
Untuk scale up (menambah kapasitas):
- Provision server baru (VM atau fisik) — menit sampai jam.
- Install OS dan patch.
- Install runtime dan dependency.
- Copy aplikasi dan konfigurasi.
- Konfigurasi load balancer agar traffic mengarah ke server baru.
- Verifikasi health check.
Total waktu: paling cepat 15–30 menit, rata-rata 1–2 jam. Ini tidak cocok untuk traffic spike yang datang tiba-tiba (misalnya event, viral content, atau attack).
Autoscaling praktis tidak ada. Kamu bisa menulis script yang memonitor CPU, tapi setiap scaling event menjalankan semua langkah di atas — lambat dan tidak reliable.
Monitoring dan Logging yang Sederhana #
Di era deployment tradisional, observability adalah sesuatu yang setiap tim implementasinya sendiri, dengan format yang berbeda-beda.
Alat yang umum dipakai:
- Log file di
/var/log, dibaca dengantail -fataugrep. - Cron job untuk rotasi log.
- Custom script untuk alerting (misalnya
if [ $(df / | awk 'NR==2{print $5}') -gt 90 ]; then mail [email protected]; fi). - Nagios, Zabbix, Icinga — monitoring tool klasik dengan UI berbasis web.
# Contoh monitoring 'tradisional' di cron
*/5 * * * * df -h | grep -E '[8-9][0-9]%' && echo "Disk almost full" | mail -s "Alert" [email protected]
Yang tidak ada di pendekatan ini:
- Structured logging — log biasanya plain text, sulit di-query.
- Centralized log aggregation — log tersebar di banyak server.
- Distributed tracing — untuk aplikasi multi-service, sulit melacak request antar service.
- Metrics & dashboards — biasanya grafik MRTG/Cacti untuk network, tapi tidak untuk aplikasi.
Pola antipattern yang masih banyak dijumpai: Aplikasi production di-debug dengan SSH ke server dan tail -f log. Ini bukan monitoring — ini reactive troubleshooting. Monitoring seharusnya proaktif: memberi tahu kamu tentang anomali sebelum user mengeluh. Pendekatan tradisional tidak mampu mendukung workflow ini dengan baik.
Kelebihan Deployment Tradisional #
Pendekatan tradisional bukan tanpa kelebihan. Ada situasi di mana ia masih masuk akal.
- Sederhana secara konsep. Tidak butuh tool tambahan selain SSH dan editor teks.
- Full control ke OS. Administrator punya akses penuh untuk tuning, debugging, dan recovery.
- Mudah dipahami pemula. Tidak ada learning curve untuk tool baru.
- Cocok untuk aplikasi kecil atau internal. Untuk satu server dengan satu aplikasi, deployment tradisional masih sangat praktis.
- Tidak ada overhead container/VM. Aplikasi berjalan langsung di OS tanpa lapisan tambahan.
Untuk aplikasi non-kritis, prototype, atau single-server deployment, pendekatan tradisional masih relevan dan tidak ada alasan kuat untuk bermigrasi.
Kekurangan Deployment Tradisional #
Kekurangan yang sama yang membuat pendekatan ini bermasalah di skala besar:
- Dependency conflict. Aplikasi saling berbagi runtime dan library.
- Tidak reproducible. Server yang sama akan berbeda setelah satu tahun berjalan karena perubahan manual.
- Scaling lambat dan manual. Tidak elastis.
- High human error. Setiap langkah deployment rentan salah.
- Setup awal lama. Onboarding server baru butuh waktu.
- Sulit CI/CD. Build environment sulit distandarkan.
- Debugging sulit. Tidak ada observability modern.
- Environment drift. Dev, staging, production tidak pernah benar-benar identik.
Semua masalah ini terakumulasi seiring waktu. Aplikasi pertama mungkin masih bisa dikelola. Aplikasi kelima belas, dengan 30 server dan 5 bahasa berbeda, menjadi mimpi buruk operasional.
Perbandingan dengan Docker #
Docker menjawab hampir semua masalah di atas. Tabel berikut merangkum perbandingan langsung.
| Masalah Tradisional | Solusi Docker |
|---|---|
| Dependency conflict | Setiap container membawa dependency sendiri |
| Setup manual | Dockerfile (build otomatis, reproducible) |
| Tidak reproducible | Image immutable (sekali build, selalu sama) |
| Sulit scaling | Container orchestration (Kubernetes, Swarm) |
| Environment tidak konsisten | “Build once, run anywhere” |
| Deployment lambat | Pull image + start container (detik) |
| Update aplikasi | Build image baru, replace container |
| Rollback | Pull image versi sebelumnya, restart |
| Onboarding lambat | docker compose up jalankan seluruh stack |
Docker bukan sulap. Ia tidak menghapus kompleksitas — ia menggeser kompleksitas. Kompleksitas berpindah dari “mengelola banyak server dengan konfigurasi manual” ke “menulis Dockerfile, mengelola image, dan mengoperasikan orkestrator”. Tapi kompleksitas baru ini otomatis, terdokumentasi, dan reproducible — jauh lebih sehat.
Kapan Deployment Tradisional Masih Relevan? #
Walau Docker adalah standar modern, ada situasi di mana pendekatan tradisional masih masuk akal.
- Legacy system dengan regulasi ketat. Bank, pemerintah, dan healthcare kadang butuh kontrol OS penuh untuk compliance.
- Aplikasi internal skala kecil. Tools internal dengan 5–10 user — overhead Docker tidak sebanding dengan nilainya.
- Single-board computer / edge device. Raspberry Pi untuk signage atau kiosk mungkin cukup deploy manual.
- Fase prototyping sangat awal. Kalau kamu baru menulis hello world, langsung Docker kadang premature.
- Air-gapped environment. Beberapa environment produksi benar-benar terisolasi dari internet, dan tool modern tidak bisa dijalankan.
Tapi untuk aplikasi yang menghadapi user, butuh scaling, dan jadi penopang bisnis, Docker (atau padanannya) sudah bukan pilihan — ia kebutuhan.
Ringkasan #
- Deployment tradisional adalah menjalankan aplikasi langsung di OS server tanpa isolasi runtime. Ia bekerja untuk skala kecil, tapi retak di skala besar.
- Empat metode umum: manual SSH, shell script, configuration management (Ansible, Puppet, Chef), dan VM image. Masing-masing menjawab sebagian masalah, tapi tidak semua.
- Dependency management adalah sumber masalah terbesar. Aplikasi berbeda butuh runtime berbeda, dan berbagi OS host selalu berakhir konflik.
- Environment drift — kondisi dev, staging, dan production tidak pernah benar-benar identik. Inilah asal muasal “works on my machine”.
- Scaling lambat dan manual: provisioning, install, konfigurasi, register ke load balancer — semua manual, total 15 menit sampai jam.
- Observability ala tradisional (
tail -f, cron script) tidak cukup untuk aplikasi modern. Butuh structured logging, metrics, dan tracing.- Docker menjawab semua masalah ini dengan menggeser kompleksitas dari “konfigurasi manual server” ke “image dan orkestrasi”. Hasilnya: reproducible, scalable, dan otomatis.
- Pendekatan tradisional masih relevan untuk aplikasi kecil, legacy dengan regulasi, atau prototyping awal. Tapi untuk aplikasi bisnis, container sudah jadi kebutuhan, bukan pilihan.