Üretim ortamında bir Docker Compose projesi kurarken en sık yapılan hatalardan biri, veritabanı şifrelerini, API anahtarlarını ve diğer hassas bilgileri doğrudan docker-compose.yml dosyasına yazmaktır. Bu dosyayı Git’e commit ettiğiniz anda, o şifreler sonsuza kadar tarihte kalır. .env dosyası bu sorunu zarif bir şekilde çözer, ama doğru kullanılmadığında kendisi de sorun kaynağı haline gelebilir. Bu yazıda .env dosyasını Docker Compose ile nasıl düzgün kullanacağınızı, tuzaklardan nasıl kaçınacağınızı ve gerçek dünya senaryolarında nasıl yapılandıracağınızı ele alacağız.
.env Dosyası Nedir ve Docker Compose Bunu Nasıl Kullanır?
.env dosyası, anahtar-değer çiftlerinden oluşan düz metin bir konfigürasyon dosyasıdır. Docker Compose, varsayılan olarak docker-compose.yml ile aynı dizindeki .env dosyasını otomatik olarak okur ve içindeki değişkenleri compose dosyasında kullanılabilir hale getirir.
Burada kritik bir ayrım var: .env dosyasındaki değişkenler Docker Compose’un kendisi tarafından okunur, yani docker-compose.yml içindeki ${DEGISKEN} ifadelerini çözümlemek için kullanılır. Bu değişkenler otomatik olarak container’ın içine geçmez, bunu sağlamak için ayrıca yapılandırma yapmanız gerekir.
# Proje dizin yapısı
myapp/
├── docker-compose.yml
├── .env
├── .env.example
└── app/
└── ...
Basit bir örnek görelim:
# .env dosyası
POSTGRES_USER=appuser
POSTGRES_PASSWORD=super_secret_123
POSTGRES_DB=myapp_production
APP_PORT=8080
REDIS_PASSWORD=redis_secret_456
# docker-compose.yml
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
app:
build: ./app
ports:
- "${APP_PORT}:3000"
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
- db
volumes:
pgdata:
docker compose config komutuyla değişkenlerin doğru çözümlenip çözümlenmediğini kontrol edebilirsiniz. Bu komutu sık kullanın, sizi çok kez kurtarır.
docker compose config
env_file ile environment Arasındaki Fark
Pek çok sysadmin bu ikisini karıştırır. Aralarındaki farkı net anlamak gerekiyor.
environment direktifi: docker-compose.yml içinde değişkenleri doğrudan container’a geçirir. Değerleri .env dosyasından veya shell ortamından alabilir.
env_file direktifi: Belirttiğiniz dosyadaki tüm değişkenleri container’ın içine aktarır. .env dosyasından farklı bir dosya kullanmanıza olanak tanır.
# environment direktifi kullanımı
services:
app:
image: myapp:latest
environment:
- DATABASE_URL=${DATABASE_URL}
- APP_ENV=production
- DEBUG=false
# env_file direktifi kullanımı
worker:
image: myapp:latest
env_file:
- .env
- .env.production
env_file kullandığınızda o dosyadaki tüm değişkenler container’a geçer. Bu bazen istemediğiniz değişkenlerin de container içine girmesine yol açar. Bu yüzden ben genellikle environment direktifiyle ihtiyaç duyulan değişkenleri tek tek belirtmeyi tercih ederim, en azından production ortamlarında.
Gerçek Dünya Senaryosu: Çok Servisli Bir Uygulama
Diyelim ki bir e-ticaret uygulaması kuruyorsunuz. PostgreSQL, Redis, bir uygulama sunucusu ve Nginx’ten oluşan bir stack. Her servisin farklı kimlik bilgileri ve konfigürasyon ihtiyaçları var.
# .env dosyası - TÜM ORTAM DEĞİŞKENLERİ
# Veritabanı
POSTGRES_USER=ecommerce_user
POSTGRES_PASSWORD=Pg_S3cure_Pass_2024!
POSTGRES_DB=ecommerce_prod
POSTGRES_PORT=5432
# Redis
REDIS_PASSWORD=Redis_S3cure_456!
REDIS_PORT=6379
# Uygulama
APP_ENV=production
APP_PORT=3000
APP_SECRET_KEY=a_very_long_random_secret_key_here
JWT_SECRET=another_random_jwt_secret_key
# SMTP
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASSWORD=SG.your_sendgrid_api_key
# External APIs
STRIPE_SECRET_KEY=sk_live_your_stripe_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
# Nginx
NGINX_PORT=80
NGINX_SSL_PORT=443
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- backend
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redisdata:/data
networks:
- backend
app:
build:
context: ./app
args:
APP_ENV: ${APP_ENV}
restart: unless-stopped
environment:
NODE_ENV: ${APP_ENV}
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:${POSTGRES_PORT}/${POSTGRES_DB}
REDIS_URL: redis://:${REDIS_PASSWORD}@redis:${REDIS_PORT}
SECRET_KEY: ${APP_SECRET_KEY}
JWT_SECRET: ${JWT_SECRET}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
depends_on:
- postgres
- redis
networks:
- backend
- frontend
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "${NGINX_PORT}:80"
- "${NGINX_SSL_PORT}:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- frontend
volumes:
pgdata:
redisdata:
networks:
backend:
frontend:
Varsayılan Değerler ve Zorunlu Değişkenler
Docker Compose, değişkenler için varsayılan değer tanımlamanıza olanak tanır. Bu özellikle development ortamlarında işe yarar.
# Varsayılan değer söz dizimi örnekleri
# ${DEGISKEN:-varsayilan} : Değişken tanımlı değilse veya boşsa varsayılanı kullan
APP_PORT: ${APP_PORT:-3000}
# ${DEGISKEN-varsayilan} : Sadece değişken tanımlı değilse varsayılanı kullan
DEBUG: ${DEBUG-false}
# ${DEGISKEN:?hata_mesaji} : Değişken yoksa veya boşsa hata ver ve dur
DATABASE_URL: ${DATABASE_URL:?DATABASE_URL değişkeni tanımlanmalı!}
# ${DEGISKEN?hata_mesaji} : Sadece değişken yoksa hata ver
SECRET_KEY: ${SECRET_KEY?SECRET_KEY ortam değişkeni gerekli}
Production’da kritik değişkenlerin tanımlı olmasını zorlamak için ? sözdizimini kullanmak çok iyi bir pratiktir. Eksik bir değişkenle container’ın ayağa kalkmasına izin vermez.
Farklı Ortamlar İçin .env Dosyası Stratejisi
Gerçek projelerde birden fazla ortam vardır: development, staging ve production. Her ortam için ayrı .env dosyaları tutmak ve bunları doğru şekilde yönetmek kritik önem taşır.
# Önerilen dosya yapısı
myapp/
├── docker-compose.yml
├── docker-compose.override.yml # Development override
├── docker-compose.prod.yml # Production specific
├── .env.example # Git'e commit edilir, şablon
├── .env.development # Git'e commit edilmez
├── .env.staging # Git'e commit edilmez
├── .env.production # Git'e commit edilmez
└── .env # Aktif ortam, git'e commit edilmez
# .env.example - Git'e bu dosyayı commit edin
# Gerçek değerler OLMADAN, sadece hangi değişkenlerin gerektiğini gösterir
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
APP_PORT=3000
APP_ENV=development
APP_SECRET_KEY=
JWT_SECRET=
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
REDIS_PASSWORD=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
Ortam değiştirmek için basit bir script yazabilirsiniz:
#!/bin/bash
# switch-env.sh
ENV=${1:-development}
if [ ! -f ".env.${ENV}" ]; then
echo "HATA: .env.${ENV} dosyası bulunamadı"
exit 1
fi
cp ".env.${ENV}" .env
echo "Ortam ${ENV} olarak ayarlandı"
# Compose'u yeniden başlat
docker compose down
docker compose up -d
Development ortamında docker-compose.override.yml kullanarak production compose dosyasını kirletmeden geliştirme kolaylıkları ekleyebilirsiniz:
# docker-compose.override.yml - sadece development için
version: '3.8'
services:
app:
volumes:
- ./app:/usr/src/app # Hot reload için
- /usr/src/app/node_modules
environment:
DEBUG: "true"
LOG_LEVEL: debug
postgres:
ports:
- "5432:5432" # Local bağlantı için portu aç
redis:
ports:
- "6379:6379" # Local bağlantı için portu aç
.gitignore Konfigürasyonu
Bu kısım sysadminlerin sıklıkla atladığı ama son derece önemli bir konu. .env dosyalarınızı Git’e commit etmemek için .gitignore dosyanızı doğru yapılandırın.
# .gitignore içeriği
# Environment dosyaları - ASLA commit etme
.env
.env.local
.env.development
.env.staging
.env.production
.env.*.local
# .env.example her zaman commit edilmeli, bu yüzden burada exclude yok
# !.env.example # Bu satırı ekleyebilirsiniz ama genellikle gerek yok
# Docker volumes
docker/volumes/
Eğer yanlışlıkla bir .env dosyası commit ettiyseniz, onu geçmişten temizlemek için git filter-branch veya BFG Repo Cleaner kullanmanız ve tüm credentials’ları değiştirmeniz gerekir. En iyisi baştan önlem almak.
# Mevcut .env dosyasını takipten çıkar (daha önce commit edildiyse)
git rm --cached .env
git commit -m "Remove .env from tracking"
# Şu andan itibaren .gitignore devreye girer
Docker Secrets ile Karşılaştırma ve Hibrit Yaklaşım
.env dosyası uygun çözümdür, ancak production ortamlarında Docker Secrets veya harici bir secret manager (HashiCorp Vault, AWS Secrets Manager) ile kombinlemek daha güvenlidir. Şimdi basit bir karşılaştırma yaparak nerede .env‘in yeterli olduğunu görelim.
.env dosyası yeterlidir:
- Tek sunucu deployment’ları
- Small-medium ölçekli projeler
- Swarm modunda değilsiniz
- Dosya izinlerini doğru ayarladınız
Docker Secrets daha uygundur:
- Docker Swarm kullanıyorsunuz
- Birden fazla node’a deploy ediyorsunuz
- Sıkı güvenlik gereksinimleri var
Production’da .env dosyası kullanıyorsanız en azından dosya izinlerini doğru ayarlayın:
# .env dosyası izinlerini ayarla
# Sadece root okuyabilsin
chmod 600 .env
chown root:root .env
# Veya sadece compose'u çalıştıran kullanıcı okuyabilsin
chown deploy_user:deploy_user .env
chmod 600 .env
# İzinleri kontrol et
ls -la .env
# Çıktı: -rw------- 1 deploy_user deploy_user 512 Kas 15 10:30 .env
Değişken Doğrulama Script’i
Production’a deploy etmeden önce tüm gerekli değişkenlerin tanımlı olduğunu kontrol eden bir script yazmak son derece iyi bir pratiktir.
#!/bin/bash
# validate-env.sh
RED='33[0;31m'
GREEN='33[0;32m'
YELLOW='33[1;33m'
NC='33[0m'
ENV_FILE=${1:-.env}
ERRORS=0
# Gerekli değişkenler listesi
REQUIRED_VARS=(
"POSTGRES_USER"
"POSTGRES_PASSWORD"
"POSTGRES_DB"
"APP_SECRET_KEY"
"JWT_SECRET"
"REDIS_PASSWORD"
"SMTP_HOST"
"SMTP_PASSWORD"
"STRIPE_SECRET_KEY"
)
# Minimum uzunluk gereksinimleri
declare -A MIN_LENGTH
MIN_LENGTH["POSTGRES_PASSWORD"]=12
MIN_LENGTH["APP_SECRET_KEY"]=32
MIN_LENGTH["JWT_SECRET"]=32
echo "=== .env Dosyası Doğrulaması ==="
echo "Dosya: ${ENV_FILE}"
echo ""
# Dosya var mı kontrol et
if [ ! -f "${ENV_FILE}" ]; then
echo -e "${RED}HATA: ${ENV_FILE} dosyası bulunamadı!${NC}"
exit 1
fi
# .env dosyasını yükle
source "${ENV_FILE}"
# Gerekli değişkenleri kontrol et
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
echo -e "${RED}[HATA]${NC} ${var} tanımlanmamış veya boş"
ERRORS=$((ERRORS + 1))
else
# Minimum uzunluk kontrolü
if [ -n "${MIN_LENGTH[$var]}" ]; then
if [ ${#!var} -lt ${MIN_LENGTH[$var]} ]; then
echo -e "${YELLOW}[UYARI]${NC} ${var} çok kısa (min: ${MIN_LENGTH[$var]} karakter)"
else
echo -e "${GREEN}[OK]${NC} ${var}"
fi
else
echo -e "${GREEN}[OK]${NC} ${var}"
fi
fi
done
echo ""
if [ $ERRORS -gt 0 ]; then
echo -e "${RED}${ERRORS} hata bulundu. Deploy edilemiyor.${NC}"
exit 1
else
echo -e "${GREEN}Tüm kontroller geçti. Deploy edilebilir.${NC}"
exit 0
fi
Bu script’i CI/CD pipeline’ınıza ekleyerek eksik değişkenlerle production’a deploy yapılmasını engelleyebilirsiniz.
Sık Yapılan Hatalar ve Çözümleri
Değişken adında boşluk veya yanlış sözdizimi:
# YANLIŞ - eşittir işaretinin etrafında boşluk olmamalı
POSTGRES_PASSWORD = secret123
# DOĞRU
POSTGRES_PASSWORD=secret123
# YANLIŞ - değer tırnak içinde olmamalı (bazı durumlarda sorun çıkarır)
APP_NAME="My Application"
# Boşluk içeren değerler için tırnak gerekli
APP_NAME=My Application # YANLIŞ
APP_NAME="My Application" # DOĞRU
Container içinden değişkene erişememe:
En yaygın sorunlardan biri .env dosyasının docker compose tarafından okunduğunu ama container içine geçirilmediğini unutmak. Container’ınızın içinden printenv veya env komutuyla kontrol edin:
# Container içindeki değişkenleri kontrol et
docker compose exec app env | grep APP_
# Belirli bir değişkeni kontrol et
docker compose exec app printenv DATABASE_URL
# Container başlarken değişkenleri logla (debugging için)
docker compose exec app sh -c 'echo "DB: $DATABASE_URL"'
Özel karakterler içeren şifreler:
# Bazı özel karakterler sorun çıkarabilir
# Özellikle $, #, = gibi karakterler
POSTGRES_PASSWORD=pass#word$123 # Sorunlu olabilir
# Çözüm: Değeri tırnak içine alın
POSTGRES_PASSWORD="pass#word$123"
# Ya da özel karakterden kaçının (escape)
POSTGRES_PASSWORD=pass#word$123
CI/CD Entegrasyonu
GitHub Actions veya GitLab CI kullananlar için environment değişkenlerini pipeline’a güvenli şekilde nasıl geçireceğinizi göstermek istiyorum.
# .github/workflows/deploy.yml
name: Deploy Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: .env dosyasını oluştur
run: |
cat > .env << EOF
POSTGRES_USER=${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_DB=${{ secrets.POSTGRES_DB }}
APP_SECRET_KEY=${{ secrets.APP_SECRET_KEY }}
JWT_SECRET=${{ secrets.JWT_SECRET }}
REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}
STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }}
APP_ENV=production
EOF
- name: Doğrulama yap
run: bash validate-env.sh
- name: Deploy et
run: |
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
- name: .env dosyasını temizle
if: always()
run: rm -f .env
GitHub Secrets bölümüne bu değerleri ekleyip pipeline’dan güvenli şekilde deploy yapabilirsiniz. .env dosyası sadece deploy sürecinde oluşturulur ve sonunda silinir.
Değişken Öncelik Sırası
Docker Compose’da değişken önceliğini bilmek debugging’de çok yardımcı olur:
- Shell ortam değişkenleri en yüksek önceliğe sahiptir
.envdosyasındaki değişkenler bunların altında gelirdocker-compose.ymliçindeki varsayılan değerler en düşük önceliğe sahiptir
# Shell'den değişken geçirme - .env'i override eder
export APP_PORT=9090
docker compose up -d
# APP_PORT .env'de 3000 olsa bile 9090 kullanılır
# Tek seferlik override
APP_PORT=9090 docker compose up -d
# Hangi değerlerin kullanıldığını görmek için
docker compose config | grep -A 2 "ports:"
Sonuç
.env dosyası Docker Compose projelerinde ortam değişkeni yönetimi için güçlü ve pratik bir çözüm. Ancak doğru kullanılmadığında güvenlik açıklarına ve konfigürasyon sorunlarına yol açabiliyor.
Özetlemek gerekirse:
.envdosyasını her zaman.gitignore‘a ekleyin,.env.example‘ı commit edinenvironmentdirektifiyle sadece ihtiyaç duyulan değişkenleri container’a geçirin- Production’da dosya izinlerini
chmod 600ile sınırlandırın - Kritik değişkenler için
${VAR:?hata mesajı}sözdizimini kullanın - Her ortam için ayrı
.envdosyası tutun - Deploy öncesi değişken doğrulama script’i çalıştırın
- CI/CD pipeline’ında
.envdosyasını runtime’da oluşturup sonra silin
Bu pratikleri uyguladığınızda hem güvenli hem de yönetilebilir bir konfigürasyon altyapısına sahip olursunuz. Küçük projelerde .env dosyası tek başına yeterlidir; ölçek büyüdükçe HashiCorp Vault veya cloud provider’ınızın secret manager’ına geçişi değerlendirmenizi öneririm.