Docker Compose ile çalışırken en çok kafanın karıştığı konulardan biri servisler arası iletişimin nasıl kurulduğudur. “Nginx container’ım neden backend’e ulaşamıyor?”, “Veritabanı bağlantısı neden localhost değil?” gibi sorular her yeni Compose kullanıcısının önüne çıkar. Bu yazıda Compose ağ yapılandırmasını derinlemesine ele alacağız, gerçek dünya senaryolarıyla pekiştireceğiz.
Compose Ağ Modelini Anlamak
Docker Compose, varsayılan olarak her proje için otomatik bir bridge network oluşturur. Bu network’ün adı _default formatında olur. Yani myapp klasöründe çalıştırdığın Compose dosyası, arka planda myapp_default adında bir sanal ağ yaratır.
Bu modelin güzel tarafı şu: Aynı Compose dosyasındaki tüm servisler bu network’e otomatik olarak dahil edilir ve birbirlerine servis adıyla erişebilirler. 172.17.0.x gibi IP adreslerini ezberlemene gerek yok. nginx servisi, backend servisine doğrudan http://backend:3000 şeklinde ulaşabilir.
# Oluşturulan network'leri görmek için
docker network ls
# Belirli bir network'ün detaylarını incelemek için
docker network inspect myapp_default
Şimdi bu temeli daha ileriye taşıyalım.
Servis Adıyla İletişim: Temel Senaryo
En basit örneğimizle başlayalım. Bir Node.js backend’in, PostgreSQL veritabanına bağlandığı klasik bir yapı:
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: gizlisifre
volumes:
- postgres_data:/var/lib/postgresql/data
backend:
build: ./backend
environment:
DATABASE_URL: postgresql://appuser:gizlisifre@db:5432/myapp
depends_on:
- db
ports:
- "3000:3000"
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
depends_on:
- backend
volumes:
postgres_data:
Burada dikkat et: DATABASE_URL içinde localhost yerine db yazıyoruz. Çünkü Docker’ın DNS çözümlemesi, db servis adını otomatik olarak ilgili container’ın IP adresine çeviriyor. Bu mekanizma sayesinde IP adresi değişse bile bağlantı kopmaz.
nginx.conf içinde de aynı prensip geçerli:
upstream backend_pool {
server backend:3000;
}
server {
listen 80;
location /api {
proxy_pass http://backend_pool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
root /usr/share/nginx/html;
index index.html;
}
}
Özel Network Tanımları
Varsayılan network yeterli olmayan durumlar var. Mesela frontend servisinin direkt olarak veritabanına erişememesini istiyorsun. Ya da microservice mimarisinde bazı servislerin izole olmasını istiyorsun. İşte bu noktada özel network tanımları devreye giriyor.
version: '3.8'
services:
frontend:
build: ./frontend
networks:
- frontend_net
ports:
- "3000:3000"
backend:
build: ./backend
networks:
- frontend_net
- backend_net
environment:
DB_HOST: db
REDIS_HOST: cache
db:
image: postgres:15
networks:
- backend_net
environment:
POSTGRES_PASSWORD: gizlisifre
cache:
image: redis:7-alpine
networks:
- backend_net
networks:
frontend_net:
driver: bridge
backend_net:
driver: bridge
Bu yapıda frontend servisi, db ve cache servislerine doğrudan erişemez. Sadece backend üzerinden ulaşabilir. backend ise her iki network’te de bulunduğu için köprü görevi görüyor. Bu tür segmentasyon, production ortamlarında güvenlik açısından kritik önem taşır.
Network Alias Kullanımı
Bazen aynı servise birden fazla isimle erişmen gerekebilir. Ya da mevcut bir uygulamayı container’a taşırken bağlantı adresini değiştirmek istemiyorsundur. aliases özelliği tam burada işe yarıyor:
version: '3.8'
services:
db:
image: postgres:15
networks:
backend_net:
aliases:
- database
- postgres-primary
environment:
POSTGRES_PASSWORD: gizlisifre
backend:
build: ./backend
networks:
- backend_net
environment:
# Üç yöntemle de aynı servise ulaşırsın
DB_HOST_1: db
DB_HOST_2: database
DB_HOST_3: postgres-primary
networks:
backend_net:
Bu özelliği özellikle blue-green deployment ya da A/B test senaryolarında da kullanabilirsin. Eski ve yeni servis versiyonlarını aynı alias altında yönetmek mümkün.
Harici Network’lere Bağlanmak
Bazen birden fazla Compose projesi birbiriyle konuşması gerekir. Örneğin bir monitoring stack’in (Prometheus + Grafana) tüm uygulama container’larına erişmesi gerekiyordur. Bu durumda harici network’ler devreye giriyor.
Önce paylaşımlı network’ü oluştur:
docker network create shared_monitoring
Sonra her iki Compose dosyasında da bu network’ü external: true ile tanımla:
Uygulama Compose dosyası (myapp/docker-compose.yml):
version: '3.8'
services:
backend:
build: ./backend
networks:
- app_internal
- monitoring_network
db:
image: postgres:15
networks:
- app_internal
networks:
app_internal:
driver: bridge
monitoring_network:
external: true
name: shared_monitoring
Monitoring Compose dosyası (monitoring/docker-compose.yml):
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- monitoring_network
ports:
- "9090:9090"
grafana:
image: grafana/grafana:latest
networks:
- monitoring_network
ports:
- "3000:3000"
networks:
monitoring_network:
external: true
name: shared_monitoring
Bu yapı sayesinde prometheus, myapp projesindeki backend servisine adıyla ulaşabilir. Ancak dikkat: Container adları çakışmamalı. Her Compose projesi container adlarını __ formatında oluşturur, bu yüzden genellikle sorun çıkmaz.
IP Adresi ve Subnet Yapılandırması
Bazı production senaryolarında, container network’lerinin IP aralığının kurumsal ağ politikalarıyla çakışmaması gerekir. Ya da belirli bir container’a statik IP ataman lazımdır. Bu durumda subnet yapılandırmasına ihtiyaç duyarsın:
version: '3.8'
services:
nginx:
image: nginx:alpine
networks:
app_net:
ipv4_address: 172.20.0.10
ports:
- "80:80"
backend:
build: ./backend
networks:
app_net:
ipv4_address: 172.20.0.11
db:
image: postgres:15
networks:
app_net:
ipv4_address: 172.20.0.12
environment:
POSTGRES_PASSWORD: gizlisifre
networks:
app_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/24
gateway: 172.20.0.1
Statik IP ataması güçlü bir araç ama dikkatli kullan. Büyük çoğunlukla servis adı üzerinden DNS çözümlemesi yeterli ve çok daha yönetilebilir oluyor. Statik IP, özellikle iptables kuralları, güvenlik duvarı politikaları ya da legacy uygulama entegrasyonları için gerekebilir.
Gerçek Dünya Senaryosu: Microservice Mimarisi
Daha kapsamlı bir örneğe bakalım. E-ticaret platformu gibi düşün: Kullanıcı yönetimi, ürün kataloğu, sipariş servisi, ve bunların önünde bir API gateway. Her servisin farklı veritabanı var ve birbirleriyle HTTP üzerinden konuşuyorlar.
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "80:80"
networks:
- gateway_net
environment:
USER_SERVICE_URL: http://user-service:4001
PRODUCT_SERVICE_URL: http://product-service:4002
ORDER_SERVICE_URL: http://order-service:4003
user-service:
build: ./user-service
networks:
- gateway_net
- user_net
environment:
DB_HOST: user-db
DB_PORT: 5432
product-service:
build: ./product-service
networks:
- gateway_net
- product_net
environment:
DB_HOST: product-db
CACHE_HOST: product-cache
order-service:
build: ./order-service
networks:
- gateway_net
- order_net
environment:
DB_HOST: order-db
USER_SERVICE_URL: http://user-service:4001
PRODUCT_SERVICE_URL: http://product-service:4002
user-db:
image: postgres:15
networks:
- user_net
volumes:
- user_db_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: users
POSTGRES_PASSWORD: ${USER_DB_PASS}
product-db:
image: mongodb:6
networks:
- product_net
volumes:
- product_db_data:/data/db
product-cache:
image: redis:7-alpine
networks:
- product_net
order-db:
image: postgres:15
networks:
- order_net
volumes:
- order_db_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: orders
POSTGRES_PASSWORD: ${ORDER_DB_PASS}
networks:
gateway_net:
driver: bridge
user_net:
driver: bridge
product_net:
driver: bridge
order_net:
driver: bridge
volumes:
user_db_data:
product_db_data:
order_db_data:
Bu mimaride api-gateway, tüm servislerle konuşabilirken veritabanları tamamen izole. Bir servisin veritabanına başka bir servis doğrudan ulaşamıyor. Bu, hem güvenlik hem de bağımsız ölçeklendirme açısından ideal bir yapı.
Network Sorunlarını Debug Etmek
En iyi yapılandırmayı yapsan bile zaman zaman bağlantı sorunları çıkar. İşte bu durumda kullanabileceğin araçlar ve komutlar:
# Servisin hangi network'lerde olduğunu görmek
docker inspect <container_adı> | grep -A 30 "Networks"
# Container içinden DNS çözümlemesini test etmek
docker exec -it <container_adı> nslookup <hedef_servis_adı>
# Container içinden bağlantı testi
docker exec -it <container_adı> curl -v http://backend:3000/health
# Network üzerindeki tüm container'ları listelemek
docker network inspect <network_adı> --format='{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"n"}}{{end}}'
# Geçici bir debug container başlatarak network'ü test etmek
docker run --rm -it --network myapp_default nicolaka/netshoot nmap -p 5432 db
nicolaka/netshoot imajı bu tür debug işlemleri için biçilmiş kaftan. İçinde nmap, tcpdump, dig, curl, netstat gibi onlarca araç hazır geliyor. Production’da bir bağlantı sorunu çıktığında bu imajı geçici container olarak aynı network’e bağlayıp sorunun nereden kaynaklandığını hızlıca anlayabilirsin.
depends_on ve Sağlık Kontrolü
Network yapılandırması doğru olsa bile, servisler başlangıç sırasına bağlı sorunlar yaşayabilir. depends_on tek başına yeterli değil, çünkü sadece container’ın başladığını bekler, hazır olduğunu değil.
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: gizlisifre
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
backend:
build: ./backend
depends_on:
db:
condition: service_healthy
networks:
- app_net
environment:
DATABASE_URL: postgresql://postgres:gizlisifre@db:5432/myapp
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 3
worker:
build: ./worker
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
networks:
- app_net
networks:
app_net:
condition: service_healthy kullanımı, bağımlı servisler tamamen hazır olmadan diğer servislerin başlamasını engelliyor. Bu yapı özellikle veritabanı migration’larının çalıştığı backend servisleri için hayat kurtarır.
Host Network Modu ve Kullanım Senaryoları
Bazı özel durumlar için container’ın doğrudan host network stack’ini kullanması gerekebilir. Performans açısından kritik uygulamalar ya da host üzerindeki servislere düşük gecikmeli erişim gerektiren durumlar bunların başında gelir.
version: '3.8'
services:
high-perf-proxy:
image: haproxy:2.8
network_mode: host
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
Ancak bu modda port çakışmalarına dikkat etmek gerekiyor. Container artık host’un portlarını doğrudan kullanıyor ve başka hiçbir servis aynı porta bind olamıyor. Ayrıca ports tanımı bu modda geçersiz oluyor, host’un tüm portları zaten erişilebilir durumda.
Host network modunu kullanman gereken durumlar:
- Gerçek zamanlı uygulamalar: Oyun sunucuları, canlı yayın sistemleri
- Ağ performansı kritik uygulamalar: Yüksek throughput gerektiren proxy’ler
- Multicast veya broadcast gerektiren uygulamalar: Bazı discovery protokolleri
- Host üzerindeki servislerle entegrasyon: Yerel MongoDB veya PostgreSQL’e bağlanma
Dikkat: Host network modu Linux’ta çalışır, Docker Desktop (Mac/Windows) üzerinde beklendiği gibi davranmaz.
.env ile Network Yapılandırmasını Yönetmek
Büyük projelerde network yapılandırmasını ortama göre değiştirmen gerekebilir. .env dosyaları ve değişkenler burada çok işe yarıyor:
# .env dosyası
COMPOSE_PROJECT_NAME=myapp
NETWORK_SUBNET=172.25.0.0/24
NETWORK_GATEWAY=172.25.0.1
DB_PORT=5432
REDIS_PORT=6379
version: '3.8'
services:
backend:
build: ./backend
networks:
- app_net
environment:
DB_HOST: db
DB_PORT: ${DB_PORT:-5432}
db:
image: postgres:15
networks:
app_net:
ipv4_address: 172.25.0.10
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
networks:
app_net:
driver: bridge
ipam:
config:
- subnet: ${NETWORK_SUBNET:-172.25.0.0/24}
gateway: ${NETWORK_GATEWAY:-172.25.0.1}
COMPOSE_PROJECT_NAME değişkenini ayarlamak, birden fazla ortamı (dev, staging) aynı host üzerinde çalıştırırken network ismi çakışmalarını önlüyor. Aksi takdirde aynı klasörden farklı branch’ler çalıştırmaya kalktığında işler karışabilir.
Sık Yapılan Hatalar ve Çözümleri
Localhost tuzağı: En klasik hata, container içinde localhost kullanmak. Container’ın kendi içini gösterir, başka bir servisi değil. Servis adını kullan.
Port expose etmek zorunda değilsin: Servisler arası iletişim için ports tanımına gerek yok. ports sadece host makineden erişim içindir. Compose network’ü üzerindeki tüm portlar zaten servisler arası erişime açık.
Network driver seçimi: Varsayılan bridge driver çoğu senaryo için yeterli. overlay driver Swarm için, macvlan container’ların fiziksel ağa doğrudan bağlanması için kullanılır. Yanlış driver seçimi performans ve bağlantı sorunlarına yol açar.
Ağ izolasyonu unutulmak: Bir servisi birden fazla network’e bağlamak gerektiğinde sadece networks listesine eklemek yetmiyor, o network’ün de networks bölümünde tanımlı olması lazım.
depends_on sıralaması yanıltıcı: Yukarıda belirttiğimiz gibi, depends_on container başlangıcını bekler ama uygulama hazırlığını değil. Sağlık kontrolü eklemek şart.
Sonuç
Docker Compose ağ yapılandırması ilk bakışta karmaşık görünse de temel mantığı kavradıktan sonra oldukça akıcı bir deneyime dönüşüyor. Servis adıyla DNS çözümlemesi, özel network’lerle izolasyon, harici network’lerle multi-project iletişim ve sağlık kontrolleriyle güvenilir başlangıç sırası… Bunların hepsini bir arada kullandığında hem geliştirme hem de production ortamında tutarlı, güvenli ve yönetilebilir bir yapı elde ediyorsun.
Pratikte şu yaklaşımı benimse: Küçük projeler için varsayılan network yeterli, sadece servis adlarını doğru kullan. Orta ölçekli projeler için frontend/backend/data katmanlarını ayrı network’lere ayır. Büyük microservice mimarileri için her servis grubunu kendi izole network’üne koy, sadece gerekli servisleri birden fazla network’e ekle.
Son olarak şunu söyleyelim: Network yapılandırmasını dokümante et. Altı ay sonra projeye geri döndüğünde ya da yeni bir ekip üyesi geldiğinde, hangi servisin hangi servisle neden konuşabildiğini açıklayan bir README hayat kurtarır. Compose dosyasındaki yorum satırları da bu konuda çok değerli.