Environment variable menjawab bagaimana aplikasi dikonfigurasi, tetapi masih menyisakan pertanyaan identitas: sebagai user siapa proses container bekerja? Jawaban bawaan Docker mengejutkan mereka yang datang dari dunia server tradisional — hampir semua container berjalan sebagai root, user nomor 0 dengan seluruh hak administratif, kecuali image-nya sendiri menurunkan identitas lewat instruksi USER. Kebiasaan ini adalah warisan pola Dockerfile: sebagian besar pekerjaan build memang menuntut root, dan tanpa satu baris USER, identitas itu terbawa sampai container produksi menyala. Budi menemukan kenyataan tersebut saat mengaudit container myapp versi lama milik timnya, dan chapter ini membedah mengapa default-nya root, bagaimana flag --user menimpanya saat runtime, serta konsekuensi izin berkas dan port yang mengikuti angka UID tersebut.
Default root #
Mari kita periksa identitas container myapp versi pertama — versi yang dibangun sebelum instruksi USER masuk ke Dockerfile:
$ docker run --rm myapp:1.0 whoamiOutput yang Dihasilkan:
root$ docker run --rm myapp:1.0 idOutput yang Dihasilkan:
uid=0(root) gid=0(root) groups=0(root)Sebagai pembanding, myapp versi 1.4 — image yang sudah memuat USER app sejak Chapter WORKDIR dan USER — menjawab berbeda:
$ docker run --rm myapp:1.4 whoamiOutput yang Dihasilkan:
appMari kita telaah tiga jawaban identitas di atas:
rootpada myapp:1.0 — Dockerfile versi itu tidak memuat instruksi USER, sehingga seluruh proses container berjalan sebagai user nomor 0; ini default Docker, bukan keputusan yang diambil Budi secara sadar.apppada myapp:1.4 — instruksiUSER apppada Dockerfile menurunkan identitas sejak build; image yang sama kini menjalankan aplikasi sebagai user biasa ber-UID 1000.docker run --rm image whoamiadalah pola command override — Chapter Command Override memberi kita alat untuk memeriksa identitas tanpa memasang shell interaktif; perintahidmemperlihatkan angka-angka di balik nama.
Mengapa root menjadi masalah? Bila aplikasi di dalam container berhasil ditembus penyerang, penyerang tersebut mendarat sebagai root di dalam namespace container — bisa menulis ke seluruh filesystem container, menunggu celah kernel, dan bila konfigurasi host lenggang, mencoba melompat keluar. Namespace dan capabilities memang memagari container, tetapi pagar yang dipeluk dari dalam tetap lebih berbahaya daripada tidak ada orang di sana sama sekali; pembahasan menyeluruhnya menanti Section Keamanan Docker, dibuka oleh Chapter Prinsip Keamanan Container. Prinsip praktisnya satu kalimat: proses seharusnya berjalan dengan hak akses paling sedikit yang masih memungkinkan pekerjaannya selesai.
Flag –user untuk Menimpa Identitas #
Flag --user (bentuk pendek -u) menimpa identitas proses utama container saat runtime — tanpa menyentuh image sama sekali. Nilainya boleh berbentuk UID, pasangan uid:gid, nama user, atau pasangan nama dan grup. Kita coba pasangan angka pada image lama:
$ docker run --rm --user 1000:1000 myapp:1.0 idOutput yang Dihasilkan:
uid=1000(node) gid=1000(node) groups=1000(node)$ docker run --rm --user 1000:1000 myapp:1.0 whoamiOutput yang Dihasilkan:
nodeSekarang kita pakai UID yang tidak dimiliki siapa pun di dalam image:
$ docker run --rm --user 2000:2000 myapp:1.0 whoamiOutput yang Dihasilkan:
whoami: unknown uid 2000$ docker run --rm --user 2000:2000 myapp:1.0 idOutput yang Dihasilkan:
uid=2000 gid=2000Mari kita telaah empat jawaban yang tampak saling bertentangan itu:
- Kernel hanya mengenal angka — identitas proses bagi kernel adalah UID dan gid numerik; nama hanyalah label manusiawi yang diterjemahkan lewat berkas
/etc/passwddi dalam container. whoamiberhasil mencari nama untuk UID 1000 — imagenode:22-alpinekebetulan membawa user bernamanodedengan UID 1000, sehingga terjemahan nama ditemukan; kebetulan ini sekaligus menjelaskan mengapa angka 1000 populer sebagai UID aplikasi.whoamimenyerah untuk UID 2000 — tidak ada entri/etc/passwduntuk 2000, sehingga tool menolak menerjemahkan;whoamikeluar dengan exit code 1, keluarga galat yang sudah kita latih membacanya pada Chapter Memahami Exit Code.idtetap jujur menampilkan angka telanjang — proses memang berjalan sebagai UID 2000 tanpa masalah; kegagalan hanya ada pada penerjemahan nama. Flag--userkarenanya tidak mensyaratkan user tersebut terdaftar di dalam image — inilah alasan menulis angka lebih deterministik daripada nama, kebiasaan yang kita sepakati sejak Chapter WORKDIR dan USER.
Konsekuensi terhadap Izin Berkas #
Identitas baru membawa aturan izin berkas ke mana-mana: berkas yang dimiliki root tidak lagi bisa ditulis, dan volume yang dibuat Docker dimiliki root secara bawaan. Kita buktikan dengan container alpine yang mencoba menulis ke volume segar:
$ docker run --rm -v /data --user 1000:1000 alpine:3.20 touch /data/berkas.txtOutput yang Dihasilkan:
touch: /data/berkas.txt: Permission deniedMari kita telaah kegagalan yang sangat lazim itu:
- Volume anonim diciptakan dan dimiliki root:root — daemon membuat volume baru dengan kepemilikan penuh milik root; proses UID 1000 tidak punya hak menulis ke dalamnya.
Permission denieddengan exit code 1 — kernel yang menolak operasi tulis, bukan Docker; angka UID yang tidak cocok adalah satu-satunya alasannya.- Sumber masalah yang sama berlaku untuk bind mount — direktori milik UID host tertentu hanya bisa ditulis oleh UID yang sama dari sisi container; konflik angka antara user host, image, dan volume adalah salah satu kisah paling klasik di dunia penyimpanan Docker, dan kita bedah tuntas nanti pada Chapter Permission dan Ownership Volume.
Port Rendah dan NET_BIND_SERVICE #
Identitas juga berurusan dengan port. Kernel Linux melarang proses non-root mengikat port di bawah 1024 kecuali proses itu memegang capability NET_BIND_SERVICE. Container Docker bawaan justru menyertakan capability tersebut dalam daftar 12 capabilities default-nya — daftar yang kita bedah nanti pada Chapter Linux Capabilities — sehingga container non-root di Docker masih bisa mendengarkan port 80. Namun mengandalkan keberuntungan capabilities bukan kebiasaan yang sehat, dan pola yang jauh lebih elegan sudah kita pegang sejak Section Dasar Docker CLI:
Dockerfile USER versus –user Runtime #
Dua cara menetapkan user — instruksi di Dockerfile dan flag saat docker run — bekerja pada waktu yang berbeda dan tidak saling menghapus. Tabel berikut merangkum hubungan keduanya:
| Aspek | USER di Dockerfile | –user saat docker run |
|---|---|---|
| Menetapkan saat | build — menempel permanen di image | create — hanya untuk container itu |
| Nilai bawaan | root bila instruksi tidak ditulis | tanpa –user, mengikuti USER image |
| Bentuk nilai | nama user atau angka UID | uid, uid:gid, nama, atau nama:grup |
| Kapan menang | hanya bila runtime tidak menyebut –user | –user selalu menimpa USER image |
| Menyentuh image? | ya — image hasil build berubah | tidak sama sekali |
| Dipakai oleh | setiap container dari image itu | proses utama container yang bersangkutan |
Catatan tambahan untuk docker exec: perintah itu punya flag -u sendiri, sehingga proses tambahan bisa berjalan sebagai identitas lain — misalnya masuk sebagai root ke container yang aplikasinya non-root untuk perbaikan darurat, tanpa mengubah identitas proses utamanya.
Image Resmi yang Menurunkan Identitas Sendiri #
Kebanyakan image resmi besar tidak menunggu kita menurunkan identitas — mereka melakukannya sendiri dengan pola dua tingkat: proses persiapan berjalan sebagai root, proses pemakaian jalan sebagai user rendah. Container web nginx yang lama kita kenal memperagakannya secara hidup. Budi menjalankan ulang container kebiasaan Section Dasar Docker CLI:
$ docker rm -f web
$ docker run -d --name web -p 8080:80 nginxOutput yang Dihasilkan:
web
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718aPerintah docker top membedah proses container dari sisi host — tanpa membutuhkan tool ps di dalam image, berbeda dari docker exec yang menuntut biner tersedia di filesystem container:
$ docker top webOutput yang Dihasilkan:
UID PID PPID C STIME TTY TIME CMD
root 4217 4185 0 09:14 ? 00:00:00 nginx: master process nginx -g daemon off;
101 4218 4217 0 09:14 ? 00:00:00 nginx: worker process
101 4219 4217 0 09:14 ? 00:00:00 nginx: worker processMari kita telaah tiga baris proses di atas:
- Master berjalan sebagai
root— proses induk nginx membaca konfigurasi, membuka port 80 di dalam container, lalu menurunkan identitas anak-anaknya; entrypoint image menjalankannginx -g daemon off;persis seperti yang terbaca pada kolom CMD. - Worker berjalan sebagai UID 101 — angka itu milik user
nginxyang dibuat oleh image nginx; penurunan dilakukan lewat directiveuser nginx;pada nginx.conf, bukan lewat instruksi USER Dockerfile. - Kolom UID diisi angka, bukan nama —
docker topmenjalankanpsmilik host danpsmenampilkan UID numerik tanpa menerjemahkannya ke nama user host; angka 101 milik usernginxdi dalam container, sedangkan di sisi host angka yang sama bisa saja tidak dimiliki siapa pun — persis situasiunknown uidpada demo--user 2000:2000tadi.
Pola serupa berlaku luas di keluarga image resmi: postgres menurunkan dirinya ke user postgres dengan UID 999 lewat entrypoint-nya, redis beralih ke user redis setelah entrypoint memperbaiki kepemilikan direktori datanya. Karena itu sebelum menambahkan --user pada image resmi, periksa dulu dokumentasinya — menimpa identitas yang sudah diturunkan dengan angka yang salah justru menciptakan masalah izin berkas baru, persis skenario Permission denied yang kita buktikan di atas.
Identitas proses kini berada dalam kendali kita, dan tip di tengah chapter sempat menyebut satu flag yang bekerja sama erat dengannya: -p 80:8080. Flag tersebut terlihat seperti trik kecil untuk memindahkan nomor port, padahal formatnya menyimpan empat segmen — alamat host, port host, port container, dan protokol — yang masing-masing punya aturan main sendiri. Chapter Port Publishing membedah format lengkap itu, dari pengikatan ke 127.0.0.1 sampai rentang port dan konflik yang paling sering menyapa pemula.
Referensi #
- docker container run — CLI Reference
— flag
--userbeserta bentuk uid, gid, dan nama. - docker container top — CLI Reference — membaca proses container dari sisi host lengkap dengan kolom UID.
- Dockerfile Reference — instruksi USER dan hubungannya dengan user runtime container.