EFEF Core Handbook

REFERANS

Docker ile SQL Server & PostgreSQL Kurulumu

Local development için veritabanlarını Docker container olarak çalıştırmak en hızlı ve tekrarlanabilir yöntemdir. Bu bölüm: container kurulumu, EF Core bağlantı yapılandırması, migration uygulaması ve production-ready docker-compose senaryolarını kapsar.

Veritabanı sağlayıcısı Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.

1. Docker Kurulumu

# SQL Server 2022 (Developer Edition — ücretsiz, development için)
docker run -d \
  --name sqlserver \
  -e "ACCEPT_EULA=Y" \
  -e "MSSQL_SA_PASSWORD=YourStr0ng!Pass" \
  -e "MSSQL_PID=Developer" \
  -p 1433:1433 \
  -v sqlserver_data:/var/opt/mssql \
  mcr.microsoft.com/mssql/server:2022-latest

# Kontrol:
docker logs sqlserver
# "SQL Server is now ready for client connections" mesajını bekle

SA şifresi gereksinimleri: En az 8 karakter, büyük harf + küçük harf + rakam + özel karakter. Basit şifre verirsen container hemen kapanır.

# PostgreSQL 16
docker run -d \
  --name postgres \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_PASSWORD=YourStr0ng!Pass \
  -e POSTGRES_DB=appdb \
  -p 5432:5432 \
  -v postgres_data:/var/lib/postgresql/data \
  postgres:16-alpine

# Kontrol:
docker exec -it postgres psql -U appuser -d appdb -c "SELECT version();"

2. Docker Compose — Tam Geliştirme Ortamı

# docker-compose.yml
services:
  sqlserver:
    image: mcr.microsoft.com/mssql/server:2022-latest
    container_name: sqlserver
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "YourStr0ng!Pass"
      MSSQL_PID: "Developer"
    ports:
      - "1433:1433"
    volumes:
      - sqlserver_data:/var/opt/mssql
    healthcheck:
      test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$$MSSQL_SA_PASSWORD" -No -Q "SELECT 1"
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

volumes:
  sqlserver_data:
# docker-compose.yml
services:
  postgres:
    image: postgres:16-alpine
    container_name: postgres
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: YourStr0ng!Pass
      POSTGRES_DB: appdb
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  postgres_data:
# Başlat:
docker compose up -d

# Durumu kontrol et:
docker compose ps
docker compose logs -f sqlserver

# Durdur (veri korunur):
docker compose stop

# Tamamen sil (veri DAHİL):
docker compose down -v

3. EF Core Connection String Yapılandırması

// appsettings.Development.json
{
  "ConnectionStrings": {
    "Default": "Server=localhost,1433;Database=AppDb;User Id=sa;Password=YourStr0ng!Pass;TrustServerCertificate=True;"
  }
}
// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("Default"),
        sqlOptions =>
        {
            sqlOptions.EnableRetryOnFailure(
                maxRetryCount: 3,
                maxRetryDelay: TimeSpan.FromSeconds(10),
                errorNumbersToAdd: null);
            sqlOptions.CommandTimeout(30);
        }));
// appsettings.Development.json
{
  "ConnectionStrings": {
    "Default": "Host=localhost;Port=5432;Database=appdb;Username=appuser;Password=YourStr0ng!Pass;"
  }
}
// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(
        builder.Configuration.GetConnectionString("Default"),
        npgsqlOptions =>
        {
            npgsqlOptions.EnableRetryOnFailure(
                maxRetryCount: 3,
                maxRetryDelay: TimeSpan.FromSeconds(10),
                errorCodesToAdd: null);
            npgsqlOptions.CommandTimeout(30);
        }));

🚨 Güvenlik: appsettings.Development.json dosyasını .gitignore'a ekle. Production'da connection string'leri environment variable veya Azure Key Vault / AWS Secrets Manager ile yönet. Asla source control'e şifre koyma.

4. Container Hazır Olana Kadar Bekleme (Startup Resilience)

Container başlatıldıktan sonra DB hemen bağlantı kabul etmeyebilir. Retry pattern ile bekle:

// Program.cs — Migration uygularken retry:
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

var retryCount = 0;
const int maxRetries = 10;

while (retryCount < maxRetries)
{
    try
    {
        await db.Database.MigrateAsync();
        break;
    }
    catch (Exception ex) when (retryCount < maxRetries - 1)
    {
        retryCount++;
        Console.WriteLine($"DB bağlantısı bekleniyor... ({retryCount}/{maxRetries})");
        await Task.Delay(TimeSpan.FromSeconds(3));
    }
}

Alternatif: Docker Compose depends_on + healthcheck kullanarak API container'ının DB hazır olduktan sonra başlamasını sağla:

# docker-compose.yml — API service
  api:
    build: .
    depends_on:
      sqlserver:
        condition: service_healthy
    environment:
      ConnectionStrings__Default: "Server=sqlserver,1433;Database=AppDb;User Id=sa;Password=YourStr0ng!Pass;TrustServerCertificate=True;"
# docker-compose.yml — API service
  api:
    build: .
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      ConnectionStrings__Default: "Host=postgres;Port=5432;Database=appdb;Username=appuser;Password=YourStr0ng!Pass;"

Docker network içinde container ismi host olarak kullanılır (postgres, sqlserver), localhost değil.

5. İlk Database ve Migration Oluşturma

# Migration oluştur:
dotnet ef migrations add InitialCreate -o Data/Migrations

# Migration'ı Docker'daki DB'ye uygula:
dotnet ef database update

# Alternatif: SQL script olarak export et:
dotnet ef migrations script -o migrations.sql

Migration öncesi DB'yi manuel oluşturma:

# Container içinde sqlcmd ile DB oluştur:
docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P "YourStr0ng!Pass" -No \
  -Q "CREATE DATABASE AppDb;"
# PostgreSQL'de ek DB oluştur (POSTGRES_DB dışında):
docker exec -it postgres psql -U appuser -c "CREATE DATABASE seconddb;"

6. Volume ve Data Persistence

Strateji Komut Veri korunur mu?
Named volume -v sqlserver_data:/var/opt/mssql Container silinse bile
Bind mount -v ./data:/var/opt/mssql Host'ta görünür
No volume (volume tanımlanmaz) Container silinince kaybolur
Temiz başlangıç docker compose down -v Volume dahil siler
# Volume listele:
docker volume ls

# Volume içeriğini incele:
docker volume inspect sqlserver_data

# Sadece belirli volume'ü sil:
docker volume rm sqlserver_data

7. Birden Fazla Veritabanı (Multi-DB Senaryo)

Microservice'lerde her servisin kendi DB'si olabilir:

# docker-compose.yml — çoklu DB
services:
  sqlserver:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "YourStr0ng!Pass"
      MSSQL_PID: "Developer"
    ports:
      - "1433:1433"
    volumes:
      - sqlserver_data:/var/opt/mssql
      - ./init-scripts:/docker-entrypoint-initdb.d

volumes:
  sqlserver_data:
-- init-scripts/01-create-databases.sql
-- sqlcmd ile çalıştır (container başlangıcında otomatik çalışmaz, manuel gerekir):
CREATE DATABASE orders_db;
GO
CREATE DATABASE inventory_db;
GO
CREATE DATABASE identity_db;
GO

-- Her DB için ayrı login:
CREATE LOGIN orders_svc WITH PASSWORD = 'OrdersPass123!';
GO
USE orders_db;
CREATE USER orders_svc FOR LOGIN orders_svc;
ALTER ROLE db_owner ADD MEMBER orders_svc;
GO
// Her DbContext farklı connection string:
builder.Services.AddDbContext<OrdersDbContext>(o =>
    o.UseSqlServer(config.GetConnectionString("OrdersDb")));

builder.Services.AddDbContext<InventoryDbContext>(o =>
    o.UseSqlServer(config.GetConnectionString("InventoryDb")));
# docker-compose.yml — çoklu DB
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: YourStr0ng!Pass
      POSTGRES_DB: shared
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d  # ← Başlangıç scriptleri

volumes:
  postgres_data:
-- init-scripts/01-create-databases.sql
-- Container ilk başlatıldığında otomatik çalışır:
CREATE DATABASE orders_db;
CREATE DATABASE inventory_db;
CREATE DATABASE identity_db;

-- Her DB için ayrı kullanıcı (en az yetki prensibi):
CREATE USER orders_svc WITH PASSWORD 'OrdersPass123!';
GRANT ALL PRIVILEGES ON DATABASE orders_db TO orders_svc;

CREATE USER inventory_svc WITH PASSWORD 'InvPass123!';
GRANT ALL PRIVILEGES ON DATABASE inventory_db TO inventory_svc;
// Her DbContext farklı connection string:
builder.Services.AddDbContext<OrdersDbContext>(o =>
    o.UseNpgsql(config.GetConnectionString("OrdersDb")));

builder.Services.AddDbContext<InventoryDbContext>(o =>
    o.UseNpgsql(config.GetConnectionString("InventoryDb")));

8. Faydalı Docker Komutları — Günlük Kullanım

Komut Açıklama
docker compose up -d Tüm servisleri başlat (detached)
docker compose stop Durdur (veri korunur)
docker compose down Container'ları sil (volume kalır)
docker compose down -v Her şeyi sil (temiz başlangıç)
docker compose logs -f <service> Canlı log takibi
docker compose restart <service> Tek servisi yeniden başlat
docker stats CPU/RAM kullanımı

Veritabanı CLI erişimi:

# SQL Server CLI (sqlcmd):
docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "YourStr0ng!Pass" -No

# Tek sorgu çalıştır:
docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P "YourStr0ng!Pass" -No \
  -Q "SELECT name FROM sys.databases;"
# PostgreSQL CLI (psql):
docker exec -it postgres psql -U appuser -d appdb

# Tek sorgu çalıştır:
docker exec -it postgres psql -U appuser -d appdb -c "\dt"

9. Sık Karşılaşılan Sorunlar

Problem Neden Çözüm
SQL Server container hemen kapanıyor Zayıf SA password En az 8 karakter, büyük+küçük+rakam+özel
"Connection refused" hatası Container henüz hazır değil Healthcheck + retry pattern kullan
Port çakışması (1433/5432 meşgul) Host'ta başka DB çalışıyor Port'u değiştir: -p 1434:1433
Permission denied (Linux volume) UID mismatch -e MSSQL_AGENT_ENABLED=false veya chown
PostgreSQL "role does not exist" Yanlış kullanıcı adı POSTGRES_USER env var'ı kontrol et
Migration timeout DB henüz bağlantı kabul etmiyor depends_on: condition: service_healthy
Veri kayboldu Volume tanımlanmamış Named volume veya bind mount ekle
Container içinden dışarı erişilemiyor Network isolation host.docker.internal kullan (host erişimi)

10. Production'a Geçiş Notları

Docker Compose production veritabanı için önerilmez (tek node, HA yok). Production'da:

Ortam SQL Server PostgreSQL
Azure Azure SQL Database (managed) Azure Database for PostgreSQL Flexible
AWS RDS for SQL Server RDS for PostgreSQL / Aurora
Self-hosted Docker Compose sadece dev/test Docker Compose sadece dev/test
Kubernetes StatefulSet + PVC (dikkatli!) CloudNativePG Operator / Crunchy
// Production — managed DB:
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        Environment.GetEnvironmentVariable("DATABASE_URL")
        ?? throw new InvalidOperationException("DATABASE_URL not configured"),
        o => o.EnableRetryOnFailure()));
// Production — managed DB:
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(
        Environment.GetEnvironmentVariable("DATABASE_URL")
        ?? throw new InvalidOperationException("DATABASE_URL not configured"),
        o => o.EnableRetryOnFailure()));

Tavsiye: Development'ta Docker Compose + local container kullan. Staging/Production'da managed database servisi kullan. Connection string'leri her ortam için ayrı tut (User Secrets → Environment Variables → Key Vault).