Tip di akhir chapter sebelumnya menyebut flag -p seolah ia satu trik kecil untuk memindahkan nomor port. Kenyataannya -p adalah flag dengan format paling padat di docker run: satu nilai bisa memuat alamat host, port host, port container, dan protokol sekaligus, dan setiap segmennya menyimpan aturan main sendiri. Kita juga sudah dua kali menyinggung hubungan -p dengan EXPOSE — pertama pada Section Dasar Docker CLI dan kedua pada Chapter EXPOSE dan Port — tanpa pernah membedah format lengkapnya. Chapter ini melunasi utang itu: anatomi empat segmen, pengikatan ke alamat tertentu, protokol udp, rentang port, port acak, sampai diagnosis konflik port yang paling sering menyapa pemula.

Anatomi Flag -p #

Bentuk lengkap flag -p (bentuk panjangnya --publish) adalah:

text
-p [alamat_host:]port_host:port_container[/proto]

Mari kita telaah keempat segmennya satu per satu:

SegmenWajibContohArtinya
alamat_hosttidak — bawaan mengikat semua antarmuka127.0.0.1antarmuka host tempat port dibuka
port_hostya — angka 0 berarti acak8000port di sisi host yang diterbitkan
port_containerya8000port tujuan di dalam container
prototidak — bawaan tcpudpprotokol pemetaan

Kombinasi yang paling sering dijumpai terangkum dalam tabel berikut:

Bentuk flagEfeknya
-p 8080:8000port host 8080 di semua antarmuka menuju port container 8000 tcp
-p 127.0.0.1:8000:8000hanya localhost host yang bisa menghubungi
-p 9000:8000/udppemetaan protokol udp
-p 8080-8082:8000rentang port host menuju satu port container

Kita jalankan bentuk dasarnya pada myapp. Port host 8080 sedang diduduki container web milik nginx, jadi Budi memakai 8000:

bash
$ docker run -d --name myapp-publik -p 8000:8000 myapp:1.0

Output yang Dihasilkan:

text
5d8c2a9e7b4f1a6d3c8e5b2f9a7d4c1e6b3a8f5d2c9e7b4a1f6d3c8e5b2a9d7c
bash
$ docker port myapp-publik

Output yang Dihasilkan:

text
8000/tcp -> 0.0.0.0:8000
8000/tcp -> [::]:8000
bash
$ curl localhost:8000

Output yang Dihasilkan:

text
{"app":"myapp","pesan":"Halo dari container"}

Mari kita telaah tiga perintah di atas:

  1. 8000/tcp -> 0.0.0.0:8000 — port 8000 di dalam container dipetakan ke port 8000 di semua antarmuka IPv4 host; baris kedua dengan [::] adalah pasangan IPv6-nya, karena tanpa segmen alamat, Docker mengikat keduanya sekaligus.
  2. Arah panah dibaca dari kiri ke kanan — port container berada di kiri, pintu host di kanan; perintah docker port menyajikan inventaris lengkap pemetaan container yang sedang berjalan.
  3. curl menembus sampai aplikasi — permintaan dari host masuk lewat port host, diteruskan daemon ke port container, dan dijawab server myapp; inilah penerbitan yang sesungguhnya, berbeda dari EXPOSE 8000 pada Dockerfile yang hanyalah catatan metadata — pembagian tugas yang sudah kita buktikan pada Chapter EXPOSE dan Port.

Perjalanan satu permintaan dari luar sampai ke aplikasi terpetakan seperti ini:

Mari kita telaah diagram di atas: cabang kiri menggambarkan jalur yang barusan kita uji dengan curl, sedangkan cabang kanan adalah nasib yang kita saksikan pada Chapter EXPOSE dan Port ketika myapp-sunyi berjalan tanpa satu pun flag -p — koneksi langsung ditolak karena tidak ada pintu host yang mengarah ke container.

Mengikat Hanya ke Localhost #

Segmen alamat membatasi pintu host ke antarmuka tertentu. Bentuk paling bergunanya mengikat ke 127.0.0.1 sehingga hanya proses di mesin yang sama yang bisa menghubungi:

bash
$ docker run -d --name myapp-lokal -p 127.0.0.1:8001:8000 myapp:1.0

Output yang Dihasilkan:

text
3e7a9c1d5f8b2b6e4d8a1c3f9d2b5f7a3e1c6b8d4a2f9c7e1b3d5f7a9c2e4d6f
bash
$ docker port myapp-lokal

Output yang Dihasilkan:

text
8000/tcp -> 127.0.0.1:8001

Mari kita telaah satu baris output itu:

  1. Satu baris saja, tanpa pasangan IPv6 — pemetaan hanya hidup di 127.0.0.1; mesin lain di jaringan yang mencoba menghubungi alamat LAN host akan ditolak, sementara curl localhost:8001 dari host tetap sampai ke myapp.
  2. Port host dan port container boleh berbeda total — 8001 di host, 8000 di container; aplikasi tidak pernah tahu dan tidak perlu tahu nomor pintu yang dipakai di sisi luar.

Protokol dan Rentang Port #

Flag -p boleh diulang dalam satu perintah, dan dua kekuatan itu — pengulangan serta rentang — paling enak diperagakan bersama:

bash
$ docker run -d --name myapp-banyak \
    -p 9000:8000/udp \
    -p 9001-9003:8000 \
    myapp:1.0

Output yang Dihasilkan:

text
7f2b8e4c6a1d3c9e5b2a7d4f1e6b3a8f5d2c9e7b4a1f6d3c8e5b2a9d7c4f1a3e
bash
$ docker port myapp-banyak

Output yang Dihasilkan:

text
8000/udp -> 0.0.0.0:9000
8000/udp -> [::]:9000
8000/tcp -> 0.0.0.0:9001
8000/tcp -> 0.0.0.0:9002
8000/tcp -> 0.0.0.0:9003
...

Output di atas dipotong — tiga baris pasangan IPv6 untuk rentang tcp tidak ditampilkan. Mari kita telaah kombinasi flag-nya:

  1. Dua flag -p dalam satu docker run — satu container boleh menerbitkan banyak pintu sekaligus; setiap flag menciptakan pemetaan yang independen.
  2. 9000:8000/udp membuka pintu protokol udp — tcp dan udp adalah dua ruang nomor port yang terpisah; pemetaan udp tetap dibuat meskipun myapp tidak mendengarkan udp, sehingga klien udp yang menghubungi tidak akan dijawab. Kasus penggunaan aslinya adalah layanan berbasis udp seperti DNS pada port 53.
  3. Rentang 9001-9003 menghasilkan tiga pemetaan — setiap port host dalam rentang mengarah ke port container yang sama; pola ini berguna saat satu layanan harus dijangkau lewat beberapa pintu host sekaligus, misalnya selama migrasi nomor port bertahap.

Port Acak dengan -p 0 dan -P #

Menyebut angka port host setiap saat tidak selalu praktis — mesin uji, pekerjaan paralel, dan skrip lebih suka membiarkan daemon memilih. Port host 0 memerintahkan persis itu:

bash
$ docker run -d --name myapp-nol -p 0:8000 myapp:1.0

Output yang Dihasilkan:

text
9b3e7d1a5c2f4e8b2d6f1a9c7d3f5e2c9a6b8d1f3e5c7a9b2d4f6e8c0a3b5d7f
bash
$ docker port myapp-nol

Output yang Dihasilkan:

text
8000/tcp -> 0.0.0.0:32773
8000/tcp -> [::]:32773

Mari kita telaah angka yang muncul begitu saja itu:

  1. 32773 dipilih daemon dari rentang port tinggi — nilai ini berbeda pada setiap mesin dan setiap docker run; selalu baca hasilnya lewat docker port atau kolom PORTS, jangan berasumsi angkanya stabil.
  2. Bentuk pendeknya serupa — -p 8000 yang hanya menyebut port container memberi efek yang sama: pemetaan ke port host acak.
  3. Saudara tuanya adalah flag -P — huruf besar tersebut menerbitkan seluruh daftar EXPOSE image ke port acak masing-masing, pola yang sudah kita buktikan dengan myapp-acak pada Chapter EXPOSE dan Port; -p 0 memberi kendali eksplisit per port, -P menyerahkan seluruhnya pada daftar EXPOSE.

Ketiga alat penerbitan beserta EXPOSE kini bisa dirangkum dalam satu tabel pembanding:

Aspek-p-PEXPOSE
Siapa menetapkan port hostoperator eksplisit — 0 berarti acakdaemon memilih acaktidak ada — bukan penerbitan
Sumber port containerargumen flagdaftar EXPOSE pada imageinstruksi Dockerfile
Kapan berlakusaat container diciptakansaat container diciptakansaat build — metadata semata
Efek jaringan nyatapemetaan dibuatpemetaan dibuattidak ada
Terbaca lewatdocker port dan kolom PORTSdocker port dan kolom PORTSdocker image inspect

Konflik Port #

Satu-satunya pintu host tidak boleh dipakai dua pemetaan sekaligus, dan itulah galat yang paling sering menyapa pemula. Budi mencoba bentuk kanonik 8080:8000 untuk myapp — tanpa sadar bahwa container web sudah lama menduduki 8080:

bash
$ docker run -d --name myapp-8080 -p 8080:8000 myapp:1.0

Output yang Dihasilkan:

text
docker: Error response from daemon: Bind for 0.0.0.0:8080 failed: port is already allocated
See 'docker run --help'.

Mari kita telaah kegagalan itu beserta jalan keluarnya:

  1. Daemon gagal mengikat port host saat create — galat terjadi sebelum aplikasi myapp sempat menyala; konflik terdeteksi sedini mungkin karena pintu host dipegang daemon, bukan aplikasi.
  2. Container gagal tetap tercipta — myapp-8080 tertinggal berstatus Created tanpa pernah berjalan; bersihkan dengan docker rm myapp-8080 sebelum mencoba lagi.
  3. Pemakai port bisa container lain atau proses host — pemilik 8080 kali ini adalah container web dengan pemetaan -p 8080:80; cari kandidat container lewat docker ps dan kolom PORTS, sedangkan layanan host di luar Docker ditelusuri dengan alat host seperti ss -tlnp.

Pembersihan dan solusinya berjalan singkat:

bash
$ docker rm myapp-8080
$ docker run -d --name myapp-8080 -p 8002:8000 myapp:1.0

Output yang Dihasilkan:

text
myapp-8080
2c6f9b3e7a1d5f8b2e4d6a3c9e1b7d3f5a2c8e4b1d6f3a9c7b2e5d4f1a3c6e9b

Menggeser nomor port host adalah obat tercepat; alternatifnya dua — menghentikan layanan host yang memakai port tersebut, atau menyerahkan nomor tersebut dan memilih angka lain untuk container baru. Inventaris pintu yang sedang terbuka terbaca dalam satu perintah berformat:

bash
$ docker ps --format "table {{.Names}}\t{{.Ports}}"

Output yang Dihasilkan:

text
NAMES          PORTS
web            0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
myapp-publik   0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp
myapp-lokal    127.0.0.1:8001->8000/tcp
myapp-banyak   0.0.0.0:9000->8000/udp, [::]:9000->8000/udp, 0.0.0.0:9001-9003->8000/tcp, ...
myapp-nol      0.0.0.0:32773->8000/tcp, [::]:32773->8000/tcp
myapp-8080     0.0.0.0:8002->8000/tcp, [::]:8002->8000/tcp

Output di atas dipotong pada baris myapp-banyak — pasangan IPv6 untuk rentang tcp tidak ditampilkan. Enam baris itu sekaligus merangkum seluruh wajah pemetaan yang kita bangun sepanjang chapter: pemetaan biasa, ikatan localhost, udp, rentang yang diringkas menjadi 9001-9003, dan port acak.

Catatan IPv6 #

Dua notasi alamat pada output di atas layak dikenali lebih dekat. Alamat 0.0.0.0 berarti seluruh antarmuka IPv4, sedangkan [::] berarti seluruh antarmuka IPv6 — itulah mengapa pemetaan tanpa segmen alamat selalu muncul berpasangan. Segmen alamat juga menerima alamat IPv6 dalam tanda kurung siku bila kita ingin membatasi pemetaan ke IPv6 saja:

bash
$ docker run -d --name myapp-v6 -p "[::1]:8003:8000" myapp:1.0

Output yang Dihasilkan:

text
6e1b4d8a2c7f9e3b5a1d6c8e4b2f7a9d1c3e5b6f8a2d4c6e1b3f5a7d9c2e4b6f
bash
$ docker port myapp-v6

Output yang Dihasilkan:

text
8000/tcp -> [::1]:8003

Notasi [::1] adalah loopback IPv6 — kebalikan langsung dari 127.0.0.1; pemetaan hanya hidup di antarmuka IPv6 localhost host, dan klien yang terhubung lewat IPv4 tidak akan menemukannya.

Budi membersihkan container latihan sebelum chapter ini ditutup, membiarkan web tetap hidup untuk latihan berikutnya:

bash
$ docker rm -f myapp-publik myapp-lokal myapp-banyak myapp-nol myapp-8080 myapp-v6

Output yang Dihasilkan:

text
myapp-publik
myapp-lokal
myapp-banyak
myapp-nol
myapp-8080
myapp-v6

Pintu-pintu host kini terpetakan utuh — dari pemetaan eksplisit, ikatan localhost, udp, rentang, sampai port acak. Namun masih ada satu knob runtime yang kecil sekaligus sering dilupakan orang: direktori kerja tempat proses container mulai membaca dan menulis berkasnya. Chapter Working Directory menutup rangkaian lima flag dasar ini dengan -w.

Referensi #