.env Dosyası ile Docker Compose Ortam Değişkeni Yönetimi

Ü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
  • .env dosyasındaki değişkenler bunların altında gelir
  • docker-compose.yml iç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:

  • .env dosyasını her zaman .gitignore‘a ekleyin, .env.example‘ı commit edin
  • environment direktifiyle sadece ihtiyaç duyulan değişkenleri container’a geçirin
  • Production’da dosya izinlerini chmod 600 ile sınırlandırın
  • Kritik değişkenler için ${VAR:?hata mesajı} sözdizimini kullanın
  • Her ortam için ayrı .env dosyası tutun
  • Deploy öncesi değişken doğrulama script’i çalıştırın
  • CI/CD pipeline’ında .env dosyası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.

Yorum yapın