Engine & Daemon #
Di balik kesederhanaan docker run dan docker build, ada arsitektur internal yang lebih berlapis dari yang terlihat. Dua istilah yang paling sering muncul — Docker Engine dan Docker Daemon — sering dianggap sama, padahal punya cakupan yang berbeda. Memahami perbedaan ini bukan sekadar pengetahuan akademis; ini menentukan apakah kamu bisa mendiagnosis masalah dengan tepat saat container tidak mau start, mengamankan akses ke Docker di server production, dan memilih strategi deployment yang sesuai dengan lingkunganmu.
Artikel ini akan membedah Docker Engine dan Docker Daemon secara mendalam. Kita akan mulai dari pemetaan arsitektur, lalu masuk ke peran spesifik daemon, anatomi Engine, hubungan dengan containerd dan runc, sampai aspek keamanan yang sering diabaikan. Setelah membaca artikel ini, kamu tidak hanya tahu apa yang berjalan, tapi juga mengapa ia berjalan dan bagaimana mengendalikannya.
Arsitektur Client-Server: Peta Komunikasi #
Docker pada dasarnya adalah aplikasi client-server. Pemahaman ini adalah prasyarat untuk semua pembahasan selanjutnya, jadi mari kita mulai dari sini.
flowchart LR
subgraph CLIENTS["Klien (bisa di mesin berbeda)"]
CLI["docker CLI<br/>(binary)"]
SDK1[Python SDK]
SDK2[Go SDK]
SDK3[Node SDK]
COMP[Docker Compose]
end
subgraph SERVER["Server (host Docker)"]
DAEMON["dockerd<br/>(Docker Daemon)"]
subgraph RUNTIME["Container Runtime"]
CONTAINERD["containerd<br/>(high-level)"]
RUNC["runc<br/>(low-level, OCI)"]
end
end
CLI -->|"REST API<br/>unix:///var/run/docker.sock<br/>atau tcp://host:2375"| DAEMON
SDK1 --> DAEMON
SDK2 --> DAEMON
SDK3 --> DAEMON
COMP --> DAEMON
DAEMON --> CONTAINERD --> RUNC
RUNC -->|"syscall"| KERNEL[Linux Kernel]
Klien adalah apa yang kamu sentuh. docker CLI adalah klien paling umum — binary yang mem-parse perintah, membangun request JSON, dan mengirimkannya lewat HTTP ke daemon. Docker Compose, Docker SDK untuk Python/Go/Node, dan tools pihak ketiga (ctop, dive, lazydocker) juga adalah klien — semuanya berbicara ke daemon lewat REST API yang sama.
Server adalah daemon itu sendiri, yaitu proses dockerd yang berjalan terus-menerus di host. Daemon listen di socket (default: /var/run/docker.sock di Linux), menerima request, dan mengeksekusinya. Daemon tidak pernah menyentuh hardware secara langsung — semua eksekusi container didelegasikan ke runtime.
Pemisahan ini punya konsekuensi penting. Pertama, klien dan daemon bisa berada di mesin yang berbeda. Kamu bisa menjalankan docker di laptop untuk mengontrol daemon di server remote, asalkan socket TCP diaktifkan dan diamankan. Kedua, klien tidak punya logika — semua keputusan terjadi di daemon. Itulah kenapa dua klien berbeda (CLI dan Compose) yang mengirim perintah pada waktu bersamaan tidak akan bentrok: daemon yang serialisasi semua request.
Detail menarik: REST API Docker sepenuhnya terbuka dan terdokumentasi. Semua yang bisa dilakukan CLI bisa dilakukan dengan curl ke socket yang sama. Ini juga yang membuat tools debugging seperti curl --unix-socket /var/run/docker.sock http://localhost/containers/json bekerja — itu adalah request HTTP biasa, bukan sihir.
Docker Daemon (dockerd) — Otak di Balik Layar #
Docker Daemon adalah proses background yang berjalan terus-menerus di host dan bertanggung jawab atas hampir semua yang berhubungan dengan container. Nama prosesnya adalah dockerd.
Tanggung Jawab Utama #
Daemon bukan sekadar “program yang menjalankan container”. Ia mengelola seluruh state Docker di host:
| Area | Tanggung Jawab |
|---|---|
| Container lifecycle | Create, start, stop, restart, kill, pause, unpause, remove |
| Image management | Build, pull, push, tag, remove, import, export, save, load |
| Network management | Membuat bridge/overlay/macvlan network, attach container ke network, port mapping |
| Volume management | Membuat named volume, bind mount, mount ke container, lifecycle management |
| Registry interaction | Autentikasi ke registry, pull/push image, image search |
| API server | Listen di socket, validasi request, dispatch ke handler yang tepat |
| Event system | Memancarkan event untuk setiap aksi (berguna untuk monitoring/audit) |
Setiap kali kamu menjalankan docker ps, daemon yang query state internalnya. Setiap kali docker run, daemon yang orkestrasi image pull, container creation, network setup, dan start. Tanpa daemon, Docker tidak akan bisa melakukan apa-apa — CLI tanpa daemon hanya akan error “Cannot connect to the Docker daemon”.
Lokasi dan Cara Kerja Daemon di Berbagai OS #
Cara daemon dijalankan berbeda di setiap操作系统, dan ini sering jadi sumber kebingungan.
Di Linux, daemon berjalan sebagai systemd service. Ini adalah setup yang paling “murni” karena daemon benar-benar berjalan di host, berbicara langsung dengan kernel Linux, dan tidak ada layer virtualisasi tambahan.
# Cek status daemon
systemctl status docker
# Lihat proses daemon
ps aux | grep dockerd
# Lihat konfigurasi daemon
cat /etc/docker/daemon.json
Di macOS dan Windows, daemon tidak berjalan native di host karena host-nya bukan Linux. Sebagai gantinya, Docker Desktop menjalankan Linux VM ringan (berbasis Apple’s Virtualization framework di Mac, atau Hyper-V/WSL2 di Windows), lalu menjalankan dockerd di dalam VM itu. CLI di sisi host berbicara ke daemon lewat socket yang di-forward.
# Di macOS, socket Docker muncul di
/var/run/docker.sock
# tapi sebenarnya itu forwarded dari VM
Ini menjelaskan beberapa perilaku yang sering membingungkan:
- Performa I/O di Mac/Windows lebih lambat dari Linux karena ada lapis VM. Solusinya: gunakan virtiofs/WSL2 backend, atau named volume (yang berada di VM, bukan di host filesystem).
- Path binding di Mac/Windows perlu path Mac/Windows, bukan path Linux. Docker Desktop menerjemahkannya.
- Resource limit (
--memory,--cpus) di Mac/Windows membatasi resource VM, bukan host — karena container benar-benar berjalan di VM.
Cara mengingat: setiap container Docker selalu berjalan di kernel Linux. Tidak peduli apakah hostmu adalah Mac, Windows, atau Linux — kernel yang mengeksekusi proses container selalu Linux. Inilah kenapa image Linux bisa dijalankan di mana-mana, dan kenapa image Windows hanya bisa dijalankan di host Windows.
Docker Engine — Platform Lengkap #
Docker Engine adalah istilah yang lebih luas dari dockerd. Ia adalah platform lengkap untuk membangun dan menjalankan container, dan daemon hanyalah salah satu komponen di dalamnya.
Anatomi Docker Engine #
flowchart TB
subgraph ENGINE["Docker Engine"]
subgraph INTERFACE["Interface Layer"]
CLI["docker CLI<br/>(perintah user)"]
API["REST API<br/>(endpoint HTTP)"]
end
subgraph CORE["Core Layer"]
DAEMON["dockerd<br/>(state management)"]
end
subgraph RUNTIME["Runtime Layer"]
CONTAINERD["containerd<br/>(image & container lifecycle)"]
RUNC["runc<br/>(OCI runtime)"]
end
end
CLI --> API
API --> DAEMON
DAEMON --> CONTAINERD
CONTAINERD --> RUNC
RUNC -->|"syscall"| KERNEL[Linux Kernel]
Empat komponen yang membentuk Docker Engine:
- docker CLI — binary yang kamu panggil di terminal.
- REST API — protokol komunikasi antara klien dan daemon.
- dockerd — daemon yang mengelola state dan mendelegasikan eksekusi.
- Container Runtime —
containerd+runcyang benar-benar membuat dan menjalankan container.
Docker Engine vs Docker Daemon — Apa Bedanya? #
| Aspek | Docker Daemon | Docker Engine |
|---|---|---|
| Bentuk | Proses tunggal (dockerd) |
Platform / paket software |
| Cakupan | Hanya proses daemon | CLI + API + Daemon + Runtime |
| Bisa di-install terpisah? | Tidak masuk akal | Ya (misalnya containerd standalone) |
| Termasuk CLI | ❌ | ✅ |
| Termasuk runtime | ❌ (mendelegasikan) | ✅ |
Cara mengingatnya: Docker Daemon adalah bagian dari Docker Engine, bukan sebaliknya. Docker Engine adalah konsep produk; Docker Daemon adalah salah satu implementasinya.
containerd dan runc — Lapis Eksekusi #
Ini bagian yang paling sering membingungkan pemula Docker: kalau daemon yang mengatur segalanya, lalu apa yang dilakukan containerd dan runc? Kenapa tidak langsung saja daemon yang menjalankan container?
Sejarah Singkat: Mengapa Dipisah #
Sebelum Docker 1.11 (rilis April 2016), semua logika runtime — mulai dari image extraction sampai namespace creation — ada di dalam dockerd. Ini membuat daemon besar, monolitik, dan rapuh: bug di runtime bisa membuat daemon crash, dan daemon tidak bisa di-restart dengan elegan karena akan membunuh semua container yang sedang berjalan.
Solusinya: refactor menjadi tiga lapis.
flowchart LR
A["Docker CLI"] -->|"REST API"| B["dockerd<br/>(daemon)"]
B -->|"gRPC"| C["containerd<br/>(supervisor)"]
C -->|"fork+exec"| D["runc<br/>(OCI runtime)"]
D -->|"syscall"| E[Linux Kernel]
style B fill:#e1f5ff
style C fill:#fff4e1
style D fill:#ffe1e1
containerd adalah supervisor untuk container. Ia mengelola:
- Image pull dan ekstraksi
- Storage management (snapshot, layer)
- Container lifecycle (low-level, bukan hanya metadata)
- Network attachment (bekerja sama dengan CNI plugin)
- Volume attachment
containerd berjalan sebagai proses terpisah dari dockerd. Keduanya berkomunikasi lewat gRPC di Unix socket internal. Jika dockerd di-restart, containerd terus berjalan dan container yang sudah jalan tidak terpengaruh.
runc adalah low-level runtime yang mengikuti standar OCI (Open Container Initiative). Ia benar-benar membuat proses container dengan memanggil system call clone() dengan flag namespace, mengatur cgroup, lalu execve() binary utama container. runc melakukan satu hal dengan sangat baik: mengubah container specification (OCI bundle) menjadi proses yang berjalan.
Detail menarik: runc adalah implementasi referensi untuk OCI Runtime Specification. Proyek lain yang compliant dengan standar yang sama termasuk crun (ditulis dalam C, lebih ringan dari runc yang ditulis dalam Go) dan youki (ditulis dalam Rust). K8s biasanya menggunakan crun di CRuntimeClass tertentu untuk optimasi startup.
Alur Lengkap: Dari CLI sampai Container Hidup #
Mari kita lihat apa yang terjadi ketika kamu menjalankan docker run -d nginx:
sequenceDiagram
participant CLI as docker CLI
participant D as dockerd
participant CD as containerd
participant R as runc
participant K as Kernel
CLI->>D: POST /containers/create<br/>(name, image, config)
D->>D: validasi, generate ID
D->>D: setup network, volume
D->>CD: CreateContainer (gRPC)
CD->>CD: image pull (jika perlu)
CD->>CD: snapshot writable layer
CD->>CD: setup cgroup, namespace spec
CD->>R: fork+exec runc
R->>K: clone(CLONE_NEWNS|NEWPID|NEWNET|...)
K-->>R: new namespace
R->>K: execve(/nginx)
K-->>R: nginx process started
R-->>CD: container started
CD-->>D: success
D-->>CLI: container ID + status
Perhatikan bahwa dockerd dan containerd berkomunikasi via gRPC (bukan HTTP), dan runc dipanggil via fork+exec (proses baru). Ini adalah isolasi proses yang nyata — jika runc crash, hanya container yang terpengaruh; daemon dan containerd tetap sehat.
REST API — Bahasa yang Dipahami Semua Klien #
Docker REST API adalah fondasi yang membuat ekosistem Docker bisa diperluas. Setiap endpoint, setiap field dalam request, didokumentasi secara terbuka di docs.docker.com.
Endpoint yang Paling Sering Digunakan #
| Endpoint | Method | Fungsi |
|---|---|---|
/containers/json |
GET | List container (sama dengan docker ps) |
/containers/create |
POST | Buat container baru |
/containers/{id}/start |
POST | Start container |
/containers/{id}/stop |
POST | Stop container |
/containers/{id}/logs |
GET | Ambil log container |
/images/json |
GET | List image (sama dengan docker images) |
/images/create |
POST | Pull image dari registry |
/networks/create |
POST | Buat network |
/volumes/create |
POST | Buat volume |
Cara Mengakses API Langsung #
Karena API ini adalah HTTP biasa, kamu bisa memanggilnya dengan curl (atau library HTTP apa pun) langsung ke Unix socket daemon.
# List semua container yang sedang berjalan
curl --unix-socket /var/run/docker.sock \
http://localhost/containers/json
# Lihat event real-time
curl --unix-socket /var/run/docker.sock \
http://localhost/events?stream=true
Perhatikan bahwa hostname di URL (http://localhost/) sebenarnya diabaikan ketika kamu menggunakan --unix-socket — yang penting adalah path Unix-nya. Ini adalah fitur curl, bukan Docker.
Mengakses Daemon dari Mesin Lain #
Untuk mengontrol daemon dari mesin lain, kamu perlu mengaktifkan listen di TCP socket di sisi server dan menambahkan otentikasi.
// /etc/docker/daemon.json di server
{
"hosts": ["tcp://0.0.0.0:2376"],
"tlsverify": true,
"tlscacert": "/etc/docker/ca.pem",
"tlscert": "/etc/docker/server-cert.pem",
"tlskey": "/etc/docker/server-key.pem"
}
Di sisi klien:
docker -H tcp://server.example.com:2376 ps
# atau
docker context create remote --docker "host=tcp://server.example.com:2376,ca=...,cert=...,key=..."
docker context use remote
docker ps
Jangan pernah expose port Docker (2375/2376) tanpa TLS ke internet publik. Port ini memberikan akses setara root ke host. Banyak bot di internet secara aktif扫描 port 2375 untuk mengeksploitasi host Docker yang tidak diamankan. Jika harus expose, gunakan TLS mutual, firewall, dan VPN.
Keamanan Docker Daemon — Attack Surface yang Kritis #
Akses ke Docker daemon setara dengan akses root ke host. Ini bukan hiperbolis — dengan akses ke dockerd, attacker bisa:
- Menjalankan container dengan mode
--privilegedyang memberikan akses hampir tak terbatas ke host. - Mount filesystem host (
-v /:/host) ke dalam container, lalu membaca/mengubah file apa pun. - Install software baru, membuat user baru, atau bahkan boot ke sistem lain.
- Mem-bypass sebagian besar kontrol keamanan tradisional.
Prinsip Keamanan Wajib #
WAJIB dilakukan:
✓ Batasi akses ke /var/run/docker.sock — hanya user
dalam group `docker` yang boleh mengakses.
✓ Jika expose via TCP, gunakan TLS mutual (mTLS).
✓ Audit konfigurasi daemon secara berkala
(/etc/docker/daemon.json).
✓ Aktifkan user namespace remap untuk membatasi
UID container ke range non-root di host.
✓ Pertimbangkan rootless mode untuk environment
yang membutuhkan privilege minimum.
JANGAN dilakukan:
✗ Expose port 2375/2376 tanpa TLS ke network publik.
✗ Jalankan container dengan --privileged kecuali
benar-benar diperlukan.
✗ Mount / atau /etc dari host ke container.
✗ Tambahkan sembarang user ke group `docker` —
ini setara dengan memberikan root shell.
Audit Konfigurasi Daemon #
File /etc/docker/daemon.json adalah konfigurasi sentral untuk daemon. Beberapa setting yang patut diperhatikan:
{
"log-level": "info",
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 64000,
"Soft": 64000
}
},
"storage-driver": "overlay2",
"features": {
"containerd-snapshotter": true
}
}
no-new-privileges: true adalah setting keamanan penting: mencegah container mendapatkan privilege baru lewat setuid binary. live-restore: true memungkinkan container tetap berjalan saat daemon di-restart — sangat berguna untuk maintenance. containerd-snapshotter: true mengaktifkan snapshotter berbasis containerd yang lebih cepat dan hemat storage.
Rootless Mode — Docker Tanpa Root #
Salah satu terobosan keamanan Docker dalam beberapa tahun terakhir adalah rootless mode. Dalam mode ini, daemon dan container berjalan sebagai user biasa tanpa privilege root, dengan bantuan user namespace kernel.
flowchart TB
subgraph TRAD["Tradisional (root daemon)"]
ROOT1["dockerd (root)"] --> K1[Kernel]
K1 --> HOST1[Host filesystem]
end
subgraph ROOTLESS["Rootless Mode"]
USER["user (UID 1000)"]
DAEMON_R["dockerd (rootless)"]
USER --> DAEMON_R
DAEMON_R -->|"unshare(NEWUSER)"| K2[Kernel]
K2 --> HOST2[Host filesystem]
end
Kelebihan Rootless #
- Mengurangi blast radius serangan. Jika ada container escape, attacker hanya mendapat akses sebagai user biasa, bukan root.
- Bisa dijalankan di shared host tanpa khawatir satu container compromised akan mengambil alih seluruh mesin.
- Setup CI/CD lebih aman karena runner tidak butuh akses root.
Keterbatasan Rootless #
- Port < 1024 tidak bisa di-bind langsung (perlu setup tambahan).
- Beberapa fitur seperti
--privileged, beberapa network driver, dan certain volume mounts tidak bekerja. - Cgroup v1 terbatas; lebih baik di cgroup v2 dengan kernel modern.
- Performance sedikit lebih rendah karena ada overhead user namespace mapping.
Rootless mode cocok untuk development, CI/CD runner, dan shared environment di mana keamanan lebih penting dari fitur lengkap. Untuk production server dedicated, mode tradisional dengan hardening yang baik masih lebih umum.
Kapan mempertimbangkan rootless: jika tim kamu menjalankan Docker di laptop yang juga dipakai untuk hal lain, atau di CI runner bersama, rootless memberikan lapisan keamanan ekstra yang signifikan dengan biaya setup yang rendah. Docker otomatis mendukung rootless dengan sub-command dockerd-rootless-setuptool.sh yang ada di paket docker-ce-rootless-extras.
Decision Tree — Mode Operasi Docker untuk Kebutuhanmu #
Tidak semua setup Docker membutuhkan konfigurasi yang sama. Gunakan decision tree ini untuk memilih konfigurasi yang sesuai dengan kebutuhanmu.
flowchart TD
A{Environment<br/>Docker?}
A -- Laptop dev --> B{Butuh<br/>port < 1024?}
B -- Ya --> C[Rootless<br/>+ port forwarding]
B -- Tidak --> D[Rootless default]
A -- Server produksi --> E{Host dedicated<br/>atau shared?}
E -- Dedicated --> F[Root daemon<br/>+ hardening]
E -- Shared --> G[Rootless<br/>atau unshare per user]
A -- CI/CD runner --> H[Rootless<br/>atau container-in-container]
F --> FI{Akses<br/>remote?}
FI -- Ya --> J[TLS mTLS<br/>+ firewall]
FI -- Tidak --> K[Unix socket<br/>+ group docker]
Pemahaman tentang Engine dan Daemon bukan akhir dari segalanya — ini fondasi untuk semua konfigurasi di atas. Ketika kamu tahu mengapa setiap setting ada, kamu bisa membuat keputusan yang tepat untuk kasusmu.
Ringkasan #
- Docker Engine = platform lengkap (CLI + API + daemon + runtime). Docker Daemon = proses
dockerdsaja. Kesalahan mengidentifikasi ini sering membuat konfigurasi security salah sasaran.- Arsitektur client-server Docker menjelaskan hampir semua perilaku yang kamu lihat: kenapa CLI bisa di mesin lain, kenapa tools pihak ketiga bisa inspeksi container, dan kenapa remote management bekerja.
- Container runtime modern terdiri dari
containerd(supervisor) +runc(low-level OCI). Docker Daemon mendelegasikan eksekusi ke mereka; ia tidak menjalankan container sendiri lagi. Pemisahan ini memberi stabilitas dan standar industri.- REST API Docker adalah HTTP biasa di Unix socket. Semua klien (CLI, Compose, SDK) berbicara ke endpoint yang sama. Ini yang membuat ekosistem Docker bisa diperluas dengan mudah.
- Akses ke Docker Daemon = akses root ke host — ini adalah prinsip keamanan yang tidak bisa dikompromikan. Selalu lindungi socket dengan permission, dan jika expose via TCP, gunakan TLS mutual.
- Rootless mode adalah pilihan tepat untuk development, CI runner, dan shared environment. Ia memberikan privilege minimum dengan biaya fitur yang terbatas.
- Konfigurasi daemon via
/etc/docker/daemon.jsonadalah cara terpusat untuk mengaktifkan fitur keamanan sepertino-new-privileges,live-restore, dan user namespace remap.