Port Mapping ve Exposure
Şimdiye kadar container'ların kendi aralarında nasıl konuştuğunu öğrendik. Ama bir web uygulaması geliştiriyorsan, sonuçta bir kullanıcının tarayıcısından o uygulamaya erişmesi lazım. Container'ın içinde çalışan Nginx, "Ben 80 portunda dinliyorum" diyor — ama bu container'ın kendi 80 portu. Dış dünyadan bu porta nasıl ulaşacağız?
İşte port mapping tam bunu çözüyor. Ve bu derste port mapping'in inceliklerini, güvenlik risklerini ve production'da nasıl kullanılması gerektiğini öğreneceğiz.
Port Mapping Neden Gerekli?
Bir oteli düşün. Otel dışından birisi "312 numaralı oda" diye doğrudan arayamaz. Önce otelin genel numarasını çevirir, resepsiyon bağlar. Dışarıdan otelin numarası 0212-555-1234, içerideki oda numarası 312. Resepsiyon bu eşlemeyi yapar: "dışarıdan gelen aramayı 312'ye bağla."
Docker'da port mapping tam bu resepsiyon mekanizmasıdır. Container'ın iç portu (oda numarası) ile host makinenin dış portu (otelin genel numarası) farklıdır. Port mapping ikisini birbirine bağlar.
Hadi bunu pratikte görelim. Bir Nginx container'ı başlatalım, ama bu sefer port mapping olmadan:
docker run -d --name web-without-port nginx:alpineContainer çalışıyor. İçeride Nginx 80 portunu dinliyor. Ama host'tan erişmeye çalışalım:
curl http://localhost:80curl: (7) Failed to connect to localhost port 80: Connection refusedErişemedik! Çünkü container'ın 80 portu kendi dünyasında — dışarıya açılmamış. Şimdi port mapping ile deneyelim:
docker run -d --name web-with-port -p 8080:80 nginx:alpine-p 8080:80 ne demek? "Host'un 8080 portuna gelen trafiği, container'ın 80 portuna yönlendir." Şimdi erişelim:
curl http://localhost:8080Nginx'in varsayılan sayfası geldi! İşte port mapping bu kadar basit. Ama detaylarına daldığımızda çok daha fazlası var.
Port Mapping Nasıl Çalışır?
Arka planda Docker, Linux kernel'ın iptables kurallarını kullanarak NAT (Network Address Translation) yapar. Yani her port mapping için kernel seviyesinde bir trafik yönlendirme kuralı oluşturulur.
Bunu görselleştirelim:
Dış Dünya (İnternet / Tarayıcı)
│
▼
┌───────────────────────────────────────────┐
│ Host Machine (192.168.1.100) │
│ │
│ Port 8080 ── iptables NAT ──→ Container Port 80 │
│ Port 3000 ── iptables NAT ──→ Container Port 3000│
│ Port 5433 ── iptables NAT ──→ Container Port 5432│
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Nginx │ │ API │ │ Postgres │ │
│ │ :80 │ │ :3000 │ │ :5432 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└───────────────────────────────────────────┘Ayrıca Docker her port mapping için bir docker-proxy adında userspace prosesi de çalıştırır. Yani iki mekanizma birlikte çalışır: kernel'daki iptables kuralları ve docker-proxy prosesi.
Port Mapping Sözdizimi
-p (veya --publish) flag'inin farklı kullanım şekilleri var. Her birini tek tek görelim.
Temel format — HOST_PORT:CONTAINER_PORT:
docker run -d -p 8080:80 nginxHost'un 8080'ine gelen trafik, container'ın 80'ine gider. En sık kullanılan format bu.
Aynı port numarası:
docker run -d -p 3000:3000 myappHost ve container aynı portu kullanıyor. Karışıklık olmaması için genelde bu tercih edilir.
Birden fazla port:
docker run -d -p 80:80 -p 443:443 nginxBirden fazla -p ekleyerek birden fazla port mapping yapabilirsin. Nginx'in hem HTTP (80) hem HTTPS (443) portlarını açtık.
Sadece container portu (rastgele host portu):
docker run -d -p 80 nginxHost port numarası belirtmedik. Docker, 32768-60999 aralığından rastgele bir port atar. Hangi portu atadığını görmek için:
docker port $(docker ps -q) 800.0.0.0:32768Docker 32768 portunu seçmiş. Development'ta işe yarayabilir ama production'da genelde istediğin portu kendin belirtirsin.
IP Binding — Güvenliğin Anahtarı
Şimdi çok önemli bir konuya geldik. Normalde -p 8080:80 yazdığında, Docker bu portu tüm ağ arayüzlerinde açar — yani dışarıdan herkes erişebilir. Bu bazen istediğin şey değildir.
# Varsayılan — tüm arayüzler (0.0.0.0)
docker run -d -p 8080:80 nginx
# = docker run -d -p 0.0.0.0:8080:80 nginx
# Dışarıdan herkes erişebilir!Ama veritabanı container'ı için bu tehlikeli. PostgreSQL'i dışarıya açmak istemezsin. Sadece host makine üzerinden erişilebilir olsun istersen, 127.0.0.1'e bağlarsın:
# Sadece localhost — dışarıdan erişilemez
docker run -d -p 127.0.0.1:5432:5432 postgres:16Bu çok önemli bir fark. İlkinde tüm dünya veritabanına erişebilir (potansiyel güvenlik açığı), ikincinde sadece host makinedeki uygulamalar erişebilir.
Hadi bunu test edelim:
# localhost'tan erişim — çalışır
curl http://localhost:5432
# Bir yanıt gelecek (PostgreSQL binary protokolü olsa da bağlantı kuruluyor)
# Başka bir makineden erişim
curl http://192.168.1.100:5432
# Connection refused — dışarıdan erişilemez ✓Belirli bir ağ arayüzüne de bağlayabilirsin:
# Sadece belirli IP'den erişilebilir
docker run -d -p 10.0.0.1:8080:80 nginxPratik kural: Web sunucuları (0.0.0.0) tüm dünyaya açılabilir. Veritabanları, cache sistemleri, admin panelleri 127.0.0.1'e bağlanmalı.
EXPOSE Talimatı — Yaygın Yanlış Anlama
Dockerfile'larda sıkça EXPOSE talimatı görürsün:
FROM nginx:alpine
EXPOSE 80
EXPOSE 443Çoğu insan EXPOSE'un portu dışarıya açtığını sanır. Hayır, açmaz! EXPOSE sadece bir belgeleme amacı taşır — "Bu container 80 ve 443 portlarını kullanıyor" der. Ama gerçekten portu açmak için runtime'da -p flag'i gerekir.
EXPOSE olmadan da port mapping çalışır:
docker run -d -p 8080:80 nginxNginx image'ında EXPOSE 80 yazsa da yazmasa da, -p 8080:80 dediğinde port açılır.
EXPOSE'un gerçek faydaları şunlar:
Dokümantasyon — container'ın hangi portları kullandığını gösterir
`-P` flag'i ile çalışır — büyük P, EXPOSE edilmiş tüm portları rastgele host portlarına map'ler
Diğer araçlar için bilgi —
docker inspectile okunabilir
# -P (büyük P): EXPOSE edilmiş tüm portları rastgele map'le
docker run -d -P nginx
docker port $(docker ps -q)
# 80/tcp -> 0.0.0.0:32768
# 443/tcp -> 0.0.0.0:32769Port Çakışması ve Çözümleri
Aynı host portunu iki container kullanamaz. Hemen deneyelim:
docker run -d --name web1 -p 80:80 nginx
docker run -d --name web2 -p 80:80 nginxError: Bind for 0.0.0.0:80 failed: port is already allocatedHost'un 80 portu zaten web1 tarafından kullanılıyor. Çözüm yolları:
Çözüm 1 — Farklı host portu kullan:
docker run -d --name web1 -p 80:80 nginx
docker run -d --name web2 -p 81:80 nginx
docker run -d --name web3 -p 82:80 nginxHer biri container içinde 80 portunu dinliyor ama host'ta farklı portlarda.
Çözüm 2 — Farklı IP'ye bağla:
docker run -d --name web1 -p 10.0.0.1:80:80 nginx
docker run -d --name web2 -p 10.0.0.2:80:80 nginxFarklı IP arayüzlerinde aynı port kullanılabilir. Birden fazla IP'si olan sunucularda bu yöntem kullanılır.
Çözüm 3 — Reverse proxy kullan:
En doğru çözüm genelde budur. Tek bir Nginx reverse proxy dışarıya açılır, arkasındaki servisler port mapping olmadan internal network'te çalışır. Bu pattern'ı birazdan göreceğiz.
Docker Firewall'ı Bypass Eder — Çok Önemli!
Şimdi sana çoğu insanın bilmediği ve potansiyel olarak ciddi güvenlik sorunu yaratan bir konuyu anlatacağım.
Docker, port mapping yaparken iptables kurallarını doğrudan manipüle eder. Bu ne demek? UFW (Uncomplicated Firewall) ile 5432 portunu kapatmış olabilirsin ama Docker açtığında UFW'yi bypass eder!
# UFW ile PostgreSQL portunu kapat
sudo ufw deny 5432
# Ama Docker port mapping açıksa:
docker run -d -p 5432:5432 postgres
# Dışarıdan 5432'ye ERİŞİLEBİLİR! UFW kuralı bypass edildi! 😱Bu ciddi bir güvenlik riski. Veritabanını UFW ile koruduğunu sanıyorsun ama Docker onu deliyor.
Çözüm 1 — Sadece localhost'a bağla (en basit ve etkili):
docker run -d -p 127.0.0.1:5432:5432 postgresÇözüm 2 — DOCKER-USER chain'ine kural ekle:
Docker, iptables'da DOCKER-USER adında özel bir chain oluşturur. Bu chain, Docker'ın kendi kurallarından önce çalışır:
# Dış dünyadan 5432'ye erişimi engelle
sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 5432 -j DROP
# Sadece internal ağdan erişime izin ver
sudo iptables -I DOCKER-USER -i eth0 -s 10.0.0.0/8 -p tcp --dport 5432 -j ACCEPTÇözüm 3 — Port mapping hiç yapma:
En güvenli yöntem. Veritabanı ve cache gibi servisleri port mapping olmadan çalıştır, sadece Docker network üzerinden erişsinler.
# Port mapping YOK — sadece aynı network'teki container'lar erişir
docker run -d --name db --network mynet postgres:16UDP Port Mapping
Şimdiye kadar hep TCP'den bahsettik çünkü web uygulamaları genelde TCP kullanır. Ama DNS gibi servisler UDP kullanır:
# TCP (varsayılan)
docker run -d -p 8080:80 nginx
# = docker run -d -p 8080:80/tcp nginx
# UDP
docker run -d -p 5353:53/udp dns-server
# Her iki protokol
docker run -d -p 53:53/tcp -p 53:53/udp dns-serverPort Kontrolü ve İzleme
Çalışan container'ların port durumlarını kontrol etmek için birkaç komut bilmende fayda var.
Container'ın port mapping'lerini görmek için:
docker port web80/tcp -> 0.0.0.0:8080
443/tcp -> 0.0.0.0:8443Tüm container'ların portlarını tablo halinde görmek için:
docker ps --format "table {{.Names}}\t{{.Ports}}"NAMES PORTS
web 0.0.0.0:8080->80/tcp, 0.0.0.0:8443->443/tcp
api 0.0.0.0:3000->3000/tcp
db 127.0.0.1:5432->5432/tcpHost'ta hangi portların dinlendiğini görmek için:
ss -tlnp | grep dockerBir portun kim tarafından kullanıldığını öğrenmek için:
sudo lsof -i :3000Gerçek Dünya Pattern'ı: Reverse Proxy
Production ortamında en yaygın pattern şudur: sadece reverse proxy dışarıya açılır, arkasındaki servisler internal network'te kalır.
docker network create webnet
# Internal servisler — port mapping YOK
docker run -d --name api --network webnet myapp
docker run -d --name admin --network webnet admin-panel
# Reverse proxy — tek dış erişim noktası
docker run -d --name nginx \
--network webnet \
-p 80:80 \
-p 443:443 \
-v ./nginx.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:alpineNginx konfigürasyonunda:
upstream api {
server api:3000;
}
upstream admin {
server admin:8080;
}
server {
listen 80;
server_name api.myapp.com;
location / {
proxy_pass http://api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 80;
server_name admin.myapp.com;
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://admin;
}
}Bu yapıda:
apiveadmincontainer'larının dışarıya açık portu yokTek giriş noktası Nginx (80/443)
Güvenlik kuralları Nginx'te uygulanır (IP kısıtlaması gibi)
SSL termination Nginx'te yapılır
Bu pattern'ı öğrenmek çok önemli çünkü gerçek production ortamlarının büyük çoğunluğu bu şekilde çalışır.
docker-proxy ve Performans
Docker her port mapping için bir docker-proxy prosesi oluşturur. Bu genelde sorun yaratmaz ama port range mapping'de dikkatli ol:
docker run -d -p 8000-8100:8000-8100 myappBu, 101 adet docker-proxy prosesi oluşturur! Her biri 5-10MB bellek kullanır. 100 port = yaklaşık 500MB-1GB ekstra bellek.
Bu yüzden geniş port aralıkları yerine reverse proxy kullanmak hem performans hem güvenlik açısından daha doğru. Tek bir Nginx arkasına istediğin kadar servis koyabilirsin — sadece 2 port (80 ve 443) yeterli.
Güvenli Port Mapping Kuralları
Pratik bir rehber olarak şunları aklında tut:
# ✅ Web sunucusu — herkese açık, sorun yok
docker run -d -p 80:80 -p 443:443 nginx
# ✅ Veritabanı — sadece localhost
docker run -d -p 127.0.0.1:5432:5432 postgres
# ✅ Admin panel — sadece VPN ağından
docker run -d -p 10.8.0.1:9090:9090 admin-panel
# ❌ Veritabanı herkese açık — TEHLİKELİ!
docker run -d -p 5432:5432 postgres
docker run -d -p 6379:6379 redis
docker run -d -p 27017:27017 mongoVeritabanları (PostgreSQL, MySQL, MongoDB), cache sistemleri (Redis, Memcached), message broker'lar (RabbitMQ) — bunların hiçbirini 0.0.0.0'a açma. Ya 127.0.0.1'e bağla ya da hiç port mapping yapma.
Bu Derste Ne Öğrendik?
Bu derste container'ları dış dünyaya nasıl açacağımızı öğrendik:
Port mapping (
-p HOST:CONTAINER) host portu ile container portunu birbirine bağlar — iptables NAT kurallarıyla.EXPOSE talimatı portu açmaz — sadece belgelemedir; runtime'da
-pflag'i gereklidir.127.0.0.1 binding ile portu sadece localhost'a aç — veritabanları ve hassas servisler için zorunlu.
Docker firewall'ı bypass edebilir — DOCKER-USER chain ile ek güvenlik kuralları ekle.
Production'da reverse proxy pattern kullan: sadece Nginx/Traefik dışarıya açık, servisler internal network'te.
Çok sayıda port mapping performansı olumsuz etkiler — reverse proxy kullanarak tek giriş noktası oluştur.
Bir sonraki derste custom network'lerin ileri seviye özelliklerine dalacağız — internal network, macvlan, IPv6 ve production-ready network mimarileri.
AI Asistan
Sorularını yanıtlamaya hazır