Perintah container kini lengkap kita pegang: CMD memberi nilai bawaan yang mudah ditimpa argumen docker run, ENTRYPOINT menambatkan perintah utama container, dan exec form memastikan sinyal docker stop sampai langsung ke proses node tanpa perantara shell. Dengan bekal itu, Dockerfile myapp sudah tahu cara menyala dengan benar. Namun satu baris kecil di dalamnya masih menyimpan miskonsepsi yang paling sering menjebak pemula Docker: EXPOSE 8000. Budi sempat yakin baris itulah yang membuka aplikasi ke dunia luar — sampai ia menyadari container pertamanya bisa diakses justru karena flag -p 8000:8000 pada docker run, bukan karena EXPOSE. Chapter ini membedah instruksi tersebut sampai ke akarnya: apa yang benar-benar dilakukan EXPOSE, di mana jejaknya tersimpan pada metadata image, dan bagaimana penerbitan port yang sesungguhnya bekerja saat runtime.
EXPOSE Adalah Dokumentasi Bukan Pintu Terbuka #
Instruksi EXPOSE hanya punya satu tugas: menuliskan pada konfigurasi image bahwa aplikasi di dalamnya mendengarkan port tertentu. Baris EXPOSE 8000 tidak membuka socket, tidak mengatur firewall, dan tidak membuat port apa pun dapat dihubungi dari luar container — semua itu bukan wilayah kerjanya. EXPOSE adalah metadata murni, satu keluarga dengan LABEL dan ENV: instruksi tanpa isi layer yang hanya menulis catatan pada konfigurasi image, persis pembagian yang kita pelajari pada Chapter Struktur Dockerfile. Anggaplah EXPOSE sebagai stiker pada kotak kardus bertuliskan “aplikasi ini mendengarkan port 8000”: siapa pun yang memegang image itu tahu port mana yang perlu diterbitkan, tetapi stiker tidak pernah menghasilkan kabel yang benar-benar tersambung.
Membaca Jejak EXPOSE pada Konfigurasi Image #
Karena murni metadata, jejak EXPOSE dapat kita baca langsung dari image hasil build, memakai docker image inspect dengan flag --format yang sudah akrab dari Chapter Image Inspect dan History:
$ docker image inspect myapp:1.1 --format '{{.Config.ExposedPorts}}'Output yang Dihasilkan:
map[8000/tcp:{}]Mari kita telaah output satu baris di atas:
.Config.ExposedPorts— field konfigurasi image tempat seluruh deklarasi EXPOSE disimpan; nilainya berupa map dengan satu kunci per port.8000/tcp— pasangan port dan protokol; sufiks/tcpadalah protokol bawaan yang ditambahkan otomatis karena Dockerfile kita tidak menyebut protokol secara eksplisit.{}setelah titik dua — nilai kunci kosong: tidak ada konfigurasi jaringan apa pun yang menempel pada deklarasi ini. EXPOSE benar-benar hanya catatan.
Jejak yang sama juga tampil pada riwayat build. Perintah docker history membaca image dari langkah termuda, dan baris EXPOSE muncul di antara instruksi Dockerfile kita:
$ docker history myapp:1.1Output yang Dihasilkan:
IMAGE CREATED CREATED BY SIZE COMMENT
c63616cb491b 2 hours ago CMD ["node" "server.js"] 0B
<missing> 2 hours ago EXPOSE 8000 0B
<missing> 2 hours ago COPY package.json server.js ./ 620B
<missing> 2 hours ago WORKDIR /app 0B
<missing> 2 hours ago LABEL versi=1.1 deskripsi=API kecil milik Budi 0B
...Output di atas terpotong — baris-baris milik node:22-alpine di bawahnya dilewati karena sudah kita bedah pada Chapter Struktur Dockerfile. Mari kita telaah bagian yang tampil:
EXPOSE 8000bernilai0B— instruksi ini tidak menyumbang satu byte berkas pun; ia metadata murni, sama sepertiCMD,WORKDIR, danLABELyang semuanya juga 0B.- Penanda
(nop)pada image lawas — image lama yang dibangun builder generasi sebelum BuildKit menampilkan baris serupa sebagai/bin/sh -c #(nop) EXPOSE 8000; penanda#(nop)adalah cara builder lama berkata “instruksi ini tidak melakukan operasi filesystem”. Baris paling bawah riwayatnode:22-alpinepada Chapter Struktur Dockerfile memperlihatkan pola#(nop)itu, sedangkan BuildKit menampilkan instruksi apa adanya. - Hanya
COPYyang berisi — 620B berkas aplikasi; pembagian “tiga instruksi berisi layer, sisanya metadata” tetap berlaku pada setiap image yang kita periksa.
Membuktikan EXPOSE Tidak Menerbitkan Port #
Bukti paling meyakinkan datang dari percobaan sederhana. Budi memulai dengan membersihkan container latihan dari chapter sebelumnya lewat docker rm -f — kebiasaan yang kita bawa sejak Section Dasar Docker CLI — lalu menjalankan myapp tanpa satu pun flag port:
$ docker run -d --name myapp-sunyi myapp:1.1Output yang Dihasilkan:
4c9a7e2b1d8f3a6c5e0b7d2f4a6c8e1b3d5f7a9c2e4b6d8f0a2c4e6b8d0f2a4cContainer hidup, aplikasi di dalamnya mendengarkan port 8000, dan EXPOSE 8000 tercatat rapi pada konfigurasi image. Sekarang Budi mencoba menghubunginya dari host:
$ curl localhost:8000Output yang Dihasilkan:
curl: (7) Failed to connect to localhost port 8000 after 0 ms: Couldn't connect to serverKoneksi ditolak: tidak ada apa pun yang mendengarkan port 8000 di sisi host. Kolom PORTS pada docker ps mempertegas keadaan itu:
$ docker psOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c9a7e2b1d8f myapp:1.1 "node server.js" About a minute ago Up About a minute 8000/tcp myapp-sunyiMari kita telaah bukti-bukti di atas:
8000/tcptanpa tanda panah — port tercatat terekspos karena deklarasi EXPOSE, tetapi tidak ada pemetaan ke host. Bandingkan dengan bariscachemilik Redis pada Chapter Melihat Container yang menampilkan6379/tcpdengan wajah persis sama.curlgagal — port 8000 milik container hidup di network namespace tersendiri; host tidak memiliki pintu menuju ke sana selama tidak ada pemetaan yang dibuat.- EXPOSE tidak berbuat apa pun di sini — deklarasinya ada di image, container-nya jalan, tetapi akses tetap tertutup rapat. Inilah bukti bahwa EXPOSE bukan penerbit port.
Menerbitkan Port dengan -p #
Yang benar-benar membuka pintu adalah penerbitan port (port publishing) — operasi runtime yang memetakan port host ke port container. Kita sudah memakainya sejak docker run pertama untuk myapp pada Chapter Membangun Image dengan Dockerfile; kali ini Budi melakukannya dengan kesadaran penuh akan mekanismenya:
$ docker run -d --name myapp-terbit -p 8000:8000 myapp:1.1Output yang Dihasilkan:
7f1c3e9b5d2a0b8e4d6f1a3c7e5b9d0f2a4c6e8b1d3f5a7c9e1b3d5f7a9c2e4b$ curl localhost:8000Output yang Dihasilkan:
{"app":"myapp","pesan":"Halo dari container"}Mari kita telaah apa yang berubah:
-p 8000:8000— polahost:container; permintaan yang datang ke port 8000 di host diteruskan daemon ke port 8000 di container. Pemetaan ini dibuat Docker daemon saat container diciptakan, dan inilah yang dinamakan port publishing.curlberhasil — pemetaan port, bukan EXPOSE, yang membuat permintaan host sampai ke aplikasi di dalam container.- Kedua container berjalan berdampingan — myapp-sunyi dan myapp-terbit sama-sama mendengarkan 8000 di namespace masing-masing tanpa bentrok; hanya myapp-terbit yang punya jembatan ke host.
- Variasi lengkap flag
-p— mengikat alamat IP tertentu, protokol udp, sampai rentang port — adalah materi Chapter Port Publishing di Section Konfigurasi Container; prinsipnya cukup satu: penerbitan terjadi saat runtime, sedangkan EXPOSE hanyalah catatan.
Flag -P Menerbitkan Seluruh Daftar EXPOSE #
Flag -P (huruf besar, bentuk panjangnya --publish-all) adalah jembatan antara dokumentasi dan penerbitan. Flag ini memerintahkan daemon menerbitkan seluruh port yang dideklarasikan EXPOSE pada image — setiap port mendapat port host acak dari rentang port tinggi yang sedang tidak terpakai. Di sinilah nilai praktis EXPOSE terlihat: daftar yang ditulis penulis Dockerfile menjadi daftar yang diterbitkan otomatis:
$ docker run -d --name myapp-acak -P myapp:1.1Output yang Dihasilkan:
9b2e5d8a1c4f7e3b6d0a8f2c5e7b1d4f8a3c6e0b2d9f4a7c1e5b8d3f0a6c2e4bPort host mana yang dipilih daemon untuk container ini? Perintah docker port membacanya untuk kita:
$ docker port myapp-acakOutput yang Dihasilkan:
8000/tcp -> 0.0.0.0:32771
8000/tcp -> [::]:32771$ curl localhost:32771Output yang Dihasilkan:
{"app":"myapp","pesan":"Halo dari container"}Mari kita telaah rangkaian di atas:
8000/tcp -> 0.0.0.0:32771— port 8000 milik container dipetakan ke port host 32771 di seluruh antarmuka IPv4; baris kedua adalah pasangan IPv6-nya dengan tanda[::].- Angka 32771 dipilih acak — daemon mengambil port tinggi yang kosong; nilainya bisa berbeda pada setiap mesin dan setiap
docker run. Selalu baca hasil pemetaan lewatdocker portatau kolomPORTS, jangan berasumsi angkanya tetap. curlberhasil lewat port acak — penerbitan-Pmembaca satu-satunya daftar yang tersedia, yaitu EXPOSE pada image. Tanpa EXPOSE, flag-Ptidak punya apa pun untuk diterbitkan.
Ketiga container tadi kini berdiri berdampingan dan memperlihatkan seluruh wajah port sekaligus dalam satu tabel:
$ docker psOutput yang Dihasilkan:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c9a7e2b1d8f myapp:1.1 "node server.js" 3 minutes ago Up 3 minutes 8000/tcp myapp-sunyi
7f1c3e9b5d2a myapp:1.1 "node server.js" 2 minutes ago Up 2 minutes 0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp myapp-terbit
9b2e5d8a1c4f myapp:1.1 "node server.js" About a minute ago Up About a minute 0.0.0.0:32771->8000/tcp, [::]:32771->8000/tcp myapp-acakMari kita telaah ketiga barisnya sebagai satu kesatuan pelajaran:
- Tiga container dari image yang sama — myapp-sunyi, myapp-terbit, dan myapp-acak menjalankan myapp:1.1 yang identik; perbedaan akses di antara mereka murni buatan flag runtime.
- myapp-sunyi hanya
8000/tcp— deklarasi EXPOSE terlihat tanpa pemetaan; container sepenuhnya tertutup dari host. - myapp-terbit memakai
-ptetap — port host 8000 dipetakan eksplisit oleh operator. - myapp-acak memakai
-Pacak — daftar EXPOSE diterbitkan ke port host acak; pola yang sama akan bekerja otomatis bila kelak image mengekspos lima port sekaligus.
EXPOSE untuk Beberapa Port dan Rentang #
Satu instruksi EXPOSE boleh memuat lebih dari satu port, lengkap dengan rentang dan protokol. Bentuk-bentuk sahnya terlihat seperti ini:
EXPOSE 8000 8443
EXPOSE 7000-7002
EXPOSE 53/udpMari kita telaah ketiga baris di atas:
EXPOSE 8000 8443— dua port dipisah spasi dalam satu instruksi; aplikasi dengan jalur HTTP dan HTTPS adalah contoh paling lazimnya.EXPOSE 7000-7002— rentang tiga port; flag-Pakan menerbitkan setiap anggota rentang ke port host acak masing-masing.EXPOSE 53/udp— protokol eksplisit/udp; tanpa sufiks protokol,tcpadalah nilai bawaan.- Semuanya tetap catatan — aturan “dokumentasi, bukan penerbitan” berlaku sama untuk bentuk apa pun; akses baru terbuka saat operator menerbitkan port saat runtime.
EXPOSE versus Publish #
Perbedaan kedua sisi ini layak dirangkum dalam satu tabel: miskonsepsi EXPOSE begitu umum sehingga tabel berikut sebaiknya diperlakukan sebagai kartu koreksi.
| Aspek | EXPOSE di Dockerfile | Publish dengan -p atau -P |
|---|---|---|
| Siapa yang bertindak | Builder, saat image dibangun | Docker daemon, saat container dibuat |
| Kapan terjadi | Sekali per build | Setiap kali docker run |
| Efek jaringan | Tidak ada — catatan pada konfigurasi image | Pemetaan port host-container benar-benar dibuat |
| Di mana terlihat | docker image inspect dan docker history | docker ps kolom PORTS dan docker port |
| Hubungan keduanya | Menjadi daftar yang dibaca flag -P | -p memakai nilai eksplisit; -P membaca daftar EXPOSE |
Dengan demikian posisi EXPOSE akhirnya jelas: ia berbicara kepada pembaca image dan tool otomatis, sementara keputusan akses sepenuhnya berpindah ke tangan operator saat container dijalankan. Pertanyaan berikutnya mengurus dua hal yang juga menentukan kenyamanan image di tangan orang lain: di dalam filesystem image, direktori mana yang menjadi rumah bagi berkas aplikasi, dan sebagai user siapa proses container berjalan. WORKDIR dan USER menjawab keduanya dengan pola yang sudah akrab bagi kita — instruksi yang menetapkan konteks bagi semua baris setelahnya — dan itulah bahasan Chapter WORKDIR dan USER.
Referensi #
- Dockerfile Reference — definisi resmi EXPOSE, aturan protokol, rentang port, dan bentuk sintaksnya.
- docker container run — CLI Reference
— flag
-pdan-Puntuk penerbitan port saat runtime. - docker container port — CLI Reference — cara membaca pemetaan port container yang sedang berjalan.