Docker Compose Secrets ile Hassas Veri Yönetimi: Şifre ve Token Güvenliği
Production ortamında çalışan bir uygulamanın veritabanı şifresi, API anahtarı ya da JWT token’ı yanlış yönetildiğinde sonuçları felaket olabilir. Docker Compose kullanarak uygulama deploy eden birçok ekip, hassas verileri environment variable olarak düz metin halinde docker-compose.yml dosyasına yazıyor ve bu dosyayı Git reposuna push ediyor. Bu yazıda Docker Compose Secrets mekanizmasını kullanarak bu güvenlik açığını nasıl kapatabileceğinizi, gerçek dünya senaryolarıyla birlikte adım adım anlatacağım.
Neden Environment Variable Yeterli Değil?
Klasik yöntemde şifreler genellikle şöyle tanımlanır:
# docker-compose.yml - YANLIS YONTEM
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: super_gizli_sifre_123
POSTGRES_USER: myapp
Bu yaklaşımın birkaç kritik sorunu var:
- Git geçmişine gömülür: Şifreyi sonradan değiştirseniz bile eski commit’lerde kalır
docker inspectile görünür: Container’a erişimi olan herkesdocker inspectkomutuyla tüm environment variable’ları düz metin olarak görebilir- Log’lara sızabilir: Bazı uygulamalar hata durumunda ortam değişkenlerini log’a yazar
.envdosyası ihmalkar ellerde: Ekip üyeleri.envdosyasını yanlışlıkla commit’leyebilir
Docker Secrets tam olarak bu problemleri çözmek için tasarlanmış bir mekanizmadır.
Docker Compose Secrets Nasıl Çalışır?
Docker Secrets, hassas verileri şifreli bir şekilde saklar ve container’a /run/secrets/ yolu üzerinden bir dosya olarak mount eder. Container içindeki process bu dosyayı okuyarak şifreye erişir. Bu yöntemde şifre hiçbir zaman environment variable olarak process listesinde görünmez.
Secrets iki farklı şekilde tanımlanabilir:
- file tabanlı: Şifreyi bir dosyadan okur
- external: Docker Swarm’daki mevcut bir secret’ı kullanır (production için)
Compose geliştirme ortamında genellikle file tabanlı yaklaşımı kullanırız.
Temel Kurulum: İlk Secrets Yapılandırması
Önce basit bir örnekle başlayalım. PostgreSQL veritabanı şifresini secrets ile yönetelim:
# Önce secret dosyalarını oluştur
mkdir -p secrets
echo "guclu_veritabani_sifresi_2024!" > secrets/db_password.txt
echo "myapp_user" > secrets/db_user.txt
# Bu dosyaları .gitignore'a ekle
echo "secrets/" >> .gitignore
Şimdi docker-compose.yml dosyasını oluşturalım:
# docker-compose.yml - DOGRU YONTEM
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_USER_FILE: /run/secrets/db_user
POSTGRES_DB: myapp_db
secrets:
- db_password
- db_user
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
app:
image: myapp:latest
depends_on:
- db
secrets:
- db_password
environment:
DB_HOST: db
DB_NAME: myapp_db
networks:
- backend
secrets:
db_password:
file: ./secrets/db_password.txt
db_user:
file: ./secrets/db_user.txt
volumes:
postgres_data:
networks:
backend:
Burada dikkat edilmesi gereken önemli nokta: PostgreSQL resmi imajı _FILE suffix’li environment variable’ları destekler. Bu sayede şifreyi /run/secrets/db_password dosyasından okur.
Uygulama Tarafında Secret Okuma
Uygulamanızın secrets dosyasını okuması gerekiyor. İşte birkaç farklı dilde nasıl yapılacağı:
# Python örneği - secret_helper.py
def read_secret(secret_name):
"""Docker secret dosyasini oku"""
secret_path = f"/run/secrets/{secret_name}"
try:
with open(secret_path, 'r') as f:
return f.read().strip()
except FileNotFoundError:
# Geliştirme ortamında environment variable'dan oku
import os
return os.environ.get(secret_name.upper())
# Kullanim
db_password = read_secret("db_password")
api_token = read_secret("api_token")
Bu yaklaşım hem production’da secrets dosyasından hem de lokal geliştirme ortamında environment variable’dan okuma yapabildiği için esnektir.
Gerçek Dünya Senaryosu: Mikroservis Mimarisi
Diyelim ki bir e-ticaret uygulaması deploy ediyorsunuz. Bu uygulama şu bileşenlerden oluşuyor:
- PostgreSQL veritabanı
- Redis cache
- Backend API servisi
- Stripe ödeme entegrasyonu
- SendGrid e-posta servisi
# secrets/ dizin yapısı
secrets/
db_password.txt
db_user.txt
redis_password.txt
stripe_secret_key.txt
sendgrid_api_key.txt
jwt_secret.txt
# docker-compose.yml - Tam mikroservis ornegi
version: '3.8'
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_USER_FILE: /run/secrets/db_user
POSTGRES_DB: ecommerce
secrets:
- db_password
- db_user
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- db_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$(cat /run/secrets/db_user)"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: >
sh -c 'redis-server --requirepass "$$(cat /run/secrets/redis_password)"'
secrets:
- redis_password
networks:
- cache_network
api:
image: ecommerce-api:latest
depends_on:
postgres:
condition: service_healthy
secrets:
- db_password
- db_user
- redis_password
- stripe_secret_key
- sendgrid_api_key
- jwt_secret
environment:
DB_HOST: postgres
DB_NAME: ecommerce
REDIS_HOST: redis
NODE_ENV: production
networks:
- db_network
- cache_network
- frontend_network
ports:
- "3000:3000"
secrets:
db_password:
file: ./secrets/db_password.txt
db_user:
file: ./secrets/db_user.txt
redis_password:
file: ./secrets/redis_password.txt
stripe_secret_key:
file: ./secrets/stripe_secret_key.txt
sendgrid_api_key:
file: ./secrets/sendgrid_api_key.txt
jwt_secret:
file: ./secrets/jwt_secret.txt
volumes:
pgdata:
networks:
db_network:
cache_network:
frontend_network:
Secret Dosyalarını Güvenli Şekilde Oluşturma
Secret dosyalarını elle oluşturmak yerine otomatik ve güvenli bir yöntem kullanmak çok daha iyidir:
#!/bin/bash
# create_secrets.sh - Secret dosyalarini guvenli olustur
SECRETS_DIR="./secrets"
mkdir -p "$SECRETS_DIR"
# Güçlü rastgele şifre üret (32 karakter)
generate_password() {
openssl rand -base64 32 | tr -d "=+/" | cut -c1-32
}
# Eğer dosya yoksa oluştur, varsa dokunma
create_secret_if_not_exists() {
local secret_file="$SECRETS_DIR/$1"
local secret_value="$2"
if [ ! -f "$secret_file" ]; then
echo "$secret_value" > "$secret_file"
chmod 600 "$secret_file"
echo "Olusturuldu: $secret_file"
else
echo "Mevcut, atlandı: $secret_file"
fi
}
# Secrets olustur
create_secret_if_not_exists "db_password.txt" "$(generate_password)"
create_secret_if_not_exists "db_user.txt" "appuser"
create_secret_if_not_exists "redis_password.txt" "$(generate_password)"
create_secret_if_not_exists "jwt_secret.txt" "$(openssl rand -hex 64)"
echo ""
echo "Secrets olusturuldu. Bu dosyalari asla Git'e commit'lemeyin!"
echo "secrets/ dizininin .gitignore'da oldugunu dogrulayin."
# Scripti çalıştırılabilir yap ve çalıştır
chmod +x create_secrets.sh
./create_secrets.sh
# .gitignore kontrolü
cat .gitignore | grep secrets
Secret Güvenliğini Doğrulama
Secrets’ın gerçekten güvenli çalıştığını doğrulamak için şu komutları kullanabilirsiniz:
# Container'ı başlat
docker-compose up -d
# docker inspect ile environment variable'larda şifre görünmemeli
docker inspect ecommerce_api_1 | grep -A 20 '"Env"'
# Çıktıda şifre görünmemeli, sadece DB_HOST, DB_NAME gibi değerler olmalı
# Secret'ın container içinde mount edildiğini doğrula
docker exec ecommerce_api_1 ls -la /run/secrets/
# Çıktı:
# -r--r--r-- 1 root root 33 Jan 15 10:23 db_password
# -r--r--r-- 1 root root 65 Jan 15 10:23 jwt_secret
# Secret içeriğini container içinden oku
docker exec ecommerce_api_1 cat /run/secrets/db_password
# Şifre burada görünür, ama bu kasıtlı - sadece yetkili container erişebilir
# Secret dosyasının izinlerini kontrol et
docker exec ecommerce_api_1 stat /run/secrets/db_password
Docker Swarm ile Production’da External Secrets
Compose dosyanızı production’da Docker Swarm üzerinde çalıştırıyorsanız, external secrets kullanmak çok daha güvenlidir. Bu yöntemde secret değerleri disk üzerinde şifreli saklanır:
# Swarm modunu etkinleştir
docker swarm init
# Secret'ı doğrudan oluştur (dosya kullanmadan)
echo "super_gizli_db_sifresi" | docker secret create db_password -
# Veya dosyadan oluştur ve sonra dosyayı sil
openssl rand -base64 32 > /tmp/db_pass.txt
docker secret create db_password /tmp/db_pass.txt
shred -u /tmp/db_pass.txt # Dosyayı güvenli sil
# Mevcut secret'ları listele
docker secret ls
# Secret detayını gör (değeri göremezsiniz, sadece metadata)
docker secret inspect db_password
# docker-compose.yml - Swarm için external secrets
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
secrets:
db_password:
external: true # Swarm'da onceden olusturulmus secret'i kullan
Secrets Rotasyonu: Şifre Değiştirme Süreci
Production’da periyodik olarak şifre değiştirmeniz gerektiğinde süreci şöyle yönetebilirsiniz:
#!/bin/bash
# rotate_secret.sh - Secret rotasyonu
SECRET_NAME="db_password"
NEW_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32)
echo "Secret rotasyonu basliyor: $SECRET_NAME"
# 1. Adım: Yeni şifreyi veritabanında güncelle
echo "Veritabani sifresi guncelleniyor..."
docker exec postgres_db psql -U postgres -c
"ALTER USER appuser PASSWORD '$NEW_PASSWORD';"
# 2. Adım: Secret dosyasını güncelle
echo "$NEW_PASSWORD" > ./secrets/${SECRET_NAME}.txt
# 3. Adım: Servisleri yeniden başlat
echo "Servisler yeniden baslatiliyor..."
docker-compose up -d --force-recreate api
echo "Secret rotasyonu tamamlandi!"
echo "Yeni şifre secrets/$SECRET_NAME.txt dosyasinda"
CI/CD Pipeline’da Secrets Yönetimi
GitLab CI veya GitHub Actions kullanıyorsanız, secret dosyalarını pipeline içinde güvenli bir şekilde oluşturabilirsiniz:
# .gitlab-ci.yml örneği
deploy_production:
stage: deploy
script:
# CI/CD değişkenlerinden secret dosyaları oluştur
# Bu değerler GitLab CI'da masked variable olarak tanımlanır
- mkdir -p secrets
- echo "$PROD_DB_PASSWORD" > secrets/db_password.txt
- echo "$PROD_STRIPE_KEY" > secrets/stripe_secret_key.txt
- echo "$PROD_JWT_SECRET" > secrets/jwt_secret.txt
- chmod 600 secrets/*.txt
# Deploy et
- docker-compose -f docker-compose.prod.yml up -d
# Güvenlik için secret dosyalarını temizle
- shred -u secrets/*.txt
- rmdir secrets
environment:
name: production
only:
- main
Buradaki kritik nokta: $PROD_DB_PASSWORD gibi değerler GitLab ya da GitHub’da masked ve protected olarak işaretlenmiş CI/CD değişkenlerinden geliyor. Bu sayede log’larda hiç görünmüyor.
Sık Yapılan Hatalar ve Çözümleri
Pratikte karşılaşılan en yaygın problemlerin listesi:
- Secrets dizinini .dockerignore’a eklememek: Docker image build sırasında
COPY . .komutuyla şifre dosyaları image’a dahil olabilir.secrets/dizinini hem.gitignorehem.dockerignoredosyasına ekleyin.
- Secret dosyasında sondaki newline karakteri: Bazı uygulamalar dosyayı okurken
nkarakterini şifrenin parçası olarak algılar.echo -n "sifre"kullanarak veya uygulama tarafında.strip()ile bu sorunu çözebilirsiniz.
- Container içinde gereksiz secret mount’ları: Her servise sadece ihtiyaç duyduğu secret’ları verin. API servisi Redis şifresine ihtiyaç duyuyorsa, veritabanı servisi Stripe API key’ine erişmemelidir.
- Development ve production için aynı yapı: Geliştirme ortamında zayıf test şifresi kullanıyorsanız, bunu açıkça belirtmek için ayrı
docker-compose.dev.ymlvedocker-compose.prod.ymldosyaları kullanın.
- Log’larda secret değerlerini yazdırmak: Uygulamanızın başlangıç loglarında bağlantı string’lerini tam olarak yazdırmayın.
postgresql://user:*@host/dbformatını tercih edin.
Secrets Yönetimi için Güvenlik Kontrol Listesi
Kurulumunuzu tamamladıktan sonra şu maddeleri kontrol edin:
secrets/dizini.gitignore‘da bulunuyor mu?- Secret dosya izinleri
600olarak ayarlı mı? (chmod 600 secrets/*.txt) docker inspectçıktısında şifreler görünmüyor mu?docker-compose.ymldosyasında düz metin şifre var mı?- CI/CD değişkenleri masked ve protected olarak işaretli mi?
- Secret dosyaları image build context’inden hariç tutuluyor mu?
- Periyodik rotasyon planı var mı?
Sonuç
Docker Compose Secrets, hassas veri yönetimi için minimum eforla maksimum güvenlik sağlayan bir mekanizma. Environment variable’ların yetersizliğinden kaynaklanan güvenlik açıklarını kapatıyor, secrets’ı container filesystem’ine salt okunur dosya olarak mount ediyor ve docker inspect gibi araçlardan gizliyor.
Özetlemek gerekirse: secret dosyalarınızı oluşturun, .gitignore ve .dockerignore‘a ekleyin, docker-compose.yml içinde secrets: bloğunu tanımlayın ve uygulamanızı /run/secrets/ yolundan okuyacak şekilde yapılandırın. Production’da Docker Swarm kullanıyorsanız external: true ile Swarm’ın yerleşik şifreli secret store’undan yararlanın.
Bu yöntemi benimsemek başta biraz zahmetli görünse de bir ekip büyüdükçe, repo’ya erişim sayısı arttıkça ve audit gereksinimleri çıktıkça bu küçük yatırımın ne kadar değerli olduğu anlaşılıyor. Güvenliği sonradan yamamamak için baştan doğru alışkanlıkları edinmek, uzun vadede hem teknik borcu hem de potansiyel güvenlik olaylarını önlüyor.
