Docker adalah alat yang mempopulerkan container sekaligus standar de facto cara memakainya sejak 2013. Konsep layer, image, dan registry yang kita bangun di Chapter Container Concepts semua dijalankan Docker lewat satu baris perintah yang nyaman: docker run, dan seisi dunia mulai memakainya. Chapter ini membedah praktik sehari-harinya — arsitektur client-server di balik perintah itu, siklus hidup container, penulisan Dockerfile, dan volume untuk data yang harus bertahan. Karena daemon Docker tidak terpasang di lingkungan latihan, kita bekerja dengan berkas konfigurasi sungguhan yang ditulis dan dibedah langsung, ditambah simulasi siklus hidup di terminal.

Arsitektur Client-Server #

Perintah docker yang kita ketik hanyalah klien tipis. Klien berbicara lewat Unix socket /var/run/docker.sock kepada dockerd — daemon yang berjalan sebagai root di host dan mengerjakan semua pekerjaan berat: membuat container, menjalankan image, mengunduh dari registry, membangun jaringan virtual. Pemisahan ini yang membuat docker CLI tetap bisa mengelola host jauh dengan docker -H ssh:budi@server-kantor ... — klien di laptop, daemon di server. Berikut potongan contoh tampilan docker version, yang mencetak kedua sisi sekaligus:

text
Client: Docker Engine - Community
 Version:    27.3.1
 OS/Arch:    linux/amd64

Server: Docker Engine - Community
 Engine Version:  27.3.1

Bagian Server kosong atau error berarti daemon tidak berjalan — atau klien tidak punya izin ke socket-nya. Akses socket diatur lewat group docker, dan keputusan ini serius:

Image dan Container: Kelas dan Instansi #

Hubungan image dan container mengikuti hubungan program dan proses yang kita kenal di Chapter Process Concepts : image adalah cetak biru baca-saja yang diam di disk, container adalah instance yang sedang berjalan di memori. Satu image bisa melahirkan banyak container sekaligus — tiga container dari image nginx:1.27 adalah tiga proses terpisah yang masing-masing punya lapisan tulisnya sendiri, tetapi berbagi layer baca-saja yang sama. Menghapus container membuang lapisan tulisnya saja; image tetap utuh dan siap melahirkan container baru.

Dockerfile adalah resep membangun image: satu berkas berisi instruksi yang dieksekusi dari atas ke bawah, dan setiap instruksi menghasilkan satu layer baru. Kita tulis resep untuk server web sederhana:

bash
cat > Dockerfile <<'EOF'
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends nginx \
    && rm -rf /var/lib/apt/lists/*
COPY index.html /var/www/html/index.html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
EOF
cat Dockerfile

Output yang Dihasilkan:

text
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends nginx \
    && rm -rf /var/lib/apt/lists/*
COPY index.html /var/www/html/index.html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Mari kita bedah instruksi demi instruksi:

  1. Baris 1 (FROM debian:12-slim): menetapkan image dasar — titik awal tumpukan layer kita; 12-slim adalah varian Debian 12 yang dipangkas sehingga image dasarnya kecil.
  2. Baris 2–3 (RUN apt-get ...): perintah yang dieksekusi saat build, bukan saat container berjalan — memasang nginx lalu membersihkan cache apt. Keduanya digabung dalam satu RUN dengan && agar menjadi satu layer: layer bersifat tetap, jadi membersihkan cache di instruksi terpisah tidak akan mengecilkan layer sebelumnya.
  3. Baris 4 (COPY index.html ...): menyalin berkas dari folder proyek (disebut build context) ke dalam image — satu layer baru berisi hanya berkas ini.
  4. Baris 5 (EXPOSE 80): dokumentasi bahwa container mendengarkan port 80 — murni catatan, tidak menerbitkan port apa pun.
  5. Baris 6 (CMD [...]): perintah default saat container menyala; format array menjadikan nginx langsung PID 1 tanpa shell perantara, dan daemon off; menjaga nginx tetap di foreground — container hidup selama proses utamanya hidup.

Saat docker build . dijalankan, tiap instruksi mencetak satu langkah beserta ID layer yang dihasilkannya — berikut potongan contoh tampilannya:

text
Step 1/5 : FROM debian:12-slim
 ---> 830c8d477f88
Step 2/5 : RUN apt-get update && apt-get install -y --no-install-recommends nginx && rm -rf /var/lib/apt/lists/*
 ---> Running in 4f9a1c2b3d10
 ---> 5d1e8a7c9f21
Step 3/5 : COPY index.html /var/www/html/index.html
 ---> a2f7b3c8e9d0
Step 4/5 : EXPOSE 80
 ---> 6c4d9e1a7b52
Step 5/5 : CMD ["nginx" "-g" "daemon off;"]
 ---> 9a1b7f3c5e28
Successfully built 9a1b7f3c5e28

Baris ---> berisi ID layer hasil tiap langkah. Build kedua yang tidak mengubah Dockerfile memakai ulang seluruh ID itu dalam sekejap — inilah cache berlapis yang membuat iterasi build cepat: ubah hanya index.html, maka RUN yang mahal tidak diulang.

.dockerignore: Menyaring Build Context #

Sebelum build dimulai, klien mengirim seluruh isi folder proyek kepada daemon sebagai build context. Tanpa penyaring, berkas rahasia dan sampah ikut terkirim — dan bila ter-COPY, ikut tertanam di image. Berkas .dockerignore di akar context mendaftarkan pola yang dikecualikan. Kita buat proyek kecil lengkap dengan barang-barang yang seharusnya tidak ikut:

bash
mkdir -p .git
printf 'print("halo")\n' > app.py
printf 'CONTOH_PASSWORD_KUAT\n' > .env
printf 'log lama\n' > debug.log
printf 'kunci-rahasia\n' > kunci-privat.pem
printf 'ref: utama\n' > .git/HEAD
cat > .dockerignore <<'EOF'
.git
*.log
.env
kunci-privat.pem
EOF
echo "== seluruh isi folder proyek =="
find . -type f | sort
echo "== yang dikirim sebagai build context =="
for berkas in $(find . -type f | sort); do
  case "$berkas" in
    ./.git/*|./.env|./.dockerignore|*.log|./kunci-privat.pem) : ;;
    *) echo "$berkas" ;;
  esac
done

Output yang Dihasilkan:

text
== seluruh isi folder proyek ==
./.dockerignore
./.env
./.git/HEAD
./app.py
./debug.log
./kunci-privat.pem
== yang dikirim sebagai build context ==
./app.py

Mari kita bedah baris demi baris:

  1. Baris 1–6 (mkdir dan printf): menyiapkan proyek mini berisi enam berkas — termasuk .env berisi password dan kunci-privat.pem yang tidak boleh pernah keluar dari laptop.
  2. Baris 7–12 (cat > .dockerignore): menulis daftar pengecualian — satu pola per baris, format yang sama dengan .gitignore yang kita kenal di proyek git.
  3. Baris 14–15 (find . -type f | sort): mendaftar seluruh isi folder sebagaimana dilihat mata telanjang — semuanya ada.
  4. Baris 16–21 (case): mensimulasikan penyaringan daemon: pola ./.git/* meniru entri .git, *.log meniru *.log, dan seterusnya — hanya ./app.py yang lolos.

Tanpa .dockerignore, COPY . . di Dockerfile akan menyematkan password dan kunci privat ke dalam image yang mungkin didorong ke registry publik — kesalahan yang sudah menjadi legenda di dunia nyata. Daftar ini juga menjaga context tetap kecil sehingga build jauh lebih cepat.

docker run dan Flag Penting #

docker run image memulai container baru; kekuatan sesungguhnya ada di flag-nya. Flag yang paling sering dipakai:

FlagPeran
-dmenjalankan container di background (detach)
-p 8080:80menerbitkan port — kiri untuk host, kanan untuk container
-v web-data:/var/www/htmlmengaitkan volume bernama web-data
--name webmemberi nama agar mudah dipanggil perintah lain
--rmmembuang container secara otomatis saat berhenti
-e POSTGRES_PASSWORD=CONTOH_PASSWORD_KUATmenyetel environment variable di container
-itinteractive dengan terminal — untuk sesi shell di dalam

Maka menjalankan server web lengkap dengan nama, port, dan volume berbunyi docker run -d --name web -p 8080:80 -v web-data:/var/www/html nginx:1.27. Mekanisme penerbitan port dan jaringan antar-container dibedah tuntas di Chapter Container Networking .

Siklus Hidup Container #

Container punya siklus kehidupan yang pendek: dibuat, menyala, berhenti, dan dihapus. Daemon Docker mencatat statusnya di database internal; untuk melihat mekanismenya tanpa daemon, kita tiru catatan status itu dengan berkas — satu berkas mewakili satu container:

bash
mkdir -p containers
printf 'image:nginx:1.27\nstatus:running\n' > containers/web
printf 'image:postgres:16\nstatus:running\n' > containers/db
echo "== docker ps =="
printf '%-8s %-16s %s\n' NAMA IMAGE STATUS
for f in containers/*; do
  printf '%-8s %-16s %s\n' "$(basename "$f")" \
    "$(sed -n 's/^image://p' "$f")" "$(sed -n 's/^status://p' "$f")"
done
echo "== docker stop db =="
sed -i 's/^status:running/status:exited/' containers/db
for f in containers/*; do
  [ "$(sed -n 's/^status://p' "$f")" = running ] || continue
  printf '%-8s %-16s %s\n' "$(basename "$f")" \
    "$(sed -n 's/^image://p' "$f")" "$(sed -n 's/^status://p' "$f")"
done
echo "== docker rm web, lalu docker ps -a =="
rm containers/web
ls containers

Output yang Dihasilkan:

text
== docker ps ==
NAMA     IMAGE            STATUS
db       postgres:16      running
web      nginx:1.27       running
== docker stop db ==
web      nginx:1.27       running
== docker rm web, lalu docker ps -a ==
db

Mari kita bedah baris demi baris:

  1. Baris 1–3 (mkdir dan printf): dua container “menyala” — daemon sungguhan mencatatnya sebagai dua proses; kita cukup mencatat nama image dan statusnya.
  2. Baris 4–9 (docker ps): mendaftar yang berstatus running — dengan kolom NAMA, IMAGE, dan STATUS seperti docker ps sungguhan.
  3. Baris 10–12 (docker stop db): mengubah status db menjadi exited — container masih ada dan muncul di docker ps -a, tetapi prosesnya sudah mati.
  4. Baris 13–17: daftar kedua hanya menampilkan yang running — web sendirian setelah db berhenti.
  5. Baris 18–20 (docker rm web): menghapus web sepenuhnya — hanya db yang tersisa, menunggu dihidupkan lagi dengan docker start db.

Perintah pendampingnya satu keluarga: docker logs web membaca seluruh keluaran proses utama, docker exec -it web bash membuka shell di dalam container yang sedang berjalan, dan docker start db menyalakan kembali container yang berhenti tanpa membuat yang baru.

Volume: Data yang Hidup Lebih Lama #

Semua tulisan di container hidup di lapisan tulis yang dibuang bersama containernya — sempurna untuk stateless, fatal untuk database. Volume memutus kaitan itu: docker volume create web-data membuat penyimpanan bernama yang dikelola daemon di bawah /var/lib/docker/volumes/, dan flag -v web-data:/var/www/html mengaitkannya ke path di dalam container. Menghapus container tidak menyentuh volume; container baru yang memakai volume sama menemukan datanya utuh. Varian bind mount (-v /home/budi/situs:/usr/share/nginx/html) mengaitkan folder host apa adanya — pilihan untuk development, ketika file diedit di host dan langsung terlihat di container. Seluruh peta storage ini dibedah di Chapter Container Storage .

Docker Hub dan Registry #

Sumber image bawaan Docker adalah Docker Hub: docker pull nginx:1.27 mengunduh image resmi nginx beserta layer-nya, otomatis memakai layer yang sudah ada di disk. Untuk berbagi image sendiri, alurnya tiga langkah: docker login, docker tag web:1.4 budi/web:1.4, lalu docker push budi/web:1.4 — dan image itu bisa ditarik di server mana pun dengan docker pull budi/web:1.4.

Satu container per perintah terasa efisien untuk satu aplikasi — sampai aplikasi kita butuh web, database, dan cache sekaligus yang saling terhubung. Mengetik tiga docker run panjang dengan jaringan buatan tangan adalah tanda kita sudah siap untuk alat berikutnya: Docker Compose, satu berkas YAML yang menyusun aplikasi multi-container — topik Chapter Docker Compose .

Referensi lanjut: referensi CLI docker sebagai kamus semua perintah, referensi Dockerfile untuk daftar instruksi lengkap, serta storage dan volume untuk detail jenis mount.