← Kursa Dön
📄 Text · 30 min

Environment Variables ve Container Konfigürasyonu

Gerçek Hayat Analojisi

Bir kahve makinesini düşün. Makine her yerde aynı — İstanbul'daki ofiste de, Berlin'deki ofiste de aynı marka, aynı model. Ama ayarları farklı: İstanbul'da Türk kahvesi modu açık, su sertliği 15°dH. Berlin'de filtre kahve modu, su sertliği 8°dH. Makineyi değiştirmiyorsun — ayarlarını değiştiriyorsun.

Docker container'ları da böyle çalışır. Aynı image, farklı ortamlarda farklı davranır. Bu farkı yaratan şey environment variables (ortam değişkenleri). Veritabanı adresi, API anahtarı, log seviyesi, port numarası — hepsi environment variable'larla ayarlanır. Aynı image, farklı config = farklı ortam. Bu derste environment variable'ları, konfigürasyon yönetimini ve güvenli kullanım pratiklerini derinlemesine öğreneceğiz.


Environment Variables Temelleri

Linux'ta Environment Variables

# Mevcut ortam değişkenlerini gör
env
printenv
printenv PATH

# Değişken tanımla
export MY_VAR="hello"
echo $MY_VAR
# hello

# Geçici değişken (sadece o komut için)
MY_VAR="world" echo $MY_VAR

Docker'da Environment Variables

Docker container'larına environment variable geçirmenin birkaç yolu var:

# 1. docker run -e ile tekil değişken
docker run -e MY_VAR=hello alpine env
# MY_VAR=hello

# 2. docker run -e ile host'tan aktarma (değer vermeden)
export API_KEY=abc123
docker run -e API_KEY alpine env
# API_KEY=abc123 (host'taki değer aktarılır)

# 3. --env-file ile dosyadan yükleme
docker run --env-file .env alpine env

# 4. Dockerfile'da ENV ile (image'a gömülü)
# ENV NODE_ENV=production

-e / --env Flag'i ile Değişken Geçirme

Temel Kullanım

# Tekil değişken
docker run -d \
    -e MYSQL_ROOT_PASSWORD=secret \
    -e MYSQL_DATABASE=myapp \
    mysql:8

# Birden fazla değişken
docker run -d \
    -e NODE_ENV=production \
    -e PORT=3000 \
    -e LOG_LEVEL=info \
    -e DATABASE_URL=postgres://user:pass@db:5432/myapp \
    -e REDIS_URL=redis://redis:6379 \
    -e SECRET_KEY=my-super-secret-key \
    --name api myapp

Host Değişkenlerini Aktarma

# Host'ta tanımlı değişkeni aktarma (değer belirtmeden)
export DATABASE_URL="postgres://user:pass@localhost:5432/myapp"
export REDIS_URL="redis://localhost:6379"
export SECRET_KEY="abc123"

# Sadece isimle aktarırsan, host'taki değer kullanılır
docker run -d \
    -e DATABASE_URL \
    -e REDIS_URL \
    -e SECRET_KEY \
    --name api myapp

# Container içinde:
docker exec api printenv DATABASE_URL
# postgres://user:pass@localhost:5432/myapp

Özel Karakterler İçeren Değerler

# Boşluk içeren değer — tırnak kullan
docker run -e "GREETING=Hello World" alpine echo $GREETING

# Özel karakterler
docker run -e 'DB_PASS=p@$$w0rd!#' alpine printenv DB_PASS
# p@$$w0rd!#

# Çok satırlı değer
docker run -e $'MULTILINE=line1\nline2\nline3' alpine printenv MULTILINE
# line1
# line2
# line3

# JSON değer
docker run -e 'CONFIG={"key":"value","port":3000}' alpine printenv CONFIG
# {"key":"value","port":3000}

--env-file ile Dosyadan Yükleme

Çok sayıda environment variable varsa, dosyadan yüklemek daha temiz:

.env Dosyası Formatı

# .env
# Yorum satırları # ile başlar
# Boş satırlar göz ardı edilir
# Format: KEY=VALUE (tırnaksız)

# Veritabanı
DATABASE_URL=postgres://user:pass@db:5432/myapp
DATABASE_POOL_SIZE=20

# Redis
REDIS_URL=redis://redis:6379
REDIS_TTL=3600

# Uygulama
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
SECRET_KEY=my-super-secret-key-change-in-prod

# API Entegrasyonları
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=noreply@myapp.com
SMTP_PASS=app-specific-password

# Feature Flags
ENABLE_CACHE=true
ENABLE_RATE_LIMIT=true
MAINTENANCE_MODE=false
# .env dosyasından yükleme
docker run -d --env-file .env --name api myapp

# Birden fazla env dosyası
docker run -d \
    --env-file .env \
    --env-file .env.local \
    --name api myapp
# .env.local'daki değerler .env'dekileri override eder

# env-file + tekil -e kombinasyonu
docker run -d \
    --env-file .env \
    -e LOG_LEVEL=debug \
    --name api myapp
# LOG_LEVEL=debug olur (tekil -e öncelikli)

Ortam Bazlı .env Dosyaları

# Dosya yapısı
myapp/
├── .env                  # Varsayılan/ortak değerler
├── .env.development      # Geliştirme
├── .env.staging          # Staging
├── .env.production       # Production
└── .env.local            # Yerel override (git'e eklenmez!)

# Ortama göre kullan
docker run -d --env-file .env --env-file .env.development --name api-dev myapp
docker run -d --env-file .env --env-file .env.staging --name api-staging myapp
docker run -d --env-file .env --env-file .env.production --name api-prod myapp

.env Dosyası Dikkat Noktaları

# ⚠️ Docker .env formatı ile docker-compose .env formatı FARKLI

# Docker run --env-file formatı:
# - Tırnak kullanma (tırnak değerin parçası olur!)
# - VAR="hello" → değer: "hello" (tırnaklar dahil!)
# - VAR=hello → değer: hello ✓

# docker-compose .env formatı:
# - Tırnak kullanılabilir (kaldırılır)
# - VAR="hello" → değer: hello ✓
# - VAR=hello → değer: hello ✓

# ❌ docker run --env-file ile YANLIŞ
MY_VAR="hello world"
# Container'da: MY_VAR="hello world" (tırnaklar dahil!)

# ✅ docker run --env-file ile DOĞRU
MY_VAR=hello world
# Container'da: MY_VAR=hello world ✓

Dockerfile ENV Talimatı

ENV ile Image'a Değişken Gömme

# Dockerfile
FROM node:20-alpine

# Tek değişken
ENV NODE_ENV=production
ENV PORT=3000

# Build ve runtime'da kullanılır
ENV APP_HOME=/app
WORKDIR ${APP_HOME}

COPY . ${APP_HOME}
CMD ["node", "server.js"]
# ENV değerleri image'a gömülüdür
docker inspect myapp --format '{{json .Config.Env}}' | jq .
# [
#   "NODE_ENV=production",
#   "PORT=3000",
#   "APP_HOME=/app",
#   "PATH=/usr/local/sbin:..."
# ]

# docker run -e ile override edilebilir
docker run -e NODE_ENV=development -e PORT=8080 myapp
# NODE_ENV=development (override edildi)
# PORT=8080 (override edildi)
# APP_HOME=/app (image'daki değer)

ENV vs ARG

# ARG: Sadece build sırasında kullanılır, runtime'da YOK
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine

ARG BUILD_DATE
LABEL build-date=${BUILD_DATE}

# ENV: Build VE runtime'da kullanılır
ENV NODE_ENV=production
ENV PORT=3000

# ARG → ENV aktarımı
ARG APP_VERSION=1.0.0
ENV APP_VERSION=${APP_VERSION}
# Artık runtime'da da erişilebilir
# ARG değeri geçirme
docker build --build-arg NODE_VERSION=18 --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) .

# Container'da ARG değerleri YOK
docker exec myapp printenv NODE_VERSION
# (boş — ARG runtime'da mevcut değil)

# Ama ENV aktarılan ARG değeri VAR
docker exec myapp printenv APP_VERSION
# 1.0.0
ENVARG
Build sırasında
Runtime'da
docker build --build-arg
docker run -e ile override
Image metadata'sında✅ (inspect ile görünür)
Gizli bilgi için güvenli mi?❌ (inspect ile okunabilir)❌ (build history'de görünebilir)

Popüler Image'larda Environment Variables

PostgreSQL

docker run -d \
    -e POSTGRES_USER=myapp \
    -e POSTGRES_PASSWORD=supersecret \
    -e POSTGRES_DB=myapp_production \
    -e POSTGRES_INITDB_ARGS="--encoding=UTF-8" \
    -e PGDATA=/var/lib/postgresql/data/pgdata \
    --name db postgres:16

MySQL

docker run -d \
    -e MYSQL_ROOT_PASSWORD=rootsecret \
    -e MYSQL_DATABASE=myapp \
    -e MYSQL_USER=myapp \
    -e MYSQL_PASSWORD=usersecret \
    -e MYSQL_CHARSET=utf8mb4 \
    -e MYSQL_COLLATION=utf8mb4_unicode_ci \
    --name mysql mysql:8

Redis

docker run -d \
    -e REDIS_PASSWORD=redissecret \
    --name redis redis:7 \
    redis-server --requirepass redissecret --maxmemory 256mb --maxmemory-policy allkeys-lru

Nginx

# Nginx envsubst ile template kullanır
docker run -d \
    -e NGINX_HOST=myapp.com \
    -e NGINX_PORT=80 \
    -v ./templates:/etc/nginx/templates \
    --name web nginx:1.25

Konfigürasyon Yönetim Stratejileri

Strateji 1: Environment Variables (12-Factor App)

# 12-Factor App metodolojisi: konfigürasyon ortam değişkenlerinde olmalı
docker run -d \
    -e DATABASE_URL=postgres://user:pass@db:5432/myapp \
    -e REDIS_URL=redis://redis:6379 \
    -e PORT=3000 \
    -e LOG_LEVEL=info \
    myapp

# Avantaj: Basit, her ortamda farklı, image değişmez
# Dezavantaj: Çok sayıda değişken yönetmek zor, secret güvenliği

Strateji 2: Config Dosyası + Volume Mount

# Config dosyasını mount et
docker run -d \
    -v ./config/production.json:/app/config.json:ro \
    myapp

# Avantaj: Karmaşık config, validation
# Dezavantaj: Dosya yönetimi, dağıtım

Strateji 3: Config + ENV Hybrid

# Temel config dosyada, ortam-spesifik değerler ENV'de
docker run -d \
    -v ./config/base.yml:/app/config.yml:ro \
    -e DATABASE_URL=postgres://user:pass@db:5432/myapp \
    -e SECRET_KEY=abc123 \
    myapp

# En yaygın yaklaşım
# Config dosyası: Değişmeyen ayarlar (timeout, retry, format)
# ENV: Ortam-spesifik ayarlar (URL'ler, credential'lar)

Strateji 4: Entrypoint Script ile Config Üretimi

#!/bin/sh
# entrypoint.sh — ENV'lerden config dosyası üret

cat > /app/config.json << EOF
{
    "database": {
        "url": "${DATABASE_URL}",
        "pool_size": ${DB_POOL_SIZE:-10}
    },
    "redis": {
        "url": "${REDIS_URL}",
        "ttl": ${REDIS_TTL:-3600}
    },
    "server": {
        "port": ${PORT:-3000},
        "log_level": "${LOG_LEVEL:-info}"
    }
}
EOF

exec "$@"
FROM node:20-alpine
WORKDIR /app
COPY . .
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "server.js"]

Environment Variables Güvenliği

⚠️ Tehlike: ENV ile Secret Gömme

# ❌ YANLIŞ: Secret'ı image'a gömme
# Dockerfile:
# ENV API_KEY=sk-live-abc123xyz

# Neden tehlikeli?
docker inspect myapp --format '{{json .Config.Env}}' | jq .
# ["API_KEY=sk-live-abc123xyz", ...]  ← Herkes görebilir!

docker history myapp
# ENV API_KEY=sk-live-abc123xyz  ← Build history'de de var!

✅ Güvenli Yaklaşımlar

# 1. Runtime'da -e ile geçir (image'a gömülmez)
docker run -e API_KEY=sk-live-abc123xyz myapp
# Image'da yok, sadece çalışan container'da var

# 2. --env-file ile dosyadan yükle
echo "API_KEY=sk-live-abc123xyz" > .env.secrets
docker run --env-file .env.secrets myapp
# .env.secrets dosyasını git'e ekleme!

# 3. Docker Secrets (Swarm mode)
echo "sk-live-abc123xyz" | docker secret create api_key -
docker service create --secret api_key myapp
# Container içinde: /run/secrets/api_key dosyasından okunur

# 4. BuildKit secret mount (build sırasında)
# Dockerfile:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
# docker build --secret id=npmrc,src=.npmrc .
# Secret hiçbir katmana yazılmaz!

docker inspect ile Secret Görünürlüğü

# ⚠️ docker inspect ile ENV'ler görülebilir
docker inspect myapp --format '{{json .Config.Env}}' | jq .
# ["DATABASE_URL=postgres://user:REAL_PASSWORD@db:5432/myapp"]
# 😱 Şifre açık!

# Çözüm: Hassas bilgiler için Docker secrets veya vault kullan
# inspect ile okunabilecek bilgilere dikkat et

.env Dosyası Güvenliği

# .gitignore'a ekle
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore
echo "!.env.example" >> .gitignore

# .env.example oluştur (gerçek değerler olmadan)
cat > .env.example << 'EOF'
# Database
DATABASE_URL=postgres://user:password@localhost:5432/myapp

# Redis
REDIS_URL=redis://localhost:6379

# Application
NODE_ENV=development
PORT=3000
SECRET_KEY=change-me-in-production
LOG_LEVEL=debug

# API Keys
SMTP_HOST=smtp.example.com
SMTP_USER=your-email@example.com
SMTP_PASS=your-app-specific-password
EOF

# Yeni geliştirici:
cp .env.example .env
# .env dosyasını düzenle ve gerçek değerleri gir

Varsayılan Değerler ve Fallback

Uygulama Kodunda Fallback

// Node.js — varsayılan değerler
const config = {
    port: parseInt(process.env.PORT) || 3000,
    nodeEnv: process.env.NODE_ENV || 'development',
    logLevel: process.env.LOG_LEVEL || 'info',
    dbUrl: process.env.DATABASE_URL || 'postgres://localhost:5432/myapp',
    redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
};

// Zorunlu değişken kontrolü
const required = ['SECRET_KEY', 'DATABASE_URL'];
for (const key of required) {
    if (!process.env[key]) {
        console.error(`ERROR: ${key} environment variable is required`);
        process.exit(1);
    }
}
# Python — varsayılan değerler
import os

PORT = int(os.environ.get('PORT', 5000))
DEBUG = os.environ.get('DEBUG', 'false').lower() == 'true'
DATABASE_URL = os.environ.get('DATABASE_URL', 'sqlite:///app.db')
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')

# Zorunlu değişken kontrolü
SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
    raise ValueError("SECRET_KEY environment variable is required")

Dockerfile'da Varsayılan Değerler

# Varsayılan değerlerle ENV
ENV NODE_ENV=production
ENV PORT=3000
ENV LOG_LEVEL=info

# docker run -e PORT=8080 ile override edilebilir

Entrypoint Script'te Varsayılan Değerler

#!/bin/sh
# Shell parameter expansion ile varsayılan değerler

DATABASE_URL=${DATABASE_URL:-postgres://localhost:5432/myapp}
REDIS_URL=${REDIS_URL:-redis://localhost:6379}
PORT=${PORT:-3000}
LOG_LEVEL=${LOG_LEVEL:-info}

# Zorunlu değişken kontrolü
: "${SECRET_KEY:?SECRET_KEY is required}"
: "${DATABASE_URL:?DATABASE_URL is required}"

echo "Starting with PORT=$PORT, LOG_LEVEL=$LOG_LEVEL"
exec "$@"

Docker Compose ile Environment Variables

# docker-compose.yml
version: '3.8'
services:
  api:
    image: myapp:v1
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
    env_file:
      - .env
      - .env.local

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: ${DB_PASSWORD}  # Host ENV'den
      POSTGRES_DB: myapp

  redis:
    image: redis:7
    environment:
      - REDIS_PASSWORD=${REDIS_PASSWORD:-defaultpass}
# docker-compose .env dosyası (proje root'unda)
# Bu dosya docker-compose.yml'deki ${VARIABLE} yerlerine konur
DB_PASSWORD=supersecret
REDIS_PASSWORD=redissecret
APP_VERSION=v1.2.3

Gerçek Dünya Senaryosu: Multi-Environment Deployment

# === Development ===
docker run -d \
    --env-file .env.development \
    -e NODE_ENV=development \
    -e LOG_LEVEL=debug \
    -e DATABASE_URL=postgres://dev:dev@localhost:5432/myapp_dev \
    -p 3000:3000 \
    --name api-dev myapp:latest

# === Staging ===
docker run -d \
    --env-file .env.staging \
    -e NODE_ENV=staging \
    -e LOG_LEVEL=info \
    -e DATABASE_URL=postgres://staging:$STAGING_DB_PASS@staging-db:5432/myapp \
    -p 3000:3000 \
    --name api-staging myapp:v1.2.3

# === Production ===
docker run -d \
    --env-file .env.production \
    -e NODE_ENV=production \
    -e LOG_LEVEL=warn \
    -e DATABASE_URL=postgres://prod:$PROD_DB_PASS@prod-db:5432/myapp \
    -p 3000:3000 \
    --name api-prod myapp:v1.2.3

# Aynı image (myapp:v1.2.3), 3 farklı ortam
# Fark sadece environment variables'da

Best Practices

Yap:

  • 12-Factor App prensibi: konfigürasyonu environment variable'larda tut

  • Hassas bilgiler için runtime -e veya Docker secrets kullan — image'a gömme

  • .env.example dosyası oluştur — yeni geliştiriciler ne lazım bilsin

  • Uygulamada varsayılan değerler ve zorunlu değişken kontrolü yap

  • .env dosyalarını .gitignore'a ekle — repo'ya commit'leme

  • Ortam bazlı .env dosyaları kullan (.env.development, .env.production)

Yapma:

  • Dockerfile'da ENV ile gizli bilgi gömme (inspect ile okunur)

  • .env dosyalarını Git'e commit'leme

  • Environment variable'ları hardcode'lama (ortam değiştiğinde image'ı yeniden build etme)

  • Çok sayıda -e flag'i kullanma — --env-file kullan

  • Varsayılan değer olmadan zorunlu değişken kontrolü yapmamak (container çöker, neden belli olmaz)


Yaygın Hatalar ve Çözümleri

Hata 1: ENV değeri boş geliyor

docker exec api printenv MY_VAR
# (boş)

# Neden: -e flag'i yanlış yazılmış olabilir
# ❌ docker run -e MY_VAR myapp  (host'ta MY_VAR tanımlı değilse boş gelir)
# ✅ docker run -e MY_VAR=hello myapp

Hata 2: .env dosyasındaki tırnaklar sorun çıkarıyor

# .env dosyasında:
# ❌ MY_VAR="hello world"
# Container'da: MY_VAR="hello world" (tırnaklar dahil!)

# ✅ MY_VAR=hello world
# Container'da: MY_VAR=hello world ✓

Hata 3: docker inspect ile secret görünüyor

docker inspect api --format '{{json .Config.Env}}' | jq .
# ["DATABASE_URL=postgres://user:REAL_PASSWORD@db:5432/myapp"]

# Çözüm: Docker secrets veya vault entegrasyonu kullan

Environment Variables İnceleme Araçları

# Container'ın tüm ENV değişkenlerini gör
docker exec api env
docker exec api printenv
docker exec api printenv DATABASE_URL

# docker inspect ile
docker inspect api --format '{{json .Config.Env}}' | jq .

# Belirli bir değişkeni kontrol et
docker inspect api --format '{{range .Config.Env}}{{println .}}{{end}}' | grep DATABASE

# Compose ile tanımlanmış ENV'leri gör
docker compose config | grep -A 20 "environment:"

# ENV debug script'i
docker exec api sh -c '
echo "=== Environment Variables ==="
echo "NODE_ENV: $NODE_ENV"
echo "PORT: $PORT"
echo "LOG_LEVEL: $LOG_LEVEL"
echo "DB connected: $(test -n "$DATABASE_URL" && echo "yes" || echo "NO!")"
echo "Redis connected: $(test -n "$REDIS_URL" && echo "yes" || echo "NO!")"
'

Özet

  • Environment variables, container konfigürasyonunun temel taşıdır — aynı image, farklı ENV = farklı ortam

  • `-e` flag'i ile tekil, `--env-file` ile dosyadan toplu değişken geçirilir

  • ENV Dockerfile'da varsayılan değerler tanımlar — docker run -e ile override edilebilir

  • ARG sadece build sırasında, ENV hem build hem runtime'da geçerlidir

  • Gizli bilgileri image'a gömme (ENV, COPY .env) — runtime'da geçir veya Docker secrets kullan

  • .env.example dosyası oluştur, .env'i gitignore'a ekle, zorunlu değişken kontrolü yap