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_VARDocker'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 myappHost 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| ENV | ARG | |
|---|---|---|
| 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:16MySQL
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:8Redis
docker run -d \
-e REDIS_PASSWORD=redissecret \
--name redis redis:7 \
redis-server --requirepass redissecret --maxmemory 256mb --maxmemory-policy allkeys-lruNginx
# 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.25Konfigü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ğiStrateji 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ımStrateji 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 girVarsayı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 edilebilirEntrypoint 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.3Gerç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'daBest 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
ENVile 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 myappHata 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 kullanEnvironment 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 -eile override edilebilirARG 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
AI Asistan
Sorularını yanıtlamaya hazır