Docker ortamında çalışırken en çok karşılaştığım sorunlardan biri, containerların network izolasyonunu korurken aynı zamanda güvenli bir VPN tüneli üzerinden erişilebilir olmasını sağlamak. Özellikle birden fazla sunucuda dağıtık servisler çalıştırıyorsanız ve bu servislerin birbirleriyle güvenli konuşması gerekiyorsa, WireGuard ile Docker ağını entegre etmek hem performans hem de güvenlik açısından çok iyi bir çözüm sunuyor. Bu yazıda sıfırdan kurulumdan başlayarak production ortamında kullandığım gerçek senaryolara kadar her şeyi ele alacağım.
Neden WireGuard + Docker?
Klasik VPN çözümleri olan OpenVPN veya IPSec’e kıyasla WireGuard, çekirdek seviyesinde çalışması sayesinde çok daha düşük gecikme ve yüksek throughput sağlıyor. Docker tarafında da durum benzer: Container’lar arasındaki network iletişimi zaten Docker’ın bridge ağı üzerinden gidiyor ama bu sadece tek bir host ile sınırlı.
Şöyle bir senaryo düşünün: İstanbul’daki sunucunuzda bir PostgreSQL container’ınız var, Amsterdam’dakinde ise bir uygulama sunucusu. Bu ikisinin birbirleriyle konuşması için ya her şeyi internet üzerinden açıyorsunuz (kötü fikir) ya da bir VPN çözümü kuruyorsunuz. WireGuard burada devreye giriyor ve her iki taraftaki Docker networklerini sanki aynı yerel ağdaymış gibi birbirine bağlıyor.
Temel avantajlar:
- Kernel space’de çalıştığı için iperf testlerinde OpenVPN’e göre 3-4 kat daha iyi throughput
- Konfigürasyon dosyası son derece sade, 10 satırla çalışır hale gelir
- Docker’ın overlay network’ü yerine gerçek bir şifreli tünel sağlar
- Multi-host Docker ortamlarında Swarm ya da Kubernetes olmadan da çalışır
Temel Kavramlar ve Mimari
Kuruluma geçmeden önce mimariden bahsetmek istiyorum çünkü kafada net bir resim olmadan yapılan kurulumlar genellikle yarım kalıyor.
WireGuard, her node için bir arayüz oluşturuyor (örneğin wg0). Bu arayüz üzerinden gelen/giden trafik şifreleniyor. Docker’ın network’leri ise docker0 bridge’i veya özel oluşturulan bridge network’leri üzerinden çalışıyor.
Yapacağımız şey şu: WireGuard tüneli üzerinden iki sunucuyu birbirine bağlayacağız, ardından her sunucudaki Docker subnet’ini karşı tarafa route edeceğiz. Böylece İstanbul’daki container, Amsterdam’daki container’ın IP’sine direkt erişebilecek.
Örnek mimarimiz:
- Sunucu A (İstanbul): Public IP
1.2.3.4, WireGuard IP10.0.0.1, Docker subnet172.20.0.0/24 - Sunucu B (Amsterdam): Public IP
5.6.7.8, WireGuard IP10.0.0.2, Docker subnet172.21.0.0/24
Sistem Hazırlığı
Her iki sunucuda da kernel modülünü yükleyelim ve gerekli paketleri kuralım.
# Ubuntu/Debian için
apt update && apt install -y wireguard wireguard-tools
# Kernel modülünü yükle
modprobe wireguard
# Modülün kalıcı olması için
echo "wireguard" >> /etc/modules-load.d/wireguard.conf
# IP forwarding'i etkinleştir (kritik!)
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "net.ipv4.conf.all.proxy_arp=1" >> /etc/sysctl.conf
sysctl -p
IP forwarding olmadan container trafiğinin WireGuard arayüzünden geçmesi mümkün değil. Bu adımı atlayan sistemlerin yarısı bu yüzden çalışmıyor.
WireGuard Anahtar Üretimi
Her sunucu için ayrı private/public key çifti oluşturuyoruz.
# Her iki sunucuda da çalıştır
cd /etc/wireguard
# Key üretimi
wg genkey | tee privatekey | wg pubkey > publickey
# İzinleri kısıtla
chmod 600 privatekey
chmod 644 publickey
# Key'leri görüntüle
echo "Private key: $(cat privatekey)"
echo "Public key: $(cat publickey)"
Bu komutları çalıştırdıktan sonra her iki sunucunun public key’ini not alın. Sunucu A’nın public key’ini Sunucu B’ye, Sunucu B’ninkini de Sunucu A’ya ekleyeceğiz.
WireGuard Konfigürasyonu
Sunucu A (İstanbul) Konfigürasyonu
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = <SUNUCU_A_PRIVATE_KEY>
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <SUNUCU_B_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32, 172.21.0.0/24
Endpoint = 5.6.7.8:51820
PersistentKeepalive = 25
EOF
Sunucu B (Amsterdam) Konfigürasyonu
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = <SUNUCU_B_PRIVATE_KEY>
Address = 10.0.0.2/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <SUNUCU_A_PUBLIC_KEY>
AllowedIPs = 10.0.0.1/32, 172.20.0.0/24
Endpoint = 1.2.3.4:51820
PersistentKeepalive = 25
EOF
AllowedIPs kısmına dikkat edin. Hem WireGuard peer IP’sini hem de karşı taraftaki Docker subnet’ini ekliyoruz. Bu sayede Docker container trafiği WireGuard tünelinden geçiyor.
Docker Network Yapılandırması
Şimdi her sunucuda ayrı subnet’lere sahip özel Docker network’leri oluşturacağız. Bu önemli çünkü iki sunucudaki Docker networklerinin aynı subnet’te olması routing karmaşasına yol açıyor.
# Sunucu A'da
docker network create
--driver bridge
--subnet 172.20.0.0/24
--gateway 172.20.0.1
--opt com.docker.network.bridge.name=docker_vpn
vpn_network
# Sunucu B'de
docker network create
--driver bridge
--subnet 172.21.0.0/24
--gateway 172.21.0.1
--opt com.docker.network.bridge.name=docker_vpn
vpn_network
com.docker.network.bridge.name parametresiyle bridge arayüzüne özel bir isim veriyoruz. Bu ilerleyen adımlarda iptables kuralları yazarken hayat kurtarıyor.
Routing Kuralları
WireGuard arayüzü ayağa kalktıktan sonra Docker subnet’lerine giden trafiğin doğru arayüzden çıkmasını sağlamak için route eklemeliyiz.
# Sunucu A'da - Amsterdam Docker subnet'ine route ekle
ip route add 172.21.0.0/24 via 10.0.0.2 dev wg0
# Bu route'u kalıcı yapmak için /etc/wireguard/wg0.conf'a PostUp ekle
# PostUp satırını şöyle güncelleyebilirsiniz:
# PostUp = ip route add 172.21.0.0/24 via 10.0.0.2 dev wg0; iptables ...
# PostDown = ip route del 172.21.0.0/24; iptables ...
# Sunucu B'de - İstanbul Docker subnet'ine route ekle
ip route add 172.20.0.0/24 via 10.0.0.1 dev wg0
Docker Compose ile Entegrasyon
Gerçek dünyada containerları tek tek çalıştırmak yerine Docker Compose kullanıyoruz. İşte iki sunucu arasındaki servis iletişimini gösteren bir compose dosyası.
Sunucu A (İstanbul) – docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:15
container_name: postgres_main
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: guclu_sifre_buraya
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
vpn_network:
ipv4_address: 172.20.0.10
# Sadece VPN üzerinden erişilebilir, dışarıya port açma!
redis:
image: redis:7-alpine
container_name: redis_cache
networks:
vpn_network:
ipv4_address: 172.20.0.11
networks:
vpn_network:
external: true
volumes:
postgres_data:
Sunucu B (Amsterdam) – docker-compose.yml:
version: '3.8'
services:
webapp:
image: myapp:latest
container_name: webapp
environment:
DATABASE_URL: postgresql://appuser:[email protected]:5432/appdb
REDIS_URL: redis://172.20.0.11:6379
ports:
- "80:8080"
- "443:8443"
networks:
vpn_network:
ipv4_address: 172.21.0.10
depends_on:
- nginx
nginx:
image: nginx:alpine
container_name: nginx_proxy
networks:
vpn_network:
ipv4_address: 172.21.0.11
networks:
vpn_network:
external: true
Bu yapılandırmada webapp container’ı, İstanbul’daki PostgreSQL’e sanki yerel ağdaymış gibi 172.20.0.10 IP’siyle bağlanıyor. Tüm trafik WireGuard tünelinden şifreli geçiyor.
WireGuard’ı Container İçinde Çalıştırmak
Bazen host seviyesinde WireGuard kurmak yerine WireGuard’ı bir container içinde çalıştırmak isteyebilirsiniz. Bu özellikle managed cloud ortamlarında ya da kernel modülü yükleyemediğiniz durumlarda işe yarıyor.
# wg-easy gibi hazır bir image kullanabilirsiniz
docker run -d
--name wireguard
--cap-add NET_ADMIN
--cap-add SYS_MODULE
--sysctl net.ipv4.ip_forward=1
--sysctl net.ipv4.conf.all.src_valid_mark=1
-e WG_HOST=1.2.3.4
-e PASSWORD=admin_sifreniz
-e WG_DEFAULT_ADDRESS=10.0.0.x
-e WG_DEFAULT_DNS=1.1.1.1
-p 51820:51820/udp
-p 51821:51821/tcp
-v ~/.wireguard:/etc/wireguard
--restart unless-stopped
ghcr.io/wg-easy/wg-easy
Bu yaklaşımda NET_ADMIN ve SYS_MODULE capability’leri kritik. Bunlar olmadan container network interface oluşturamaz.
Güvenlik Hardening
VPN kurulumu tek başına yeterli değil. Production ortamında şu iptables kurallarını da eklemenizi öneririm:
# Sadece WireGuard portuna izin ver
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
# WireGuard interface'inden gelen trafiğe izin ver
iptables -A INPUT -i wg0 -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -j ACCEPT
# Docker VPN network'ünden internet'e çıkışı engelle (sadece peer-to-peer)
iptables -A FORWARD -s 172.20.0.0/24 -d 0.0.0.0/0 -o eth0 -j DROP
iptables -A FORWARD -s 172.21.0.0/24 -d 0.0.0.0/0 -o eth0 -j DROP
# Bu kuralları kalıcı yap
apt install -y iptables-persistent
netfilter-persistent save
Son iki kural önemli: Docker container’larının WireGuard VPN dışındaki trafiği internet üzerinden gitmesini engelliyoruz. Böylece container bir şekilde yanlış bir IP’ye bağlanmaya çalışırsa trafik internete çıkmıyor.
Bağlantı Testi ve Doğrulama
Kurulum sonrası her şeyin çalıştığını doğrulamak için:
# WireGuard servisini başlat
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
# Bağlantı durumunu kontrol et
wg show
# Peer'ın görünüp görünmediğini kontrol et
wg show wg0 peers
# Route tablosunu doğrula
ip route show | grep 172.2
# Sunucu A'dan Sunucu B'deki container'a ping at
ping 172.21.0.10
# Container içinden test
docker exec -it webapp ping 172.20.0.10
docker exec -it webapp curl http://172.20.0.10:5432 # PostgreSQL portuna erişim testi
wg show komutunun çıktısında latest handshake satırı görüyorsanız bağlantı kurulmuş demektir. Eğer bu satır yoksa iki peer henüz el sıkışamamış, büyük ihtimalle firewall veya routing problemi var.
Sorun Giderme
Karşılaştığım en yaygın sorunlar ve çözümleri:
Container’lar birbirini göremiyor:
- Her iki sunucuda da
ip_forwardaktif mi kontrol et:sysctl net.ipv4.ip_forward - Docker network’lerin farklı subnet’lerde olduğunu doğrula
wg showile handshake gerçekleşmiş mi baktcpdump -i wg0 icmpile paketin arayüze ulaşıp ulaşmadığını kontrol et
WireGuard handshake gerçekleşmiyor:
- UDP 51820 portunu firewall’da açtığından emin ol
- Endpoint IP ve portun doğru yazıldığını kontrol et
- Key’lerin doğru sunuculara atandığını doğrula (yanlış tarafa atamak çok sık yapılan hata)
Container restart sonrası route kayboluyor:
- Route ekleme işlemini WireGuard’ın
PostUpdirektifine taşı - Docker network oluşturma işlemini de bir startup script’e al
Docker ağ adreslemesi çakışıyor:
docker network inspect vpn_networkile subnet’i doğrula- Gerekirse
docker network rm vpn_networkyapıp farklı subnet ile yeniden oluştur
Production’da Kullandığım Otomasyon
Bu kurulumu her seferinde elle yapmak yerine basit bir shell script’e döktüm:
#!/bin/bash
# wireguard-docker-setup.sh
set -e
SERVER_ROLE=$1 # "istanbul" veya "amsterdam"
PEER_PUBLIC_IP=$2
PEER_PUBLIC_KEY=$3
if [ "$SERVER_ROLE" = "istanbul" ]; then
WG_IP="10.0.0.1/24"
DOCKER_SUBNET="172.20.0.0/24"
PEER_WG_IP="10.0.0.2"
PEER_DOCKER_SUBNET="172.21.0.0/24"
else
WG_IP="10.0.0.2/24"
DOCKER_SUBNET="172.21.0.0/24"
PEER_WG_IP="10.0.0.1"
PEER_DOCKER_SUBNET="172.20.0.0/24"
fi
# Key üret
cd /etc/wireguard
wg genkey | tee privatekey | wg pubkey > publickey
chmod 600 privatekey
PRIVATE_KEY=$(cat privatekey)
PUBLIC_KEY=$(cat publickey)
echo "Bu sunucunun Public Key'i: $PUBLIC_KEY"
echo "Peer konfigürasyonuna bu key'i ekleyin."
# Konfigürasyon yaz
cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = $PRIVATE_KEY
Address = $WG_IP
ListenPort = 51820
PostUp = ip route add $PEER_DOCKER_SUBNET via $PEER_WG_IP dev wg0; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip route del $PEER_DOCKER_SUBNET; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = $PEER_PUBLIC_KEY
AllowedIPs = $PEER_WG_IP/32, $PEER_DOCKER_SUBNET
Endpoint = $PEER_PUBLIC_IP:51820
PersistentKeepalive = 25
EOF
# Docker network oluştur
docker network create
--driver bridge
--subnet $DOCKER_SUBNET
--opt com.docker.network.bridge.name=docker_vpn
vpn_network 2>/dev/null || echo "Network zaten mevcut"
# Servisi başlat
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
echo "Kurulum tamamlandı. WireGuard durumu:"
wg show
Bu script’i şöyle çalıştırıyorum:
chmod +x wireguard-docker-setup.sh
./wireguard-docker-setup.sh istanbul 5.6.7.8 <SUNUCU_B_PUBLIC_KEY>
Sonuç
WireGuard ile Docker ağını entegre etmek başlangıçta karmaşık görünse de temel mantığı kavradıktan sonra oldukça şık bir çözüm ortaya çıkıyor. Kernel seviyesinde çalışan WireGuard, container trafiğini şifreleyerek taşırken performanstan neredeyse hiç ödün vermiyor.
Bu yazıda anlattığım kurulumla elde ettiğiniz şeyler:
- Farklı fiziksel sunuculardaki Docker container’ları arasında şifreli, düşük gecikmeli iletişim
- Veritabanı gibi hassas servislerin internete açık port olmadan erişilebilmesi
- İptables kurallarıyla kontrol altında tutulan, sıkılaştırılmış bir network güvenlik katmanı
- Kolayca genişletilebilir bir yapı (üçüncü bir sunucu eklemek
[Peer]bloğu eklemekten ibaret)
Birden fazla sunucuya çıkmanız gerektiğinde veya mevcut multi-host Docker ortamınızı güvenli hale getirmek istediğinizde bu yaklaşımı kesinlikle tavsiye ediyorum. Swarm veya Kubernetes gibi orkestrasyon araçları kullanmıyorsanız bu yöntem, sizi gereksiz karmaşıklıktan korurken güvenliği garantiliyor.