Docker ile yayınladığınız bir port, UFW'de kapalı olsa bile internetten erişilebilir olabilir. Docker belgelerine göre bunun sebebi, Docker'ın container trafiğini nat tablosunda yönlendirmesi ve paketlerin UFW'nin kullandığı INPUT ve OUTPUT zincirlerine hiç ulaşmamasıdır; belgeler Docker ile UFW'yi bu yüzden birbiriyle uyumsuz sayar. Çözüm UFW'yi kurcalamak değil, portu yalnızca gerekiyorsa ve mümkünse 127.0.0.1 ile yayınlamak, dış erişimi kısıtlamak gerekiyorsa da kuralları Docker'ın ayırdığı DOCKER-USER zincirine (nftables arka ucunda kendi tablonuza) eklemektir.

Bu yazı konunun yalnızca Docker tarafını anlatır: Docker'ın oluşturduğu iptables zincirleri, FORWARD politikası, DOCKER-USER ile kaynak IP kısıtlama, Docker Engine 29 ile deneysel olarak gelen nftables arka ucu, firewalld ile bütünleşme ve kural oluşturmayı kapatmanın sonuçları. UFW'nin kendisini Ubuntu UFW güvenlik duvarı rehberinde, genel sunucu sertleştirmeyi VPS güvenlik sertleştirme yazısında bulabilirsiniz. Serinin tamamı için Docker öğrenme rehberine bakın.

Docker neden güvenlik duvarı kuralı oluşturur?

Linux üzerinde Docker; ağ yalıtımı, port yayınlama ve filtreleme için güvenlik duvarı kuralları oluşturur. Belgelere göre bu kurallar bridge ağlarının doğru çalışması için gereklidir ve Docker'ın oluşturduğu kuralları değiştirmemelisiniz. Kurallar yalnızca bridge ağları için oluşturulur; ipvlan, macvlan ve host ağları için hiç kural oluşturulmaz. Bu ağları kullanıyorsanız erişim denetimi tamamen size kalır.

Docker kurallarını varsayılan olarak iptables ile oluşturur. Docker Engine 29.0.0'dan itibaren deneysel bir nftables desteği de vardır. Belgelere göre bridge ağları için iki arka ucun işlevi aynıdır; hangisinin kullanılacağı daemon'ın firewall-backend seçeneğiyle belirlenir. Engine 29.0.0 sürüm notlarına göre Linux'ta docker info komutu da kullanılan güvenlik duvarı arka ucunu raporlar.

Docker ve UFW: paketler neden UFW'yi atlar?

UFW (Uncomplicated Firewall), Ubuntu ile gelen ve güvenlik duvarı kurallarını yönetmeyi kolaylaştıran bir ön yüzdür. Belgelerin açıklaması kısadır: bir container portunu Docker ile yayınladığınızda o container'a giden ve ondan gelen trafik UFW ayarlarından geçmeden önce başka yöne çevrilir. Docker container trafiğini nat tablosunda yönlendirdiği için paketler UFW'nin kullandığı INPUT ve OUTPUT zincirlerine varmadan yönlendirilmiş olur. Sonuçta güvenlik duvarı yapılandırmanız bu trafik için fiilen yok sayılır.

Pratik sonuç şudur: ufw deny 8080 yazıp docker run -p 8080:80 ... çalıştırırsanız 8080 portu dışarıdan erişilebilir kalır. Compose başvuru belgesi de aynı uyarıyı yapar: ports girdisinde sunucu IP'si belirtmezseniz Docker bütün arayüzlere bağlanır ve sunucu güvenlik duvarı kurallarını atlar; sunucunun genel bir IP adresi varsa container doğrudan internete açılabilir.

iptables arka ucu: Docker'ın zincirleri

iptables arka ucu: Docker'ın zincirleri: DOCKER-USER, DOCKER-FORWARD, DOCKER, DOCKER-BRIDGE, DOCKER-INTERNAL, DOCKER-CT

Docker bridge ağları için sunucunun ağ ad alanında iptables kuralları oluşturur. Bridge ve diğer ağ türleri için DNS ile ilgili kurallar ayrıca container'ın ağ ad alanında da oluşturulur. Bridge ve overlay ağlarını desteklemek için filter tablosunda şu özel zincirler bulunur:

ZincirGörevi
DOCKER-USERKullanıcı kuralları için ayrılmış yer; DOCKER-FORWARD ve DOCKER zincirlerinden önce işlenir
DOCKER-FORWARDDocker ağları için ilk işleme aşaması; kurulu bağlantılara ait paketleri kabul eder, diğerlerini Docker zincirlerine geçirir
DOCKER, DOCKER-BRIDGE, DOCKER-INTERNALKurulu bağlantıya ait olmayan paketin, çalışan container'ların port yönlendirmesine göre kabul edilip edilmeyeceğine karar verir
DOCKER-CTKöprü başına bağlantı izleme (connection tracking) kuralları
DOCKER-INGRESSSwarm ağlarıyla ilgili kurallar

Docker, FORWARD zincirine koşulsuz olarak DOCKER-USER, DOCKER-FORWARD ve DOCKER-INGRESS zincirlerine atlayan kurallar ekler. nat tablosunda ise DOCKER zincirini oluşturur ve masquerading ile port eşlemeyi burada uygular. Engine 29.0.0 sürüm notlarına göre bridge ağlarının iptables kuralları güncellenmiş, DOCKER-ISOLATION-STAGE-1 ve DOCKER-ISOLATION-STAGE-2 zincirleri kaldırılmıştır; eski yazılarda bu adları görürseniz güncel olmadıklarını bilin.

FORWARD politikası ve IP yönlendirme

Docker, varsayılan bridge yapılandırması için sunucuda IP yönlendirmenin (IP forwarding) açık olmasını ister. Başlarken net.ipv4.ip_forward ve net.ipv6.conf.all.forwarding ayarları kapalıysa bunları açar. Açtığında aynı zamanda filter tablosundaki FORWARD zincirinin varsayılan politikasını DROP yapar; böylece açıkça kabul edilmeyen yönlendirilmiş paketler düşürülür. Belgeler bunun Docker sunucusunun bir yönlendirici (router) gibi davranmasını engellediğini ve yönlendirici işlevi gerekmedikçe önerilen ayar olduğunu söyler. Bu davranışı istemiyorsanız /etc/docker/daemon.json içine "ip-forward-no-drop": true ekleyebilir ya da dockerd'ye --ip-forward-no-drop verebilirsiniz.

FORWARD politikası DROP iken sunucu arayüzleri arasında yönlendirmeye izin vermek için belgeler DOCKER-USER içinde bir kural önerir:

bash
iptables -I DOCKER-USER -i src_if -o dst_if -j ACCEPT

DOCKER-USER ile dış erişimi kısıtlamak

DOCKER-USER ile dış erişimi kısıtlamak: Özgün hedef adresle eşleşmek: conntrack, Doğrudan yönlendirilmiş paketler DOCKER-USER'a ulaşmaz

Docker'ın özel zincirlerinde kabul edilen ya da reddedilen paketler, FORWARD zincirinin sonuna eklediğiniz kurallara hiç ulaşmaz; FORWARD'a sonradan eklenen kurallar Docker'ın kurallarından sonra işlenir. Bu yüzden bu paketleri ek kurallarla süzmek için DOCKER-USER zinciri kullanılır. Varsayılan olarak bütün dış kaynak IP'leri, Docker sunucusunun adreslerine yayınlanmış portlara bağlanabilir. Yalnızca belirli bir IP'ye ya da ağa izin vermek için zincirin başına olumsuzlanmış bir kural eklenir:

bash
# 192.0.2.2 dışındaki bütün adreslerden gelen paketleri düşür
iptables -I DOCKER-USER -i ext_if ! -s 192.0.2.2 -j DROP

# Yalnızca 192.0.2.0/24 alt ağına izin ver
iptables -I DOCKER-USER -i ext_if ! -s 192.0.2.0/24 -j DROP

# Bir IP aralığına izin ver (iprange modülü gerekir)
iptables -I DOCKER-USER -m iprange -i ext_if ! --src-range 192.0.2.1-192.0.2.3 -j DROP

ext_if yerine sunucunuzun gerçek dış arayüz adını yazmalısınız. -s ya da --src-range seçeneklerini -d ya da --dst-range ile birleştirerek hem kaynağı hem hedefi denetleyebilirsiniz; örneğin sunucunun iki IPv6 adresinden biri için kural koyup diğerini açık bırakabilirsiniz.

Bir ayrıntı kolayca unutulur: container'lar izin verilen aralığın dışındaki makinelere DNS ya da HTTP istekleri gönderebilir ve bu isteklerin yanıtlarının da geçebilmesi gerekir. Belgeler daha önce kabul edilmiş bir akışa ait gelen ve giden paketleri kabul eden bir kural önerir ve bu kuralın DROP kurallarından önce yer alması gerektiğini vurgular. -I zincirin başına eklediği için bu kuralı DROP kurallarından sonra çalıştırmak onu en üste taşır:

bash
iptables -I DOCKER-USER -m state --state RELATED,ESTABLISHED -j ACCEPT

Özgün hedef adresle eşleşmek: conntrack

Paketler DOCKER-USER zincirine geldiğinde hedef adres dönüştürmesinden (DNAT) zaten geçmiştir. Bu yüzden burada yazdığınız iptables eşleşmeleri yalnızca container'ların iç IP adreslerini ve portlarını görür. İstemcinin bağlandığı özgün IP ve porta göre eşleşmek istiyorsanız conntrack uzantısını kullanmalısınız:

bash
sudo iptables -I DOCKER-USER -p tcp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdst 198.51.100.2 --ctorigdstport 80 -j ACCEPT

Belgeler conntrack uzantısının performansı düşürebileceği konusunda uyarır. Kuralları gerektiği kadar dar tutmak ve gereksiz yere çoğaltmamak iyi bir alışkanlıktır.

Doğrudan yönlendirilmiş paketler DOCKER-USER'a ulaşmaz

Varsayılan olarak uzak makineler bir container'a yalnızca Docker sunucusunun adreslerinden birine yayınlanmış bir port üzerinden ulaşabilir. Paketleri doğrudan container'ın IP adresine göndermek ("direct routed" erişim) izinli değildir. Belgelere göre bu paketler raw tablosunun PREROUTING zincirindeki bir kuralla, yani filter tablosundan önce düşürülür; DOCKER-USER'a hiç ulaşmazlar ve oradaki bir kural onlara izin veremez. Yayınlanmış portlara doğrudan erişime izin vermek için ağ seçeneği com.docker.network.bridge.trusted_host_interfaces ya da daemon seçeneği allow-direct-routing, bütün portlar için ise ağ geçidi modları kullanılır; ayrıntılar port yayınlama yazısında.

nftables arka ucu (Engine 29, deneysel)

Docker Engine 29.0.0 ile nftables desteği deneysel olarak geldi. Belgeler yapılandırma seçeneklerinin, davranışın ve uygulamanın gelecek sürümlerde değişebileceği uyarısını yapar. Overlay ağlarının kuralları henüz iptables'tan taşınmadığı için daemon Swarm modunda çalışırken nftables etkinleştirilemez. Etkinleştirmek için komut satırında --firewall-backend=nftables ya da yapılandırma dosyasında şu anahtar kullanılır:

json
{
  "firewall-backend": "nftables"
}

nftables arka ucunda Docker, bridge ağları için ip docker-bridges ve ip6 docker-bridges adlı iki tablo oluşturur. Her tabloda birkaç temel zincir (base chain) ve her bridge ağı için ek zincirler bulunur. Belgeler bu tabloları doğrudan değiştirmemenizi söyler: Docker tabloların tamamen kendisine ait olmasını bekler ve değişiklikler muhtemelen kaybolur. iptables ve ip6tables daemon seçenekleri bu arka uçta da geçerlidir ve nftables kurallarının oluşturulmasını kapatır.

DOCKER-USER yok: kendi tablonuzu yazın

iptables'ta zincir kümesi sabit olduğu için DOCKER-USER, filter tablosunun FORWARD zincirine Docker'dan önce çalışacak kurallar eklemenin yoludur. nftables uygulamasında DOCKER-USER zinciri yoktur. Bunun yerine kurallarınızı ayrı tablolara, Docker'ın temel zincirleriyle aynı türde ve aynı kanca (hook) noktasında temel zincirler tanımlayarak eklersiniz. Kurallarınızın Docker'ınkilerden önce çalışması için daha düşük, sonra çalışması için daha yüksek bir öncelik değeri verirsiniz. Belgelerdeki örnek yalnızca belirli bir alt ağın container'lara erişmesine izin verir ve Docker'ın kullanıcı tanımlı ağlarının varsayılan br- önekiyle hedefi daraltır:

bash
table ip my-table {
	chain my-filter-forward {
		type filter hook forward priority filter; policy accept;
		iifname "ext_if" oifname "br-*" ip saddr != 192.0.2.0/24 counter drop
	}
}

nftables'ta bir "accept" kararı nihai değildir: yalnızca kendi temel zincirinin işlenmesini bitirir, kabul edilen paket diğer temel zincirlerde yine düşürülebilir. Bu yüzden iptables'ta Docker'ın DROP kuralını geçersiz kılmak için kullanılan ACCEPT kuralları nftables'ta çalışmaz. Docker'ın düşürme kuralını aşmanın yolu bir güvenlik duvarı işaretidir (firewall mark): sunucuda kullanılmayan bir işaret seçip daemon'a --bridge-accept-fwmark ile verirsiniz; örneğin --bridge-accept-fwmark=1 ya da maskeli --bridge-accept-fwmark=0x1/0x3. Ardından paketi kabul etmek yerine bu işareti eklersiniz. İşaret Docker'ın kuralları çalışmadan önce eklenmelidir; filter türünde ve forward kancasında bir zincirde ekliyorsanız önceliği filter - 1 ya da daha düşük olmalıdır. Bu seçenek Engine 29.0.0 sürüm notlarında yeni bir daemon seçeneği olarak listelenir.

iptables'tan nftables'a geçiş

iptables arka ucuyla çalışan bir daemon nftables ile yeniden başlatılınca Docker'ın iptables zincirlerinin ve kurallarının çoğu silinir, yerine nftables kuralları oluşturulur. Belgeler geçişte üç noktaya dikkat çeker:

  • IP yönlendirme: nftables arka ucunda Docker IP yönlendirmeyi kendisi açmaz. Gerekiyorsa ve kapalıysa hata verir; varsayılan bridge nedeniyle IPv4 yönlendirme kapalıysa hata daemon başlarken görülür. net.ipv4.ip_forward=1 ve net.ipv6.conf.all.forwarding=1 sysctl ayarlarını siz yaparsınız; systemd kullanan sunucularda /etc/sysctl.d/99-sysctl.conf gibi bir dosya kullanılabilir. Kontrolü kapatmak için "ip-forward": false verilebilir, ama sürüm notlarına göre bu durumda port yönlendirme dahil bazı bridge işlevleri çalışmayabilir.
  • FORWARD politikası: iptables'ta politikası DROP olan bir FORWARD zinciri, Docker'ın nftables kurallarının kabul ettiği paketleri de düşürür. Docker'ı geçiş için durdurduğunuzda daha önce bu politikayı DROP yapmış olabilir; port yayınlama gibi özellikler bu politika kaldırılana ya da Docker trafiğine izin veren kurallar eklenene kadar çalışmaz. Politika yeniden başlatmada zaten kalkar.
  • DOCKER-USER kuralları: nftables ile başlayan daemon FORWARD'dan DOCKER-USER'a atlamayı eklemez. Önceden kalan atlama yeniden başlatmaya kadar çalışmaya devam eder; sonra kurallarınız yok sayılır. Kuralları kendi nftables tablonuza taşımanız gerekir.

Mevcut politikayı kontrol etmek ve gerekirse ACCEPT yapmak için belgelerdeki komutlar şunlardır (çıktı belgedeki örnektir):

bash
iptables -L FORWARD
Chain FORWARD (policy DROP)
target     prot opt source               destination
ip6tables -L FORWARD
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

iptables -P FORWARD ACCEPT
ip6tables -P FORWARD ACCEPT

firewalld ile bütünleşme

iptables ya da ip6tables seçenekleri true iken sistemde firewalld etkinse Docker, olağan kurallarına ek olarak hedefi ACCEPT olan docker adlı bir firewalld bölgesi (zone) oluşturur. Docker'ın oluşturduğu bütün bridge arayüzleri (örneğin docker0) bu bölgeye eklenir ve herhangi bir bölgeden docker bölgesine yönlendirmeye izin veren docker-forwarding adlı bir yönlendirme politikası oluşturulur. Engine 29.9.1 sürüm notlarına göre iptables ve ip6tables ikisi de kapalıyken bridge arayüzleri artık bu bölgeye eklenmez; aynı sürüm, köprüsü docker dışında bir bölgeye bağlı bridge ağlarının daemon başlarken geri yüklenememesi sorununu da düzeltir. Engine 29.9.3 ise firewalld Docker'ın iptables kurallarını temizledikten sonra Swarm overlay ağlarında şifreleme ve sahteciliğe karşı korumaların eksik kalmasıyla ilgili bir güvenlik açığını kapatır. firewalld kullanıyorsanız Engine'i güncel tutmak bu yüzden önemlidir.

Kural oluşturmayı kapatmak neden önerilmez?

daemon.json içinde iptables ya da ip6tables anahtarlarını false yapmak, Docker'ın iptables ya da nftables kurallarının çoğunu oluşturmasını engeller. Belgeler bunun çoğu kullanıcı için uygun olmadığını ve container ağını büyük olasılıkla bozacağını söyler. Örnek olarak verilen sonuç dikkat çekicidir: Docker'ın güvenlik duvarı kapalıyken ve yerine kural yazılmamışsa bridge ağlarındaki container'lar masquerading ile internete çıkamaz, ama bütün portları yerel ağdaki makinelere açık olur. Ayrıca Docker'ın kural oluşturmasını tamamen engellemek mümkün değildir ve kuralları sonradan elle oluşturmak belgelerin ifadesiyle son derece karmaşıktır. Yani "UFW çalışsın diye Docker'ın iptables'ını kapatayım" yaklaşımı güvenliği artırmaz, tersine daha geniş bir açık bırakabilir.

Sunucu için güvenli bir düzen

Belgelerdeki araçları birleştirince tek bir VPS için sade bir düzen ortaya çıkar: dışarıya yalnızca reverse proxy'nin 80 ve 443 portlarını yayınlayın, yönetim arayüzlerini loopback'e bağlayın, veritabanını internal bir ağa koyun ve kaynak IP kısıtlaması gerekiyorsa bunu DOCKER-USER (ya da nftables tablonuz) ile yapın. Aşağıdaki Compose dosyası bu düzeni kurar ve Docker CLI 29.8.1, Docker Compose v5.5.1 ile docker compose config --quiet doğrulamasından geçmiştir:

yaml
services:
  proxy:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    networks:
      - edge
      - app
  app:
    image: example/app:latest
    networks:
      - app
  admin:
    image: example/admin:latest
    ports:
      - "127.0.0.1:9000:9000"
    networks:
      - app
  db:
    image: postgres:18
    networks:
      - data
      - app

networks:
  edge:
  app:
  data:
    internal: true

Burada yalnızca proxy dış dünyaya açıktır. app hiç port yayınlamaz; proxy ona aynı app ağı üzerinden servis adıyla ulaşır. admin paneline yalnızca sunucunun kendisinden (örneğin bir SSH tüneliyle) erişilebilir. db de port yayınlamaz; data ağı internal: true olduğu için Compose belgelerine göre bu ağın dış bağlantı için varsayılan ağ geçidi yoktur. Docker soketinin korunması gibi ilgili konular Docker daemon uzak erişim ve Portainer kurulumu yazılarında ele alınıyor.

Sık yapılan hatalar

Sık yapılan hatalar: Kuralları FORWARD zincirinin sonuna eklemek, DOCKER-USER'da sunucu portuna göre eşleşmek, RELATED,ESTABLISHED kuralını unutmak, Docker'ın nftables tablolarını...
  • Kuralları FORWARD zincirinin sonuna eklemek: Docker'ın zincirlerinde karar verilmiş paketler bu kurallara ulaşmaz; DOCKER-USER kullanın.
  • DOCKER-USER'da sunucu portuna göre eşleşmek: paketler DNAT'tan geçtiği için yalnızca container adresi ve portu görünür; özgün hedef için conntrack gerekir.
  • RELATED,ESTABLISHED kuralını unutmak: kaynak kısıtlaması koyduktan sonra container'ların dışarıya yaptığı isteklerin yanıtları düşebilir.
  • Docker'ın nftables tablolarını düzenlemek: değişiklikler kaybolur; kendi tablonuzu yazın.
  • Swarm modunda nftables denemek: belgelere göre bu birleşim şimdilik desteklenmez.
  • iptables'ı kapatmak: container ağı bozulur ve portlar yerel ağa açık kalabilir.

Sık Sorulan Sorular

UFW açıkken Docker portları neden erişilebilir?

Docker yayınlanan container trafiğini nat tablosunda yönlendirir; paketler UFW'nin kullandığı INPUT ve OUTPUT zincirlerine varmadan başka yöne çevrilir. Bu yüzden UFW kuralları bu trafiğe uygulanmaz.

Yalnızca kendi IP'mden erişilen bir container portu nasıl yaparım?

iptables arka ucunda iptables -I DOCKER-USER -i ext_if ! -s <IP> -j DROP biçiminde bir kural ve öncesinde RELATED,ESTABLISHED kabul kuralı eklersiniz. nftables arka ucunda aynı mantığı kendi tablonuzdaki forward kancalı bir zincirle kurarsınız.

nftables'a geçmeli miyim?

Belgeler nftables desteğini Engine 29'da deneysel olarak tanımlar ve seçeneklerin değişebileceğini belirtir; Swarm modunda kullanılamaz. Geçiş IP yönlendirme, FORWARD politikası ve DOCKER-USER kurallarının taşınmasını gerektirir.

Hangi arka ucu kullandığımı nasıl görürüm?

Engine 29.0.0 sürüm notlarına göre Linux'ta docker info komutu kullanılabiliyorsa güvenlik duvarı arka ucunu raporlar.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Docker öğrenme rehberi · Ubuntu UFW rehberi · Docker port yayınlama · Docker network türleri · Docker daemon uzak erişim · VPS güvenlik sertleştirme