SSD Nodes Learn Hosting plans →
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-08-31

Bakit Nilalampasan ng Docker ang UFW at Paano Ayusin

Naka-deny ang port 8080 pero sumasagot pa rin sa internet dahil sa Docker iptables rules. Alamin ang bypass ng UFW at dalawang gumaganang fix.

Bakit nilalampasan ng Docker ang UFW

Nilalampasan ng Docker ang UFW dahil ang mga published container port ay hindi dumadaan sa mga firewall rule na mina-manage ng UFW. Kapag pinatakbo mo ang docker run -p 8080:80, nagsusulat ang Docker ng DNAT (destination network address translation) rule sa PREROUTING chain ng kernel's nat table. Binabago ng rule na ito ang destination ng bawat packet para tumuro sa private address ng container bago magpasya ang kernel kung saan ito dapat pumunta. Pagkatapos, ipinapasa ang binagong packet sa container sa pamamagitan ng FORWARD chain, na kontrolado ng Docker. Nasa INPUT chain ang mga rule ng UFW, at hindi kailanman pumapasok dito ang packet. Kaya ipinapakita ng ufw status ang default deny, nag-uulat ang sudo ufw deny 8080 ng tagumpay, ngunit sumasagot pa rin ang port 8080 sa buong internet.

Hindi ito bug ng Docker, at hindi sira ang UFW. Parehong nagpo-program ang dalawang tool sa iisang kernel firewall. Nauuna lamang kumilos ang mga rule ng Docker sa daanan ng packet, kaya hindi kailanman nasusuri ng UFW ang packet. Ipinapakita ng gabay na ito kung paano nangyayari ang bypass, ipinapaliwanag ang mekanismo, at tinatalakay ang dalawang gumaganang solusyon: pag-publish ng mga port sa 127.0.0.1 at pag-filter sa DOCKER-USER chain. Kung bago sa iyo ang UFW, i-set up muna ito gamit ang gabay sa mga pangunahing kaalaman sa UFW firewall, dahil ang firewall na may default-deny ay nananatiling tamang base para sa iba pang serbisyo sa server.

Tingnan mismo ang bypass sa sarili mong server

Magsimula sa isang VPS na aktibo ang UFW at may default deny policy para sa incoming traffic. Magpatakbo ng web container na may published port:

sudo ufw status verbose
docker run -d --name web -p 8080:80 nginx:1.29-alpine

Ipinapakita ng ufw status verbose ang Default: deny (incoming), allow (outgoing) at walang rule para sa port 8080. Ayon sa sariling report ng firewall, sarado ang port. Ngayon, magsagawa ng test mula sa ibang machine, hindi mula sa server mismo:

curl -I http://your-vps-ip:8080/
HTTP/1.1 200 OK

Sumasagot ang container. Magdagdag ng explicit deny rule at magsagawa ulit ng test:

sudo ufw deny 8080/tcp

Sumasagot pa rin ang port dahil nasa chain ang deny rule na hindi kailanman pinupuntahan ng packet. Hindi nag-fail ang UFW. Hindi ito kailanman nakonsulta. Ito rin ang dahilan kung bakit mahirap makita ang problema: walang error na napi-print saanman, gumagana ang deploy, at ang output ng firewall status ay eksaktong katulad ng sa isang maayos na naka-lockdown na server.

Ang mekanismo: nauuna ang PREROUTING kaysa INPUT

Pinoproseso ng kernel ang papasok na packet sa isang nakatakdang pagkakasunod-sunod, at nasa pagkakasunod-sunod na iyon ang buong problema.

  1. PREROUTING ang unang tumatakbo. Maaaring baguhin ng mga rule dito ang destination ng packet, at eksaktong ito ang ginagawa ng Docker rule para sa isang published port.
  2. Kasunod ang routing decision. Ang packet na nakatalaga mismo sa host ay ipinapasa sa INPUT chain. Ang packet na nakatalaga sa ibang machine ay ipinapasa sa FORWARD chain.
  3. Nasa INPUT ang mga rule ng UFW. Nasa FORWARD ang mga rule ng Docker.

Tingnan ang Docker rule para sa container na kakasimula mo lang:

sudo iptables -t nat -L DOCKER -n
Chain DOCKER (2 references)
target   prot opt source       destination
RETURN   0    --  0.0.0.0/0    0.0.0.0/0
DNAT     6    --  0.0.0.0/0    0.0.0.0/0    tcp dpt:8080 to:172.17.0.2:80

Nasa DNAT line ang buong paliwanag. Ang bawat packet na dumarating para sa port 8080 ay binabago ang destination nito sa 172.17.0.2:80, ang address ng container sa private bridge network ng Docker. Pagkatapos ng pagbabago, hindi na nakatalaga ang packet sa host, kaya ipinapasa ito ng routing decision sa FORWARD path, kung saan nakapagdagdag na ang Docker ng mga rule na tumatanggap ng traffic papasok sa sarili nitong mga network. Naghihintay ang deny 8080/tcp rule mo sa INPUT para sa packet na hindi kailanman dumarating.

Sa Ubuntu 24.04, ang iptables command ay front end para sa nftables, pero magkapareho ang pagkakasunod-sunod ng chain at ang resulta. Parehong nagsusulat ang UFW at Docker sa iisang kernel packet pipeline, at mas maaga ang entry point ng Docker. Hindi ito partikular sa UFW: nagfi-filter ang firewalld sa isang Rocky o AlmaLinux VPS sa parehong punto ng pipeline at nalalampasan din ng parehong DNAT rule, kaya ang mga fix sa ibaba ang dapat mong gamitin doon.

Ang karaniwang ayos: i-publish ang ports sa 127.0.0.1

Hindi naman kailangang gawing public ang karamihan sa mga container. Database man ito, app server sa likod ng reverse proxy, admin panel, o metrics endpoint, hindi dapat direktang sumagot ang mga ito sa internet. I-publish ang mga ito sa loopback address:

docker run -d --name web -p 127.0.0.1:8080:80 nginx:1.29-alpine

O sa isang Compose file:

services:
  web:
    image: nginx:1.29-alpine
    ports:
      - "127.0.0.1:8080:80"

Gumagana ito dahil tumutugma na ang Docker DNAT rule sa mga packet na nakalaan lamang sa 127.0.0.1. Hindi kailanman lehitimong magdadala ang packet mula sa internet ng destination na iyon, kaya ibinabagsak ito ng kernel bago tumakbo ang anumang firewall rule. Maa-access ang port mula sa host at wala nang iba. I-verify ang binding:

sudo ss -tlnp | grep 8080

Dapat lumabas sa output ang 127.0.0.1:8080, hindi ang 0.0.0.0:8080 o [::]:8080. Pagkatapos, kumpirmahin mula sa ibang machine na nire-refuse ang curl http://your-vps-ip:8080/.

Para sa mga serbisyong dapat direktang ma-access mula sa internet, gumamit ng isang reverse proxy na may hawak sa ports 80 at 443 at nagra-route batay sa hostname. Huwag nang mag-publish ng iba pang port. Ito ang pattern na binubuo ng gabay sa Traefik reverse proxy, at ito rin ang paraan upang manatiling hindi direktang ma-access ang isang self-hosted app gaya ng Nextcloud sa isang VPS maliban sa pamamagitan ng proxy nito. Ipinaliwanag sa gabay sa mga pangunahing kaalaman sa Docker Compose kung paano dine-declare ang mga entry na ports: at ang iba pang bahagi ng Compose workflow.

Kapag nasa loopback ang bawat internal container, nagagawa na muli ng UFW ang normal nitong tungkulin: bantayan ang mga port na mismong sine-serve ng host. Buuin dito ang rule set, pagkatapos ay patakbuhin ang mga command ayon sa pagkakasunod-sunod:

ToolUFW rule generator

Tunay na filtering: ang DOCKER-USER chain

Minsan kailangang manatiling published sa network ang container port pero dapat itong paghigpitan. Halimbawa, maaaring database replica port ito na isang office address lamang ang puwedeng maabot. Para rito, nagbibigay ang Docker ng DOCKER-USER chain. Dumadaan ang bawat packet na patungo sa anumang container sa DOCKER-USER bago ang sariling accept rules ng Docker, at hindi kailanman nagsusulat ang Docker ng rules dito. Para sa iyo ang chain na ito, at hindi binabago ng Docker ang laman nito kahit mag-restart ang daemon.

May isang dapat tandaan bago patakbuhin ang command: pagdating ng packet sa DOCKER-USER, naganap na ang DNAT rewrite. Ang destination port ng packet ay ang container port (80 sa halimbawa natin), hindi ang published port (8080). Kaya walang nami-match na anuman ang rule na gumagamit ng --dport 8080. Ang maaasahang paraan ay i-match ang port na orihinal na tinawagan ng client, na itinatala ng kernel's connection tracker:

sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL ! -s 10.0.0.10 -j DROP

Ganito ito basahin: para sa mga packet na pumasok sa eth0 at kabilang sa connection na ang original destination port ay 8080, i-drop ang lahat ng packet na hindi ipinadala mula sa 10.0.0.10. Nililimitahan ng --ctdir ORIGINAL match ang rule sa direksiyong client-to-container, kaya hindi aksidenteng nasasama ang reply packets. Palitan ang eth0 ng iyong public interface; tinutukoy ito ng ip route | grep default. Subukan ito sa parehong paraan gaya ng dati: magtatagumpay ang curl mula sa pinapayagang address, at magti-time out ang connection mula sa iba pang address. Ang pag-hang na iyon ang palatandaan na gumagana ang DROP rule, sa halip na nagpapahiwatig na walang serbisyong nasa likod ng port. Ang pagkakaiba ng refused connection at connection na nag-ti-time out ang pinakamabilis na paraan upang matukoy kung filtered ang port o talagang walang serbisyong nakikinig dito.

Nawawala sa reboot ang mga rule na idinagdag gamit ang iptables command. Dahil UFW na ang namamahala sa firewall na ito, ang tamang lugar para i-persist ang mga ito ay /etc/ufw/after.rules. Mag-append ng block sa dulo ng file:

*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -i eth0 -p tcp -m conntrack --ctorigdstport 8080 --ctdir ORIGINAL ! -s 10.0.0.10 -j DROP
COMMIT

Pagkatapos, patakbuhin ang sudo ufw reload. Ire-replay ng UFW ang file na iyon sa bawat reload at boot, kaya nasa iisang lugar na kasama ng iba pang firewall configuration ang container filtering mo. Mananatili rin ito pagkatapos ng reboot at Docker upgrade.

Bakit hindi dapat i-disable ang iptables integration ng Docker

Iminumungkahi ng mga lumang sagot sa problemang ito na itakda ang { "iptables": false } sa /etc/docker/daemon.json. Huwag itong gawin. Higit pa sa pag-publish ng mga port ang ginagawa ng firewall rules ng Docker. Ang masquerade rule ang nagbibigay sa mga container ng outbound internet access gamit ang address ng host. Kapag naka-off ang integration, hindi makakapag-pull ng images, makaka-access ng package mirrors, o makakatawag sa anumang external API (application programming interface) ang mga container. Ang DNAT rules ang nagpapatakbo sa -p, kaya tuluyang hindi gagana ang mga published port. Mawawala rin ang isolation rules na naghihiwalay sa magkakaibang Compose network. Maaayos mo nga ang bypass, pero masisira naman ang container networking, at ikaw ang kailangang manu-manong magsulat at mag-maintain ng bawat isa sa mga rule na iyon. Inilalarawan mismo ng documentation ng Docker ang setting na ito bilang para sa mga taong sadyang gagawa ng lahat ng iyon. Umiiral ang DOCKER-USER chain para walang mangailangang gumamit ng switch na ito.

Ang IPv6 na bahagi ng parehong problema

Una, tingnan kung ano ang hitsura ng published port sa IPv6:

sudo ss -tlnp | grep 8080

Mula sa Docker Engine 27, default nang mina-manage ng Docker ang ip6tables. Sa Docker network na may naka-enable na IPv6, dumadaan ang published port sa parehong DNAT treatment sa IPv6 tables. Kaya umiiral din doon ang parehong bypass at naaangkop ang parehong fix: umiiral din ang DOCKER-USER chain sa ip6tables, kaya i-mirror ang rule gamit ang sudo ip6tables -I DOCKER-USER ... at mag-test mula sa labas gamit ang curl laban sa public IPv6 address ng server, halimbawa curl -6 http://[2001:db8:2a::1]:8080/.

Sa network na walang IPv6, ang mga IPv6 client ay hinahandle naman ng docker-proxy, isang normal na user-space process na nakikinig sa [::]:8080 at nagpapasa ng traffic sa container gamit ang IPv4. Dumadaan sa INPUT ang traffic patungo sa isang host process, kaya maaaring i-filter ng UFW ang path na iyon, pero kapag mina-manage ng UFW ang IPv6. Kung mina-manage nga ito ng UFW, pati ang iba pang paraan kung paano nagkakaroon ng IPv6 gap sa isang VPS, ay tinalakay sa gabay sa UFW at IPv6.

Iniiwasan ng pag-publish sa loopback ang buong isyung ito: ang -p 127.0.0.1:8080:80 ay nagbi-bind lamang sa IPv4 loopback, kaya walang IPv6 listener at walang maaabot mula sa labas gamit ang alinmang stack.

Ang pattern na matibay sa aktuwal na paggamit

  • I-publish ang bawat internal port sa 127.0.0.1 upang hindi ito direktang ma-expose.
  • Ibigay ang public side sa isang reverse proxy na may kontrol sa ports 80 at 443.
  • Panatilihing default deny ang UFW para sa host, at payagan lamang ang SSH at mga port ng proxy.
  • I-filter ang tunay na public container ports sa DOCKER-USER, batay sa orihinal na destination port, at i-persist sa /etc/ufw/after.rules.
  • Panatilihing naka-enable ang Docker iptables integration.

Kapag isang beses itong na-set up, mawawala ang mga hindi inaasahang exposure: inilalarawan ng ufw status ang host, at inilalarawan ng DOCKER-USER ang mga container. Walang maipu-publish nang hindi sinasadya, at eksaktong ilalantad ng susunod na docker run -p na ita-type mo ang nilayon mong ilantad.

FAQ

Bakit naaabot ko ang Docker container kahit bina-block ng UFW ang port?

Dahil pino-publish ng Docker ang port gamit ang DNAT rule sa PREROUTING chain. Binabago ng rule ang destination ng packet upang tumuro sa address ng container bago mangyari ang anumang filtering. Pagkatapos, dumadaan ang packet sa FORWARD path. Samantala, nasa INPUT ang mga rule ng UFW, isang chain na hindi kailanman pinapasukan ng packet. Hindi nasusuri ng firewall ang packet, kaya walang epekto ang mga deny rule nito sa mga published container port.

Paano ko mapapablock sa UFW ang mga published port ng Docker?

Hindi ito kayang gawin ng UFW mismo dahil nasa maling chain ang mga rule nito. Maaari mong ihinto ang paglalantad ng port sa pamamagitan ng pag-publish nito bilang 127.0.0.1:8080:80 upang host lamang ang makapag-access dito. Maaari ka ring mag-filter sa DOCKER-USER chain gamit ang iptables rule na tumutugma sa orihinal na destination port sa pamamagitan ng conntrack. I-persist ang rule na iyon sa /etc/ufw/after.rules upang manatili ito matapos ang reboot at ufw reload.

Dapat ko bang itakda ang "iptables": false sa Docker's daemon.json?

Hindi. Inaalis ng setting na iyon ang lahat ng firewall at NAT rule ng Docker. Mas marami itong sinisira kaysa sa bypass. Mawawalan ng outbound internet access ang mga container dahil mawawala ang masquerade rule. Hihinto rin sa paggana ang mga published port dahil mawawala ang mga DNAT rule. Sa halip, gamitin ang loopback publishing at ang DOCKER-USER chain. Inaayos ng mga ito ang exposure nang hindi sinisira ang container networking.

Naba-bypass din ba ng Docker ang UFW sa IPv6?

Sa Docker Engine 27 at mga susunod na bersyon, naka-enable bilang default ang pamamahala ng ip6tables. Kaya ang port na naka-publish sa Docker network na may IPv6 ay nire-rewrite sa paligid ng UFW, tulad ng sa IPv4. Kailangan nito ang katumbas na DOCKER-USER rule na may ip6tables. Sa mga network na walang IPv6, nakikinig ang docker-proxy process sa [::]. Dumadaan ang traffic na iyon sa INPUT, kung saan maaari itong i-filter ng UFW kung pinamamahalaan ng UFW ang IPv6. Maiiwasan ang dalawang sitwasyon sa pamamagitan ng pag-publish sa 127.0.0.1 dahil walang anumang nakikinig sa IPv6.