Docker Nedir? yazısında tek bir uygulamayı container içinde çalıştırdık. Gerçek projeler ise nadiren tek parçadır: Bir web uygulaması, bir veritabanı, belki bir önbellek servisi ve bir arka plan işçisi birlikte çalışır. Bunların her birini ayrı docker run komutlarıyla, doğru sırayla, doğru ağ ve port ayarlarıyla başlatmak hem yorucu hem de hataya açıktır. Docker Compose bu işi tek bir dosyaya ve tek bir komuta indirir. Bu yazının sonunda bir web uygulamasını ve Redis veritabanını tek komutla birlikte ayağa kaldırmış olacaksınız.
compose.yaml dosyasının yapısını: services, ports, environment, volumes, depends_onDocker Desktop (Windows ve macOS) ve Linux'taki güncel Docker Engine kurulumları Docker Compose'u hazır olarak getirir. Kontrol etmek için:
docker compose version
Docker Compose version v2... veya daha yeni bir sürüm görüyorsanız hazırsınız.
Docker Compose, birden fazla container'dan oluşan bir uygulamayı tek bir YAML dosyasında tanımlayıp tek komutla başlatmanızı, durdurmanızı ve yönetmenizi sağlayan bir Docker aracıdır.
Tek container'lı dünyada bir uygulamayı şöyle başlatıyorduk:
docker run -d -p 8080:8000 --name benim-uygulamam ilk-uygulamam
Uygulamaya bir de Redis eklemek istediğinizde iş büyür: Önce ortak bir ağ oluşturursunuz, sonra Redis'i o ağda başlatırsınız, verileri için bir volume tanımlarsınız, ardından uygulamayı aynı ağa bağlayıp Redis'in adresini ortam değişkeniyle verirsiniz. Her seferinde dört beş komut, doğru sırayla.
Compose ile bu bilgilerin hepsi compose.yaml adlı tek bir dosyaya yazılır ve şu komut yeterli olur:
docker compose up -d
Bir benzetmeyle: docker run komutları tek tek çalınan enstrümanlarsa, compose.yaml bir orkestranın notasıdır. Kimin ne zaman, hangi ayarla çalacağı tek bir kâğıtta yazılıdır ve şef (Compose) herkesi aynı anda yönetir.
compose.yaml içinde açıkça yazılıdır. Bu dosya projeyle birlikte Git'e eklenir.docker compose up ve docker compose down bütün servisleri birlikte yönetir.redis ismiyle ulaşır.depends_on ile tanımlayabilirsiniz.docker compose up yazmak yeterlidir.compose.yaml dosyasını doğrudan okuyup projenizi sunucuya kurabilir.docker-compose mu, docker compose mu?İnternetteki eski yazılarda komutun tireli hali olan docker-compose kullanılır. Bu, Python ile yazılmış eski Compose V1 aracıdır ve artık geliştirilmemektedir. Güncel sürüm Compose V2'dir ve Docker komutunun bir eklentisi olarak çalışır: docker compose (arada boşluk).
Güncel bir Docker kurulumunda eski komutu yazarsanız büyük ihtimalle şu hatayı alırsınız:
docker-compose: command not found
Çözüm basittir: Tire yerine boşluk kullanın. Bu yazıdaki tüm komutlar güncel docker compose biçimindedir.
Dosya adı için de benzer bir durum vardır. Eskiden docker-compose.yml kullanılırdı. Güncel tercih edilen ad compose.yaml'dır, ancak Compose eski adları da tanımaya devam eder.
En sade haliyle bir compose.yaml dosyası şöyle görünür:
services:
web:
build: .
ports:
- "8080:8000"
redis:
image: redis:8-alpine
Temel anahtarlar şunlardır:
| Anahtar | Ne işe yarar? | docker run karşılığı |
|---|---|---|
services | Çalıştırılacak container'ların listesi. Her alt başlık bir servistir. | Her servis bir docker run |
image | Hazır bir image kullanır (örneğin Docker Hub'dan) | docker run redis:8-alpine |
build | Image'ı belirtilen klasördeki Dockerfile'dan oluşturur | docker build . |
ports | bilgisayar_portu:container_portu eşlemesi yapar | -p 8080:8000 |
environment | Container'a ortam değişkeni gönderir | -e ANAHTAR=deger |
volumes | Kalıcı depolama alanı veya klasör bağlar | -v isim:/yol |
depends_on | Servisin başka bir servisten sonra başlamasını sağlar | Karşılığı yok |
command | Image'ın varsayılan başlangıç komutunu değiştirir | Image adından sonra yazılan komut |
healthcheck | Servisin "hazır" olup olmadığını düzenli olarak kontrol eder | --health-cmd |
YAML dosyalarında girintiler anlam taşır. Girintiler boşlukla yapılmalı, sekme (Tab) karakteri kullanılmamalıdır. Genellikle her seviye için iki boşluk kullanılır. Yeni başlayanların aldığı hataların büyük kısmı girinti kaymasından gelir; bunlara aşağıda "Sık karşılaşılan hatalar" bölümünde bakacağız.
Eski örneklerde dosyanın en üstünde version: "3.8" gibi bir satır görebilirsiniz. Güncel Compose bu satırı kullanmaz. Bıraktığınızda şu uyarıyı verir:
the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
Yeni dosyalarda bu satırı hiç yazmayın.
Şimdi her sayfa açılışında ziyaret sayısını artıran küçük bir web uygulaması yapacağız. Sayı, Redis adlı hızlı bir anahtar-değer veritabanında tutulacak. Böylece iki servis olacak:
web: Python ve Flask ile yazılmış uygulamamızredis: Docker Hub'daki resmi Redis image'ıBilgisayarınızda ne Python ne de Redis kurulu olmasına gerek yok. Her ikisini de container içinde Docker sağlayacak.
Bu örnekteki compose.yaml dosyasını, uygulama kodunu ve aşağıdaki komutları Linux üzerinde Docker Engine 29.4 ve Docker Compose v5.1 ile çalıştırarak test ettik; yazıdaki çıktılar ve hata mesajları bu testten alındı. Test ortamımız Docker Hub'a erişemediği için resmi python ve redis image'ları yerine aynı Python, Flask ve Redis bileşenlerini içeren yerel bir image kullandık. Bu yüzden Dockerfile'daki pip install adımı ve image indirme aşaması bizim ortamımızda çalıştırılmadı. Windows ve macOS'ta Docker Desktop ile de aynı dosyalar ve komutlar kullanılır.
mkdir ziyaret-sayaci
cd ziyaret-sayaci
Klasör adı önemlidir: Compose, varsayılan olarak klasör adını proje adı olarak kullanır ve container'ları ziyaret-sayaci-web-1 gibi isimlendirir.
app.py adlı bir dosya oluşturun:
import os
import redis
from flask import Flask
app = Flask(__name__)
veritabani = redis.Redis(
host=os.environ.get("REDIS_HOST", "localhost"),
port=6379,
decode_responses=True,
)
@app.route("/")
def ana_sayfa():
sayi = veritabani.incr("ziyaret")
return f"Merhaba! Bu sayfa {sayi} kez görüntülendi.\n"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Önemli noktalar:
veritabani.incr("ziyaret"), Redis'teki ziyaret anahtarının değerini 1 artırır ve yeni değeri döndürür. Anahtar yoksa 0'dan başlatır.REDIS_HOST ortam değişkeninden okunuyor. Bu değeri birazdan compose.yaml içinde vereceğiz.host="0.0.0.0", uygulamanın container dışından gelen istekleri kabul etmesi için gereklidir. Bunu Docker yazısında ayrıntılı anlatmıştık.Ardından uygulamanın kullandığı paketleri belirten requirements.txt dosyasını oluşturun:
flask==3.1.3
redis==8.1.0
Sürümleri sabitlemek, image'ı bir yıl sonra yeniden build ettiğinizde de aynı sonucu almanızı sağlar.
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]
Docker yazısındaki Dockerfile'dan tek farkı paket kurulumudur. requirements.txt dosyasının app.py'den önce kopyalanması bilinçli bir tercihtir: Docker her satırı önbelleğe alır. Yalnızca app.py değiştiğinde paketler yeniden kurulmaz, build saniyeler içinde biter.
Şimdi asıl konumuza geldik. Aynı klasörde compose.yaml adlı dosyayı oluşturun:
services:
web:
build: .
ports:
- "8080:8000"
environment:
REDIS_HOST: redis
depends_on:
redis:
condition: service_healthy
redis:
image: redis:8-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-veri:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
volumes:
redis-veri:
Satır satır inceleyelim.
web servisi:
build: . – Image'ı bu klasördeki Dockerfile'dan oluştur.ports: - "8080:8000" – Bilgisayarınızın 8080 portunu container'ın 8000 portuna bağla. Port eşlemelerini tırnak içinde yazmak iyi bir alışkanlıktır; YAML bazı sayı biçimlerini beklenmedik şekilde yorumlayabilir.environment: REDIS_HOST: redis – Uygulamaya Redis'in adresini ver. Buradaki redis, aşağıdaki servisin adıdır. Compose, her servisi kendi adıyla bulunabilir hale getirir.depends_on ve condition: service_healthy – web servisini, redis servisi yalnızca başlamış değil, sağlıklı (healthy) olduktan sonra başlat.redis servisi:
image: redis:8-alpine – Docker Hub'daki resmi Redis image'ının Alpine Linux tabanlı küçük sürümünü kullan.command: ["redis-server", "--appendonly", "yes"] – Redis'i, verileri diske yazacak şekilde (AOF modu) başlat. Bu ayar olmadan Redis verileri esas olarak bellekte tutar.volumes: - redis-veri:/data – Redis'in verilerini yazdığı /data klasörünü redis-veri adlı volume'a bağla.healthcheck – Her 5 saniyede bir redis-cli ping komutunu çalıştır. Redis PONG cevabı verirse servis sağlıklı sayılır. 5 deneme başarısız olursa servis "unhealthy" olarak işaretlenir.En alttaki volumes bölümü: redis-veri adlı volume'u tanımlar. Compose bu volume'u ilk çalıştırmada otomatik oluşturur.
Dikkat ederseniz redis servisinde ports yok. Redis'e yalnızca web servisinin ulaşması gerekiyor; bilgisayarınızdan veya internetten erişilmesine gerek yok. Compose'un oluşturduğu iç ağda servisler birbirine port açmadan ulaşabilir. Dışarıya yalnızca gerçekten ihtiyaç duyulan portları açmak hem düzenli hem de güvenli bir alışkanlıktır.
Klasörünüz şimdi şöyle görünmeli:
ziyaret-sayaci/
├── app.py
├── compose.yaml
├── Dockerfile
└── requirements.txt
docker compose up -d --build
up: compose.yaml dosyasındaki tüm servisleri oluşturur ve başlatır.-d: Arka planda çalıştırır.--build: build tanımlı servislerin image'ını (yeniden) oluşturur. Kodu her değiştirdiğinizde bu parametreyi kullanın.İlk çalıştırmada Python ve Redis image'ları indirileceği için işlem biraz sürer. Sonunda çıktının son kısmı şuna benzer:
Volume ziyaret-sayaci_redis-veri Created
Container ziyaret-sayaci-redis-1 Created
Container ziyaret-sayaci-web-1 Created
Container ziyaret-sayaci-redis-1 Started
Container ziyaret-sayaci-redis-1 Waiting
Container ziyaret-sayaci-redis-1 Healthy
Container ziyaret-sayaci-web-1 Started
Sıralamaya dikkat edin: Compose önce Redis'i başlattı, Healthy olmasını bekledi, ardından web servisini başlattı. depends_on ile healthcheck tam olarak bunu sağlar.
Şimdi tarayıcıda http://localhost:8080 adresini açın ve sayfayı birkaç kez yenileyin. Ya da terminalden deneyin:
curl http://localhost:8080
curl http://localhost:8080
curl http://localhost:8080
Merhaba! Bu sayfa 1 kez görüntülendi.
Merhaba! Bu sayfa 2 kez görüntülendi.
Merhaba! Bu sayfa 3 kez görüntülendi.
İki container birbiriyle konuşuyor: Web uygulaması her istekte Redis'e bağlanıp sayacı artırıyor.
docker compose ps
Çıktıda her servis için bir satır görürsünüz. Dikkat edilecek sütunlar şunlardır:
NAME: Container'ın adı. Compose bunu proje-servis-sıra biçiminde verir, örneğin ziyaret-sayaci-web-1.STATUS: Container'ın durumu. Redis satırında Up ... (healthy) yazmalıdır; bu, healthcheck'in başarılı olduğunu gösterir.PORTS: Dışarıya açılan portlar. Web satırında 0.0.0.0:8080->8000/tcp görürsünüz.Redis'in satırında 0.0.0.0:...-> biçiminde bir eşleme yoktur, çünkü Redis'e ports tanımlamadık. Redis yalnızca iç ağda kullanılıyor.
Container'lardan biri çalışmıyorsa docker compose ps onu listelemeyebilir. Durmuş olanlar dahil tüm servisleri görmek için docker compose ps -a kullanın; STATUS sütununda Exited (1) gibi bir değer görürseniz o servisin loglarına bakın.
Logları görmek için:
# Tüm servislerin logları
docker compose logs
# Yalnızca web servisinin logları
docker compose logs web
# Logları canlı takip et (Ctrl+C ile çıkılır)
docker compose logs -f web
Web servisinin loglarında Flask'ın başlangıç mesajlarını ve her isteğin kaydını görürsünüz:
web-1 | * Running on all addresses (0.0.0.0)
web-1 | * Running on http://127.0.0.1:8000
web-1 | 172.18.0.1 - - [02/Oct/2026 23:07:27] "GET / HTTP/1.1" 200 -
Flask ayrıca "This is a development server. Do not use it in a production deployment." uyarısını da yazar. Bu örnek öğrenme amaçlıdır; canlı ortamda Flask uygulamaları Gunicorn gibi bir WSGI sunucusuyla çalıştırılır.
Compose'un en çok kullanılan komutları:
# Servisleri durdur (container'lar silinmez)
docker compose stop
# Durdurulmuş servisleri tekrar başlat
docker compose start
# Container'ları ve ağı sil (volume'lar KALIR)
docker compose down
# Container'ları, ağı VE volume'ları sil (veriler SİLİNİR)
docker compose down -v
Şimdi volume'un ne işe yaradığını görelim. Önce her şeyi kaldırıp yeniden başlatın:
docker compose down
docker compose up -d
curl http://localhost:8080
Merhaba! Bu sayfa 4 kez görüntülendi.
Container'lar tamamen silinip yeniden oluşturuldu, ama sayaç 4'ten devam etti. Çünkü Redis verileri container'ın içinde değil, redis-veri volume'unda duruyordu ve docker compose down volume'lara dokunmaz.
Şimdi -v parametresiyle deneyin:
docker compose down -v
docker compose up -d
curl http://localhost:8080
Merhaba! Bu sayfa 1 kez görüntülendi.
Volume da silindiği için sayaç sıfırlandı. down -v komutunu veritabanı içeren bir projede kullanmadan önce iki kez düşünün: Geliştirme ortamında "temiz başlangıç" için faydalıdır, ama verileri geri getirmenin yolu yoktur.
docker compose up komutunu verdiğinizde perde arkasında şunlar olur:
ziyaret-sayaci).ziyaret-sayaci_default adlı bu ağa projedeki tüm servisler bağlanır.redis-veri volume'u, projeye özel olarak ziyaret-sayaci_redis-veri adıyla oluşturulur.image tanımlı servisler için image indirilir, build tanımlı servisler için build yapılır.depends_on sırasına göre başlatılır.İkinci madde özellikle önemlidir. Compose ağı, Docker'ın dahili DNS sunucusunu kullanır ve her servisi adıyla çözümler. Bu yüzden web uygulamasında Redis'in adresi redis olarak yazılabildi. Container'ların IP adresleri her yeniden başlatmada değişebilir, ama servis isimleri değişmez.
Tarayıcıda 500 Internal Server Error görürsünüz, web servisinin loglarında ise şu satır vardır:
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
Neden olur: Uygulama Redis'e localhost üzerinden bağlanmaya çalışıyor. Container içinde localhost, o container'ın kendisi demektir. Web container'ının içinde Redis çalışmadığı için bağlantı reddedilir.
Çözüm: Diğer servislere her zaman servis adıyla bağlanın. Örneğimizde REDIS_HOST: redis satırının yazıldığından ve compose.yaml dosyasındaki servis adıyla aynı olduğundan emin olun. Bu hata, Compose'a yeni geçenlerin en sık yaptığı hatadır.
yaml: while parsing a block mapping at line 1, column 3: line 5, column 5: did not find expected key
Neden olur: Bir satırın girintisi diğerleriyle uyumlu değil. Örneğin ports: satırı build: ile aynı hizada değil de bir seviye geride kalmışsa bu hata alınır.
Çözüm: Hata mesajındaki satır numarasına gidin ve aynı servise ait tüm anahtarların (build, ports, environment...) aynı hizada olduğunu kontrol edin. VS Code gibi editörler girintileri görünür hale getirerek bu işi kolaylaştırır.
yaml: while scanning for the next token at line 2: found character that cannot start any token
Neden olur: Girintide Tab karakteri kullanılmış. YAML, girinti için Tab kabul etmez.
Çözüm: Tab karakterlerini boşlukla değiştirin. Çoğu editörde "Tab yerine boşluk kullan" (Insert Spaces) ayarını açabilirsiniz.
Bind for 0.0.0.0:8080 failed: port is already allocated
Neden olur: 8080 portunu başka bir container veya program kullanıyor. Örneğin Docker yazısındaki benim-uygulamam container'ı hâlâ çalışıyor olabilir.
Çözüm: docker ps ile portu kullanan container'ı bulup durdurun ya da compose.yaml dosyasında bilgisayar tarafındaki portu değiştirin ("8081:8000"). Ardından docker compose up -d komutunu yeniden çalıştırın.
Neden olur: docker compose up -d komutu, image zaten varsa onu yeniden build etmez.
Çözüm: Kodu değiştirdikten sonra --build parametresini ekleyin:
docker compose up -d --build
Compose yalnızca değişen servisleri yeniden oluşturur; Redis container'ına dokunmaz.
depends_on yazdım ama uygulama yine de veritabanına bağlanamıyorNeden olur: depends_on tek başına (koşulsuz) yazıldığında yalnızca başlatma sırasını belirler. Veritabanı container'ı başlamış olabilir ama bağlantı kabul etmeye henüz hazır olmayabilir. Bu durum özellikle PostgreSQL ve MySQL gibi açılışı birkaç saniye süren veritabanlarında sık görülür.
Çözüm: Örneğimizdeki gibi veritabanı servisine bir healthcheck ekleyin ve depends_on içinde condition: service_healthy kullanın. Böylece uygulama, veritabanı gerçekten hazır olduğunda başlar.
Evet. Docker Compose açık kaynaklıdır ve ücretsizdir. Docker Desktop ile birlikte gelir, Linux'ta Docker Engine'e eklenti olarak kurulur. Docker Desktop'ın ticari kullanım koşulları ise ayrıdır; ayrıntıları Docker Nedir? yazısının SSS bölümünde anlattık.
Evet, özellikle tek sunucuda çalışan küçük ve orta ölçekli projelerde yaygın olarak kullanılır. Coolify gibi kendi sunucunuzda çalışan yayınlama platformları da Compose dosyalarını doğrudan destekler. Ancak birden fazla sunucuya yayılan, otomatik ölçeklenmesi gereken büyük sistemlerde Kubernetes gibi orkestrasyon araçları tercih edilir.
Örneğimizdeki redis-veri:/data bir adlandırılmış volume'dur; Docker tarafından yönetilir ve verilerin nerede tutulduğuyla ilgilenmezsiniz. ./veriler:/data gibi başında ./ olan bir tanım ise bind mount'tur ve bilgisayarınızdaki bir klasörü doğrudan container'a bağlar. Veritabanları için adlandırılmış volume, geliştirme sırasında kod klasörünü container'a bağlamak için bind mount daha uygundur.
Hayır. compose.yaml genellikle Git'e eklenir ve içindeki şifreler herkes tarafından görülebilir. Bunun yerine şifreleri proje klasöründeki bir .env dosyasına yazıp compose.yaml içinde ${DEGISKEN_ADI} biçiminde kullanabilirsiniz. Compose, aynı klasördeki .env dosyasını otomatik okur. .env dosyasını .gitignore listesine eklemeyi unutmayın.
Evet. Komutların çoğu servis adı alır:
docker compose restart web
docker compose logs -f redis
docker compose up -d --build web
docker compose exec komutuyla çalışan bir servisin içinde komut çalıştırabilirsiniz. Örneğin Redis'teki sayacı doğrudan okumak için:
docker compose exec redis redis-cli get ziyaret
compose.yaml dosyasıyla tanımlayıp tek komutla yönetmeyi sağlar.docker compose (boşluklu) biçimindedir; tireli docker-compose eski V1 sürümüdür.localhost o container'ın kendisidir.depends_on ile condition: service_healthy birlikte kullanıldığında uygulama, veritabanı gerçekten hazır olduğunda başlar.docker compose down volume'ları korur, docker compose down -v verileri de siler.ports tanımlamayın.Artık bir uygulamayı ve veritabanını bilgisayarınızda birlikte çalıştırabiliyorsunuz. Bir sonraki doğal adım, aynı compose.yaml dosyasını bir sunucuya taşıyıp uygulamayı internete açmaktır. Bunu sunucuya SSH ile bağlanıp komut yazarak da yapabilirsiniz, ama alan adı, HTTPS sertifikası ve otomatik güncelleme gibi işleri sizin yerinize yapan Coolify çok daha pratik bir yoldur. Coolify yazısında bu projeyi kendi sunucunuzda nasıl yayınlayacağınızı anlatıyoruz. Windows kullanıyorsanız Docker'ı en verimli şekilde çalıştırmak için WSL yazısına da göz atabilirsiniz.
Kurumsal web siteleri için içerik yönetim sistemi.