Apa itu Docker — DevOps

"It works on my machine" — kalimat klasik yang setiap dev pernah ucapkan. Kode jalan di laptop kamu, tapi pas dideploy ke server: error. Beda Node version…

Docker adalah alat untuk membungkus aplikasi beserta semua yang dibutuhkannya (versi Node, library, file sistem operasi) jadi satu paket bernama image, lalu menjalankan paket itu sebagai container yang terpisah dari isi komputer lainnya. Bayangin kontainer kapal: isinya bisa apa saja, tapi bentuk luarnya standar, jadi kapal, truk, dan crane mana pun memindahkannya dengan cara yang sama tanpa membongkar isinya. Docker bikin aplikasimu jadi kotak standar begitu: laptopmu, server CI, dan VPS menjalankannya dengan satu perintah yang sama, dan isinya gak berubah di jalan.

🧪 Tebakan cepat: kamu menjalankan dua container dari image catatan-api:1.0, lalu menghapus keduanya dengan docker rm. Apakah image catatan-api:1.0 ikut hilang? Tebak dulu, jawabannya ada di bagian "Image dan container".

Masalah "works on my machine"

Ingat catatan-api, API catatan kita (app.js, port 3000) yang jalan di server web-1. Misalnya laptopmu memakai Node 20, tapi web-1 masih Node 18. Kamu menulis catatan.toSorted(...) untuk mengurutkan catatan terbaru dulu, method yang baru ada sejak Node 20. Di laptop jalan, di web-1 langsung error:

TypeError: catatan.toSorted is not a function

Kalimat "di laptopku jalan kok" (works on my machine) muncul karena tiap mesin punya isi yang berbeda: versi Node, versi library, sistem operasi, environment variable. Docker menyelesaikannya dengan ikut membawa isi itu. Image catatan-api:1.0 berisi file Alpine Linux, Node 20, node_modules, dan kode kamu. web-1 cukup punya Docker; Node versi berapa yang terpasang di sana gak penting lagi, karena yang dipakai adalah Node 20 di dalam image.

Image dan container

Ini terminal di laptop (folder ~/catatan-api), persis seperti di Lab:

$ docker run -d --name catatan-api -p 3000:3000 catatan-api:1.0
3f2a9c1b7d4e8a51c6f0b2d97e4a1c385f6b0d2e9a7c4b1f8e3d6a0c5b9f2e71
$ docker run -d --name catatan-api-2 -p 3001:3000 catatan-api:1.0
8b7e6d5c4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d
$ docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS          PORTS                    NAMES
8b7e6d5c4a3f   catatan-api:1.0   "docker-entrypoint.s…"   15 seconds ago   Up 15 seconds   0.0.0.0:3001->3000/tcp   catatan-api-2
3f2a9c1b7d4e   catatan-api:1.0   "docker-entrypoint.s…"   30 seconds ago   Up 30 seconds   0.0.0.0:3000->3000/tcp   catatan-api

Lalu matikan dan hapus keduanya:

$ docker stop catatan-api catatan-api-2
catatan-api
catatan-api-2
$ docker rm catatan-api catatan-api-2
catatan-api
catatan-api-2
$ docker images
REPOSITORY    TAG         IMAGE ID       CREATED       SIZE
catatan-api   1.0         5e1f8a2c7b90   2 hours ago   182MB
node          20-alpine   1c4e7b2a9f60   3 weeks ago   135MB

Container-nya hilang, image catatan-api:1.0 tetap ada. Itu jawaban tebakannya. Image itu cetakannya, container itu hasil cetakan yang sedang jalan: membuang hasil cetakan gak merusak cetakannya. Yang ikut hilang adalah file yang ditulis app ke dalam container itu sendiri, karena lapisan tulisnya milik container.

Perintah lain yang sering dipakai: docker logs catatan-api membaca apa yang dicetak app (console.log-nya), dan docker stop mengirim sinyal berhenti ke app lalu menunggunya selesai.

Membaca output docker ps

Tabel docker ps enak dibaca manusia, tapi susah dibaca program karena kolomnya dipisah spasi, padahal isinya juga ada spasi (Up 2 hours). Untuk script, minta format sendiri dengan --format, kolomnya dipisah tab (\t):

$ docker ps --format "{{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
3f2a9c1b7d4e	catatan-api:1.0	Up 2 hours	0.0.0.0:3000->3000/tcp	catatan-api

Satu baris per container, lima kolom: ID, image, status, ports, nama. Mengurainya di JavaScript:

const line = "abc123\tnode:20\tUp 2 hours\t0.0.0.0:3000->3000/tcp\tmy-app";
const parts = line.split("\t");
// ["abc123", "node:20", "Up 2 hours", "0.0.0.0:3000->3000/tcp", "my-app"]
const container = { id: parts[0], image: parts[1], status: parts[2], ports: parts[3], name: parts[4] };

Container vs virtual machine

Virtual machine (VM) juga memisahkan aplikasi, tapi dengan cara yang jauh lebih berat. Tiap VM menjalankan sistem operasi lengkap miliknya sendiri, termasuk kernel (inti sistem operasi yang mengatur CPU, memori, dan file), di atas software bernama hypervisor. Container gak punya kernel sendiri: semua container berbagi kernel OS host, dan Docker cuma memisahkan proses, file, dan jaringannya.

3 app dijalankan sebagai 3 VM 3 container
Sistem operasi 3 OS lengkap, masing-masing dengan kernel 1 kernel host dipakai bersama
Memori (contoh) sekitar 3 GB sekitar 180 MB
Waktu nyala (contoh) 30 sampai 60 detik sekitar 1 detik

Karena berbagi kernel Linux, container Linux butuh kernel Linux. Di Mac dan Windows, Docker Desktop diam-diam menjalankan satu VM Linux kecil, dan semua container-mu jalan di dalamnya.

⚠️ Jebakan yang sering ketemu

TL;DR: Docker menyelesaikan masalah "works on my machine" dengan container: aplikasi dan semua isinya dibungkus jadi image, lalu dijalankan sebagai container yang terisolasi. Image itu cetakan read-only, container itu image yang sedang jalan; satu image bisa jadi banyak container, dan docker rm cuma menghapus container, bukan image. Perintah dasar: docker run -d --name ... -p host:container image, docker ps (yang jalan), docker ps -a (termasuk yang berhenti), docker stop, docker rm, docker logs. Container berbagi kernel OS host, VM punya OS lengkap sendiri, jadi container jauh lebih ringan dan nyala dalam hitungan detik. Output docker ps --format dipisah tab: potong per baris dengan \n, lalu per kolom dengan \t.

Yang akan kamu pelajari