Merhabalar. Bugünkü yazımızda Docker Compose ile birden fazla konteyneri nasıl tek bir komutla yöneteceğinizi anlatacağım. Eğer Docker’ı biliyorsunuz ama her seferinde docker run ile ayrı ayrı konteyner başlatmaktan yorulduysanız, bu yazı tam size göre.

Docker Compose Nedir?

Diyelim ki bir web uygulamanız var. Bu uygulamanın bir backend servisi (mesela Python FastAPI), bir veritabanı (PostgreSQL) ve bir önbellek (Redis) servisine ihtiyacı var. Bunların her birini ayrı ayrı docker run komutlarıyla başlatıp, ağlarını ayarlayıp, sıralarını takip etmek oldukça zahmetli olur. İşte Docker Compose tam bu noktada devreye girer.

Docker Compose, tüm servislerinizi tek bir YAML dosyasında tanımlamanızı sağlar. Bu dosyada hangi imajları kullanacağınızı, hangi portları açacağınızı, hangi ortam değişkenlerini tanımlayacağınızı, verileri nereye kaydedeceğinizi yazarsınız ve tek bir komutla hepsini başlatırsınız.

Önemli bir not: 2025’in başından itibaren Docker Compose v1 (docker-compose komutu) tamamen kaldırıldı. Artık docker compose (tire yok) olarak kullanıyoruz. Ayrıca compose.yaml dosyasında artık version: alanı belirtmenize gerek yok — Compose spesifikasyonu v5.0.0 ile bu alan kullanımdan kaldırıldı.

Neden Docker Compose Kullanmalısınız?

  • Tek komut, tüm yığın: docker compose up ile tüm servisleri başlatın, docker compose down ile hepsini durdurun
  • Ağ yönetimi otomatik: Tüm servisler otomatik olarak aynı ağa konur ve birbirlerini isimle bulabilirler
  • Veri kalıcılığı: Volume tanımları ile veritabanı verileriniz konteyner durdurulsa bile kaybolmaz
  • Taşınabilirlik: compose.yaml dosyasını paylaşın, başkası aynı ortamı tek komutla kursun
  • Geliştirme kolaylığı: docker compose watch ile kod değiştikçe otomatik senkronizasyon yapılır

1. Kurulum

Docker Compose, Docker Desktop ile birlikte otomatik olarak gelir. GNU/Linux’ta Docker’ı kurduğunuzda eklenti olarak yüklenir. Kurulu olup olmadığını kontrol edelim:

docker compose version

Çıktınız şuna benzer olmalı:

Docker Compose version v2.40.2

Eğer Docker yüklü değilse, Docker’ın resmi sitesinden işletim sisteminize uygun kurulumu yapın.

2. Proje Yapısını Oluşturma

Basit bir blog uygulaması hayal edelim: Python (FastAPI) backend, PostgreSQL veritabanı ve Redis önbellek. Proje klasörümüzü oluşturalım:

mkdir blog-uygulamasi
cd blog-uygulamasi

İçinde şu dosyaları oluşturacağız:

blog-uygulamasi/
├── compose.yaml
├── app/
│ ├── main.py
│ └── requirements.txt

3. compose.yaml Dosyasını Yazma

Projenin kalbi compose.yaml dosyasıdır. (Eski adıyla docker-compose.yml — ikisi de çalışır ama yeni standart compose.yaml‘dır.) Bu dosyada üç servis tanımlayacağız:

services:
  web:
    build: ./app
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://kullanici:sifre@db:5432/blogdb
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    develop:
      watch:
        - action: sync
          path: ./app
          target: /app
    restart: unless-stopped
db:
    image: postgres:17-alpine
    environment:
      - POSTGRES_USER=kullanici
      - POSTGRES_PASSWORD=sifre
      - POSTGRES_DB=blogdb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U kullanici -d blogdb"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped
cache:
    image: redis:8-alpine
    restart: unless-stopped
volumes:
  postgres_data:

Bu dosyada neler yaptık adım adım açıklayalım:

web servisi: Uygulamamızın kendisi. build: ./app ile app klasöründeki Dockerfile ile imaj oluşturulur. Port 8000 dışa açılır. depends_on ile veritabanı sağlıklı olmadan başlamamasını sağladık. develop.watch kısmı ise 2025’te production-ready olan watch mode özelliği — kodu değiştirdiğinizde otomatik konteynere senkronlar.

db servisi: PostgreSQL 17’nin Alpine sürümünü kullanıyoruz. Alpine imajlar çok daha küçüktür (yaklaşık 100MB tasarruf). healthcheck ile veritabanının hazır olup olmadığını kontrol ediyoruz. volumes kısmında postgres_data volume’una verileri kaydediyoruz — konteyner silinse bile veriler kaybolmaz.

cache servisi: Redis 8’in Alpine sürümü. Önbellekleme için. En basit servis — sadece imajı çekip çalıştırıyoruz.

4. Uygulama Kodunu Hazırlama

app/main.py dosyasını oluşturalım. Bu basit bir FastAPI uygulaması olacak:

from fastapi import FastAPI
import os
import psycopg2
import redis
import json

app = FastAPI()

# Redis bağlantısı
redis_client = redis.Redis(host="cache", port=6379, decode_responses=True)
# PostgreSQL bağlantısı
db_url = os.environ.get("DATABASE_URL")
@app.get("/")
def ana_sayfa():
    # Önce önbellekten kontrol et
    cached = redis_client.get("ziyaret_sayisi")
    if cached:
        return {"ziyaret": int(cached), "kaynak": "redis"}
# Veritabanından oku (basit örnek)
    sayi = 0
    try:
        conn = psycopg2.connect(db_url)
        cur = conn.cursor()
        cur.execute("CREATE TABLE IF NOT EXISTS sayac (id SERIAL PRIMARY KEY, deger INTEGER DEFAULT 0)")
        cur.execute("INSERT INTO sayac (deger) VALUES (1)")
        cur.execute("SELECT COUNT(*) FROM sayac")
        sayi = cur.fetchone()[0]
        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        return {"hata": str(e)}
# Önbelleğe kaydet (60 saniye TTL)
    redis_client.setex("ziyaret_sayisi", 60, sayi)
    return {"ziyaret": sayi, "kaynak": "postgres"}

app/requirements.txt dosyasına bağımlılıkları yazalım:

fastapi==0.115.0
uvicorn==0.32.0
psycopg2-binary==2.9.10
redis==5.2.0

app/Dockerfile dosyasını da oluşturalım:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

5. Uygulamayı Başlatma

Artık her şey hazır. Terminalde proje kök dizinine gidin ve şu komutu çalıştırın:

docker compose up -d

-d bayrağı servisleri arkaplanda başlatır. Çıktınız şuna benzer olmalı:

[+] Running 4/4
✔ Network blog-uygulamasi_default Created
✔ Volume blog-uygulamasi_postgres_data Created
✔ Container blog-uygulamasi-db-1 Healthy
✔ Container blog-uygulamasi-cache-1 Started
✔ Container blog-uygulamasi-web-1 Started

Docker Compose otomatik olarak:
- Bir ağ oluşturdu (blog-uygulamasi_default)
- PostgreSQL için bir volume oluşturdu
- Önce veritabanını ve Redis'i başlattı, sağlıklı olduklarını doğruladı, sonra web servisini başlattı

Servislerin durumunu kontrol edin:

docker compose ps

Çıktı:

NAME                          STATUS                     PORTS
blog-uygulamasi-web-1         Up 3 seconds               0.0.0.0:8000->8000/tcp
blog-uygulamasi-db-1          Up 10 seconds (healthy)    5432/tcp
blog-uygulamasi-cache-1       Up 10 seconds             6379/tcp

Tarayıcınızda http://localhost:8000 adresine gidin. Şöyle bir yanıt görmelisiniz:

{"ziyaret": 1, "kaynak": "postgres"}

Sayfayı yenileyin. Bu sefer Redis’ten gelecek:

{"ziyaret": 1, "kaynak": "redis"}

6. Logları İnceleme

Servislerin loglarını görmek için:

docker compose logs -f

Sadece belirli bir servisin loglarını görmek için:

docker compose logs -f web

7. Uygulamayı Durdurma ve Temizleme

İşiniz bittiğinde tüm servisleri durdurmak için:

docker compose down

Bu komut konteynerleri durdurur ve ağları kaldırır. Volume’lar korunur — verileriniz kaybolmaz. Eğer verileri de silmek isterseniz:

docker compose down -v

Yaygın Hatalar ve Çözümleri

Hata: “port is already allocated”

8000 portu başka bir uygulama tarafından kullanılıyor olabilir. compose.yaml dosyasında portu değiştirin, örneğin "8001:8000" yapın. Hangi port kullanımda olduğunu görmek için:

sudo lsof -i :8000

Hata: “db container is unhealthy”

Veritabanı sağlıksız olarak işaretleniyorsa, healthcheck süresini artırın. Bazen yavaş sistemlerde PostgreSQL’in başlaması daha uzun sürer:

healthcheck:
      test: ["CMD-SHELL", "pg_isready -U kullanici -d blogdb"]
      interval: 10s
      timeout: 10s
      retries: 10

Hata: “Connection refused” web servisinde

Bu genellikle depends_on ile veritabanının hazır olmasını beklemediğinizde olur. condition: service_healthy kullandığınızdan emin olun. Eğer direkt depends_on: ["db"] yazarsanız Docker sadece konteynerin başlamasını bekler, servisin hazır olmasını değil.

Hata: “Cannot connect to the Docker daemon”

Docker servisi çalışmıyor olabilir:

sudo systemctl start docker

macOS’ta Docker Desktop’ı açın.

İpuçları

  • .env dosyası kullanın: Şifreleri compose.yaml içine yazmak yerine .env dosyasına koyun. Compose otomatik olarak okur:

.env dosyası:
POSTGRES_USER=kullanici
POSTGRES_PASSWORD=cokGuvenliSifre

compose.yaml'da:
  environment:
    - POSTGRES_USER=${POSTGRES_USER}
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
  • docker init ile hızlı başlangıç: Docker’ın 2025’te eklenen docker init komutu projenizi tarayıp otomatik compose.yaml, Dockerfile ve .dockerignore oluşturur. Sıfırdan başlıyorsanız çok zaman kazandırır.
  • Watch mode aktif kullanın: docker compose watch komutunu çalıştırın. Kodunuzu değiştirdiğinizde değişiklikler otomatik olarak konteynere senkronlanır, imajı yeniden oluşturmanıza gerek kalmaz. 2025’in ortasından itibaren initial_sync özelliği de eklendi — watch başladığı anında tüm dosyaları bir kere senkronlar.

Sonuç

Docker Compose, çoklu konteynerli uygulamaları yönetmenin en pratik yoludur. Tek bir YAML dosyası ile tüm servislerinizi tanımlar, tek komutla başlatır ve durdurursunuz. compose.yaml dosyası projenizin nasıl çalıştığını belgeleyen bir tarif gibidir — başkası projeyi indirdiğinde ne yapacağını hemen anlar.

Bu yazıda FastAPI, PostgreSQL ve Redis’den oluşan basit bir yığın kurduk. Kendi projelerinizde farklı servisler ekleyebilir, daha karmaşık ağ yapıları kurabilir, GPU desteği (yeni gpus: sözdizimi ile) ekleyebilirsiniz. Okuduğunuz için teşekkür ederim!