Setelah Docker terpasang dan lolos verifikasi, langkah berikutnya bukan langsung terjun membuat image atau menjalankan container tanpa memahami cara kerjanya. Docker sebenarnya bukan satu aplikasi tunggal, melainkan kumpulan komponen terpisah yang saling berkomunikasi lewat API: ada bagian yang menerima perintah dari kita, ada bagian yang benar-benar mengeksekusi container di level kernel, dan ada tempat penyimpanan image yang bisa diakses dari mesin mana pun. Memahami peta komponen ini sejak awal akan sangat membantu ketika suatu saat proses debugging dibutuhkan, baik oleh Developer yang container-nya tiba-tiba tidak mau start, maupun Sysadmin/DevOps Engineer yang harus menelusuri kenapa daemon di server production berhenti merespons. Bab ini membahas empat fondasi yang akan terus dipakai di seluruh sub-bab lain series ini: arsitektur Docker beserta komponen utamanya, perbedaan mendasar antara image dan container, siklus hidup container dari dibuat sampai dihapus, serta pengenalan Dockerfile sebagai cara mendefinisikan image lewat kode.
3.1 Arsitektur Docker
Docker memakai arsitektur client-server. Ada dua komponen inti yang selalu terlibat setiap kali kita mengetik perintah docker: Docker client yang menerima perintah dari kita lewat terminal, dan Docker daemon (dockerd) yang benar-benar mengerjakan permintaan tersebut, seperti membangun image, menjalankan container, atau mengelola network dan volume. Client dan daemon berkomunikasi lewat REST API, baik lewat Unix socket di mesin yang sama (default di Linux dan macOS), named pipe di Windows, maupun lewat network interface kalau daemon berada di mesin lain.
Karena komunikasinya berbasis REST API, Docker client sebenarnya hanyalah salah satu cara mengakses daemon, bukan satu-satunya. Kita bisa membuktikan ini langsung dengan mengirim request ke Unix socket daemon memakai curl, tanpa melibatkan Docker CLI sama sekali.
curl --unix-socket /var/run/docker.sock http://localhost/versionPerintah di atas mengembalikan informasi versi API dalam format JSON, persis seperti yang ditampilkan docker version, karena keduanya sama-sama memanggil endpoint yang sama di Docker Engine API. Trik ini berguna di lapangan ketika Sysadmin/DevOps Engineer perlu mengintegrasikan monitoring atau automation tool ke Docker tanpa harus memasang Docker CLI penuh di mesin tersebut.
Di balik daemon, ada dua lapisan lagi yang jarang disentuh langsung tapi penting dipahami perannya. containerd adalah container runtime tingkat tinggi yang dipanggil dockerd untuk mengelola siklus hidup container: menarik image, membuat container, memulai, dan menghentikannya. Docker mendonasikan containerd ke Cloud Native Computing Foundation, sehingga runtime yang sama juga dipakai banyak platform container lain di luar Docker. Di bawah containerd, ada runc, yaitu runtime tingkat rendah yang benar-benar membuat namespace dan cgroup di kernel untuk mengisolasi proses container. runc mengikuti spesifikasi Open Container Initiative (OCI), sehingga container yang dijalankan Docker pada dasarnya comply dengan standar terbuka yang sama dipakai runtime lain.
Komponen terakhir dalam arsitektur ini adalah registry, yaitu tempat image disimpan dan didistribusikan. Docker Hub adalah registry publik default yang dipakai Docker client ketika kita menjalankan docker pull atau docker push tanpa menyebutkan registry lain secara eksplisit. Selain Docker Hub, organisasi juga lazim menjalankan private registry sendiri untuk menyimpan image internal yang tidak boleh diakses publik, sesuatu yang umum ditemui di lingkungan kerja Sysadmin/DevOps Engineer yang menangani aplikasi milik perusahaan.
| Komponen | Peran |
|---|---|
| Docker client | Menerima perintah docker dari kita, mengirimkannya ke daemon lewat REST API |
Docker daemon (dockerd) | Mengelola image, container, network, dan volume; menerima request dari client |
containerd | Container runtime tingkat tinggi, mengatur siklus hidup container atas perintah daemon |
runc | Runtime tingkat rendah yang membuat namespace dan cgroup sesuai spesifikasi OCI |
| Registry (mis. Docker Hub) | Menyimpan dan mendistribusikan image lewat docker pull/docker push |
Satu hal yang perlu diperhatikan Sysadmin/DevOps Engineer yang mengelola server multi-tenant: siapa pun yang bisa mengakses socket daemon pada dasarnya punya kendali penuh atas host, karena daemon berjalan dengan privilese tinggi untuk bisa membuat namespace dan memasang mount ke filesystem host. Membatasi akses ke Unix socket daemon, atau tidak mengekspos Docker Engine API lewat network tanpa TLS, adalah langkah keamanan dasar yang wajib diperhatikan sebelum server benar-benar dipakai di production. Bagi Attacker, socket ini adalah target empuk: begitu berhasil mendapat akses ke dalamnya, baik lewat container yang secara sengaja atau tidak sengaja me-mount /var/run/docker.sock ke dalam dirinya sendiri, maupun lewat API yang terekspos ke network tanpa autentikasi, Attacker bisa memerintahkan daemon membuat container baru dengan bind mount ke root filesystem host, yang pada praktiknya setara dengan mendapat akses root penuh ke mesin tersebut.
3.2 Image vs Container
Image adalah template read-only yang berisi seluruh instruksi untuk membuat container: filesystem aplikasi, dependency, environment variable, dan perintah yang dijalankan saat container menyala. Image tersusun dari beberapa layer yang ditumpuk satu sama lain, dan setiap layer bersifat immutable, tidak pernah berubah setelah dibuat. Container adalah instance yang benar-benar berjalan dari sebuah image. Saat container dibuat, Docker menambahkan satu writable layer tipis di atas seluruh layer image tersebut, memakai mekanisme copy-on-write lewat storage driver seperti overlay2. Semua perubahan yang terjadi selama container berjalan, baik itu file baru, modifikasi konfigurasi, atau log aplikasi, tercatat di writable layer ini, bukan mengubah image aslinya.
Analogi yang sering dipakai untuk menjelaskan hubungan ini adalah class dan object dalam pemrograman berorientasi objek. Image setara dengan class, definisi yang menentukan struktur dan perilaku. Container setara dengan object, instance konkret yang dibuat dari class tersebut. Sama seperti satu class bisa membuat banyak object yang independen satu sama lain, satu image bisa dipakai untuk menjalankan banyak container sekaligus, masing-masing dengan writable layer sendiri, tanpa saling mempengaruhi.
Konsekuensi praktis dari arsitektur ini adalah container bersifat disposable. Karena writable layer container terpisah dari image, menghapus container tidak akan mengubah image yang dipakainya, tapi seluruh data yang ditulis container ke writable layer tersebut juga ikut hilang begitu container dihapus, kecuali data itu disimpan lewat volume. Mari buktikan langsung lewat praktik. Jalankan container dari image alpine yang menuliskan sebuah file ke dalam writable layer-nya.
docker run --name demo-layer alpine sh -c "echo 'data container' > /tmp/data.txt"Container di atas langsung berhenti setelah perintah echo selesai dijalankan, tapi container beserta writable layer-nya tetap tersimpan sampai dihapus secara eksplisit. Perintah docker diff menampilkan seluruh perubahan yang terjadi di writable layer dibanding image aslinya.
docker diff demo-layerOutput-nya menunjukkan path /tmp/data.txt ditandai A (added), membuktikan bahwa file tersebut hanya ada di writable layer container, bukan di image alpine itu sendiri. Kalau perintah docker run --name demo-layer ... di atas dicoba dijalankan ulang tanpa menghapus container lamanya, Docker akan menolak dengan error "Conflict. The container name is already in use", karena nama container harus unik di satu host; hapus dulu container yang lama sebelum membuat container baru dengan nama yang sama. Bandingkan dengan perintah berikut untuk melihat image yang tersimpan lokal dan container yang pernah dibuat.
docker image ls
docker container ls -adocker image ls menampilkan image sebagai unit yang statis, sedangkan docker container ls -a menampilkan container sebagai unit yang punya status (running, exited, dan sebagainya). Bersihkan container percobaan ini setelah selesai, karena container yang sudah exited tetap memakan ruang disk selama belum dihapus.
docker rm demo-layerDi lapangan, kesalahpahaman soal image vs container ini sering jadi sumber bug yang membingungkan Developer pemula: mengubah file di dalam container yang sedang berjalan lewat docker exec, lalu bingung kenapa perubahan itu hilang setelah container di-recreate. Perubahan tersebut sebenarnya tidak hilang secara ajaib, melainkan memang tidak pernah tersimpan ke image, hanya ke writable layer container lama yang sudah digantikan container baru.
3.3 Container Lifecycle
Container tidak hanya punya dua status sederhana (jalan atau tidak). Sepanjang hidupnya, sebuah container melewati beberapa nilai Status berbeda yang tercatat dan bisa dicek lewat docker inspect, dan memahami transisi antar status ini penting bagi Sysadmin/DevOps Engineer supaya bisa mengelola container secara tepat, terutama saat menyusun strategi restart atau troubleshooting container yang berperilaku tidak seperti yang diharapkan.
| Status | Penjelasan |
|---|---|
| created | Container sudah dibuat dari image, tapi proses di dalamnya belum berjalan |
| running | Proses utama container sedang berjalan aktif |
| paused | Seluruh proses di dalam container dibekukan sementara, belum dihentikan |
| restarting | Container sedang dinyalakan ulang secara otomatis oleh restart policy setelah proses utamanya berhenti |
| removing | Container sedang dalam proses dihapus lewat docker rm, status transisi yang biasanya berlangsung sangat singkat |
| exited | Proses utama container sudah berhenti, tapi container dan writable layer-nya masih tersimpan |
| dead | Container gagal dibersihkan sepenuhnya oleh daemon; status yang jarang terjadi dan biasanya perlu docker rm -f atau restart daemon untuk membersihkannya |
Ketujuh status di atas adalah nilai resmi yang bisa muncul di field Status. Begitu container benar-benar dihapus lewat docker rm, status removing hanya singgah sesaat, dan setelah itu container sepenuhnya lenyap; tidak ada status "removed" yang bisa dicek lagi karena memang sudah tidak ada lagi container yang tersisa untuk diperiksa. Langkah praktik berikut fokus ke transisi yang paling sering ditemui sehari-hari, yaitu created, running, paused, dan exited.
Mari praktikkan setiap transisi ini satu per satu. Mulai dengan membuat container tanpa langsung menjalankannya, memakai docker create.
docker create --name lifecycle-demo alpine sleep 300Perintah ini hanya menyiapkan container di state created, belum menjalankan apa pun. Cek statusnya.
docker ps -a --filter name=lifecycle-demoKolom STATUS akan menunjukkan "Created". Jalankan container tersebut dengan docker start, lalu cek ulang statusnya.
docker start lifecycle-demo
docker ps --filter name=lifecycle-demoSebagai catatan, docker run yang biasa dipakai sehari-hari sebenarnya adalah gabungan dua perintah sekaligus, docker create lalu docker start, dibungkus jadi satu langkah untuk kemudahan. Selanjutnya, bekukan container yang sedang berjalan memakai docker pause.
docker pause lifecycle-demo
docker ps --filter name=lifecycle-demoKolom STATUS akan menambahkan keterangan "(Paused)" di belakang durasi uptime container, menandakan proses di dalamnya sedang dibekukan. Docker mencapai efek ini lewat freezer cgroup, yang membekukan seluruh proses di dalam container tanpa benar-benar menghentikannya, mirip menekan tombol pause di aplikasi, bukan menutupnya. Kembalikan container ke state running dengan docker unpause, lalu hentikan container tersebut.
docker unpause lifecycle-demo
docker stop lifecycle-demodocker stop mengirim signal SIGTERM ke proses utama container, memberi kesempatan aplikasi mematikan diri secara graceful, misalnya menutup koneksi database atau menyelesaikan request yang sedang diproses. Jika proses belum berhenti setelah masa tunggu berakhir, secara default 10 detik untuk container Linux, Docker akan memaksa mematikannya dengan SIGKILL. Masa tunggu ini bisa diperpanjang lewat flag -t, penting diperhatikan untuk aplikasi yang butuh waktu lebih lama untuk graceful shutdown, misalnya proses yang sedang menyelesaikan transaksi database.
docker stop -t 30 lifecycle-demoJika sebuah container harus dihentikan seketika tanpa peduli graceful shutdown, misalnya proses yang benar-benar hang dan tidak merespons SIGTERM, pakai docker kill yang langsung mengirim SIGKILL tanpa masa tunggu.
docker kill lifecycle-demoTerakhir, hapus container yang sudah tidak dibutuhkan lagi.
docker rm lifecycle-demoContainer yang masih berstatus running tidak bisa langsung dihapus dan akan menampilkan error; hentikan dulu lewat docker stop, atau paksa sekaligus hapus dengan flag -f pada docker rm kalau memang container harus segera dibersihkan. Perlu jadi catatan, docker rm -f melewati proses graceful shutdown sama sekali dan langsung mengirim SIGKILL sebelum menghapus container, jadi cocok untuk situasi darurat tapi berisiko meninggalkan data yang belum sempat ditulis aplikasi ke disk kalau dipakai sembarangan terhadap container yang sedang menangani transaksi penting di production.
Di server production, Sysadmin/DevOps Engineer jarang menghentikan dan menyalakan container secara manual seperti ini. Yang lebih sering dipakai adalah restart policy, aturan yang menentukan apakah Docker daemon harus otomatis menyalakan ulang container ketika container tersebut berhenti. Ada empat pilihan: no (default, tidak pernah restart otomatis), on-failure[:max-retries] (restart hanya jika container berhenti dengan exit code bukan nol, dengan batas jumlah percobaan opsional), always (selalu restart container yang berhenti, termasuk setelah daemon Docker sendiri di-restart), dan unless-stopped (mirip always, tapi tidak ikut menyala lagi setelah daemon di-restart kalau container tadinya dihentikan manual oleh kita).
docker run -d --name web-service --restart unless-stopped nginxunless-stopped adalah pilihan yang paling umum dipakai untuk service jangka panjang di production, karena container otomatis pulih dari crash atau reboot server, tapi tidak akan "ngeyel" menyala lagi kalau memang sengaja dihentikan untuk keperluan maintenance. Restart policy pada container yang sudah berjalan juga bisa diubah tanpa perlu membuat ulang container-nya.
docker update --restart unless-stopped web-serviceSatu pitfall yang sering menjebak Developer yang baru belajar container lifecycle: container akan otomatis berhenti begitu proses utama (PID 1) di dalamnya selesai atau keluar, meskipun ada proses background lain yang masih berjalan di dalamnya. Ini berbeda dari VM yang tetap menyala selama sistem operasinya belum dimatikan. Container pada dasarnya membungkus satu proses utama; kalau proses itu berhenti, container ikut berhenti, apa pun proses lain yang masih ada di dalamnya.
3.4 Dockerfile Basics
Dockerfile adalah file teks berisi serangkaian instruksi yang dipakai Docker untuk membangun image secara otomatis. Setiap baris instruksi di dalamnya pada dasarnya membentuk satu layer baru di image hasil build, dan Docker meng-cache layer tersebut agar build berikutnya bisa berjalan lebih cepat selama instruksi dan file yang terkait belum berubah.
Empat instruksi paling dasar yang hampir selalu muncul di Dockerfile mana pun adalah FROM, RUN, COPY, dan CMD. FROM menentukan base image yang jadi titik awal, dan wajib menjadi instruksi pertama di Dockerfile (kecuali didahului instruksi ARG untuk mendefinisikan argumen build sebelum FROM). RUN menjalankan perintah shell pada saat image sedang dibangun, biasanya dipakai untuk memasang package atau menyiapkan environment. COPY menyalin file dari build context di mesin kita ke dalam filesystem image. CMD menentukan perintah default yang dijalankan ketika container dibuat dari image tersebut, meski perintah ini bisa ditimpa saat docker run dipanggil dengan argumen tambahan.
Mari praktikkan langsung dengan membuat image sederhana. Siapkan direktori kerja baru, lalu buat file hello.sh di dalamnya.
mkdir docker-basics-demo && cd docker-basics-demo#!/bin/sh
echo "Halo dari container Docker!"Simpan isi di atas sebagai hello.sh. Selanjutnya, buat file bernama persis Dockerfile (tanpa ekstensi) di direktori yang sama.
FROM alpine:3.20
COPY hello.sh /hello.sh
RUN chmod +x /hello.sh
CMD ["/hello.sh"]FROM alpine:3.20 memakai Alpine Linux sebagai base image karena ukurannya kecil, pilihan umum untuk contoh dan aplikasi yang tidak butuh banyak dependency sistem. COPY hello.sh /hello.sh menyalin script ke root filesystem image. RUN chmod +x /hello.sh memberi izin eksekusi pada script tersebut selama proses build. CMD ["/hello.sh"] menjalankan script ini secara default setiap kali container dibuat dari image, ditulis dalam bentuk exec form (array JSON) yang direkomendasikan dibanding shell form karena tidak melibatkan shell perantara untuk menjalankan perintahnya.
Bangun image dari Dockerfile ini memakai docker build, beri nama dan tag lewat flag -t.
docker build -t hello-docker:1.0 .Tanda titik di akhir perintah menunjuk ke build context, yaitu direktori yang isinya dikirim ke daemon sebagai bahan build, termasuk semua file yang bisa diakses instruksi COPY atau ADD. Kalau Docker menampilkan error "COPY failed: file not found in build context", penyebab paling umum adalah file hello.sh belum tersimpan di direktori yang sama dengan Dockerfile, atau perintah docker build dijalankan dari direktori yang salah; pastikan direktori kerja saat ini adalah docker-basics-demo sebelum mengulang perintah build. Setelah build selesai, jalankan container dari image yang baru dibuat untuk memverifikasi hasilnya.
docker run hello-docker:1.0Jika pesan "Halo dari container Docker!" muncul di terminal, image sudah berhasil dibangun dan berjalan sesuai instruksi di Dockerfile. Perintah build ini secara default sudah memakai BuildKit, backend build yang sudah stabil dan menjadi default builder di Docker Engine maupun Docker Desktop, ditandai dengan output build yang menampilkan progress tiap layer secara paralel. Kalau suatu saat perlu membandingkan dengan builder lama untuk keperluan troubleshooting, builder tersebut masih bisa diaktifkan lewat environment variable DOCKER_BUILDKIT=0.
Satu kebiasaan yang baik disiapkan sejak Dockerfile pertama dibuat adalah file .dockerignore, yang isinya mirip .gitignore namun berfungsi mengecualikan file atau folder tertentu dari build context. Tanpa file ini, build context bisa membengkak tanpa disadari kalau direktori kerja berisi folder besar seperti node_modules atau .git, memperlambat proses docker build karena seluruh isi folder tersebut ikut dikirim ke daemon meski tidak pernah dipakai instruksi COPY mana pun.
node_modules
.git
*.logEmpat instruksi di atas, FROM, RUN, COPY, dan CMD, sudah cukup untuk membangun image yang benar-benar bisa berjalan, tapi belum mencerminkan seluruh kemampuan Dockerfile. ENTRYPOINT mirip CMD, tapi tidak semudah ditimpa dari luar, sehingga lebih cocok untuk mengunci perintah utama container. ENV mendefinisikan environment variable yang tersedia di dalam container. WORKDIR menentukan direktori kerja default untuk instruksi sesudahnya. EXPOSE mendokumentasikan port yang dipakai aplikasi di dalam container. Teknik multi-stage build memisahkan tahap kompilasi dari tahap runtime dalam satu Dockerfile yang sama, sehingga dependency build seperti compiler tidak ikut terbawa ke image akhir yang benar-benar dijalankan di production.

