← Kursa Dön
📄 Text · 30 min

CMD vs ENTRYPOINT — Ne Zaman Hangisi?

Önceki derslerde CMD ve ENTRYPOINT'i kısaca gördük. Ama bu iki talimat Docker'ın en çok karıştırılan konularından biri. İkisi de "container başladığında ne çalışsın?" sorusuna cevap veriyor gibi görünüyor. Peki neden ikisi de var? Farkları ne? Ne zaman hangisini kullanmalı?

Bir restoran düşün. ENTRYPOINT restoranın konsepti — İtalyan mutfağı. Değiştiremezsin (kolay kolay). CMD ise günün menüsü — her gün farklı olabilir. Restoran her zaman İtalyan yemeği servisi yapar (ENTRYPOINT), ama hangi yemek olduğu değişebilir (CMD).

Bu derste CMD ve ENTRYPOINT'i derinlemesine öğrenecek, tüm kombinasyonlarını görecek ve gerçek dünya senaryolarını inceleyeceğiz.


Shell Form vs Exec Form

CMD ve ENTRYPOINT'i konuşmadan önce, Docker'da komut yazmanın iki yolu olduğunu bilmelisin:

Exec Form (JSON Array) — ✅ Önerilen

CMD ["node", "server.js"]
ENTRYPOINT ["python", "app.py"]

Bu komut doğrudan çalıştırılır. Shell'den geçmez. PID 1 olarak belirlediğin process çalışır. SIGTERM sinyali doğrudan bu process'e gider. Graceful shutdown çalışır.

Shell Form — ❌ Genellikle Kaçın

CMD node server.js
ENTRYPOINT python app.py

Bu komut /bin/sh -c "node server.js" olarak çalıştırılır. PID 1 sh olur, node onun child process'i olur. SIGTERM sinyali sh'a gider ama sh bunu child'a iletmez. Dolayısıyla docker stop verdiğinde Node.js process'in gracefully shutdown olmaz — 10 saniye sonra SIGKILL ile öldürülür.

Bu farkı somut olarak görelim:

# Exec form ile
docker run --rm --name test -d node:20-alpine node -e "
    process.on('SIGTERM', () => { console.log('Graceful shutdown!'); process.exit(0); });
    setInterval(() => {}, 1000);
"
docker stop test
docker logs test
# Graceful shutdown!  ← Sinyal alındı ✅

# Shell form ile
docker run --rm --name test2 -d node:20-alpine sh -c "
    node -e \"process.on('SIGTERM', () => { console.log('Graceful shutdown!'); process.exit(0); }); setInterval(() => {}, 1000);\"
"
docker stop test2  # 10 saniye bekleyecek...
docker logs test2
# (hiçbir şey) ← Sinyal ulaşmadı ❌

Kural: Her zaman exec form kullan. Shell form sadece shell özelliklerine ihtiyaç duyduğunda (pipe, variable expansion gibi) kullan.


CMD — Varsayılan Komut

CMD, container başladığında çalışacak varsayılan komutu belirler. Anahtar kelime "varsayılan" — kullanıcı isterse override edebilir.

FROM node:20-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
# CMD çalışır
docker run myapp
# → node server.js

# CMD override edilir
docker run myapp node test.js
# → node test.js

# Tamamen farklı komut
docker run myapp sh
# → sh (shell açılır)

CMD Kuralları

Dockerfile'da sadece bir CMD olabilir. Birden fazla yazarsan sadece sonuncusu geçerli:

CMD ["echo", "birinci"]
CMD ["echo", "ikinci"]
# Sadece "ikinci" çalışır

ENTRYPOINT — Sabit Giriş Noktası

ENTRYPOINT, container'ın her zaman çalıştıracağı komutu belirler. Override edilmez (özel flag olmadan).

FROM python:3.12-slim
WORKDIR /app
COPY . .
ENTRYPOINT ["python"]
CMD ["app.py"]
# ENTRYPOINT + CMD = python app.py
docker run myapp
# → python app.py

# CMD override, ENTRYPOINT sabit kalır
docker run myapp test.py
# → python test.py

# CMD override
docker run myapp --version
# → python --version

Gördüğün gibi ENTRYPOINT (python) her zaman orada. docker run myapp test.py dediğinde CMD (app.py) yerine test.py geçer ama ENTRYPOINT değişmez.

ENTRYPOINT'i override etmek istersen --entrypoint flag'i gerekir:

docker run --entrypoint sh myapp
# → sh (python yerine shell açılır)

CMD + ENTRYPOINT Kombinasyonları

Bu iki talimat birlikte kullanıldığında güçlü pattern'lar oluşturur. Kuralları bir tablo ile görelim:

ENTRYPOINT yok + CMD yok        → Hata (bir şey çalışmalı)
ENTRYPOINT yok + CMD var        → CMD çalışır
ENTRYPOINT var + CMD yok        → Sadece ENTRYPOINT çalışır
ENTRYPOINT var + CMD var        → ENTRYPOINT CMD'yi argüman olarak alır

Örneklerle görelim:

# Pattern 1: Sadece CMD (en yaygın — servisler için)
CMD ["node", "server.js"]
# docker run myapp        → node server.js
# docker run myapp bash   → bash

# Pattern 2: Sadece ENTRYPOINT (sabit davranış)
ENTRYPOINT ["python", "app.py"]
# docker run myapp             → python app.py
# docker run myapp --verbose   → python app.py --verbose

# Pattern 3: ENTRYPOINT + CMD (en esnek — araçlar için)
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run myapp             → python app.py
# docker run myapp test.py     → python test.py
# docker run myapp --version   → python --version

Gerçek Dünya Pattern'ları

Pattern 1: Web Servisi — Sadece CMD

Bir web sunucusu, API servisi veya microservice çalıştırıyorsan:

FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
USER node
CMD ["node", "server.js"]

CMD kullanıyoruz çünkü debug için shell'e geçmek isteyebiliriz:

docker run myapp                    # Normal çalışma
docker run myapp sh                 # Debug — shell aç
docker run myapp node repl          # REPL aç

Pattern 2: CLI Aracı — ENTRYPOINT + CMD

Bir komut satırı aracı containerize ediyorsan:

FROM python:3.12-slim
RUN pip install --no-cache-dir awscli
ENTRYPOINT ["aws"]
CMD ["help"]
docker run aws-cli                          # aws help
docker run aws-cli s3 ls                    # aws s3 ls
docker run aws-cli ec2 describe-instances   # aws ec2 describe-instances

Bu pattern ile container'ın kendisi bir CLI aracı gibi davranır.

Başka örnekler:

# curl aracı
FROM alpine:3.19
RUN apk add --no-cache curl
ENTRYPOINT ["curl"]
CMD ["--help"]

# jq aracı
FROM alpine:3.19
RUN apk add --no-cache jq
ENTRYPOINT ["jq"]
CMD ["--help"]
docker run mycurl https://api.example.com/health
docker run myjq '.name' <<< '{"name":"test"}'

Pattern 3: Wrapper Script — Başlatma Öncesi Hazırlık

Bazen container başlamadan önce hazırlık yapman gerekir — veritabanı migration, config dosyası oluşturma, sağlık kontrolü gibi. Bu durumda bir wrapper script kullanırsın:

#!/bin/sh
# docker-entrypoint.sh

echo "Starting up..."

# Veritabanı bağlantısını bekle
echo "Waiting for database..."
until pg_isready -h $DB_HOST -p 5432 -q; do
    echo "Database not ready, waiting..."
    sleep 2
done
echo "Database is ready!"

# Migration çalıştır
echo "Running migrations..."
python manage.py migrate

# Ana komutu çalıştır
echo "Starting application..."
exec "$@"
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:application"]

`exec "$@"` çok önemli! Bu satır CMD'deki komutu mevcut shell'in yerine geçirir (PID 1 olarak). exec olmadan shell PID 1 olur ve sinyal sorunu yaşarsın.

docker run myapp                    # Migration + gunicorn
docker run myapp python manage.py shell   # Migration + Django shell
docker run myapp bash               # Migration + bash

Her durumda önce migration çalışır, sonra belirtilen komut.


docker-compose.yml'da Override

services:
  web:
    image: myapp:v1
    command: ["node", "server.js", "--port", "8080"]  # CMD override
    # veya
    entrypoint: ["node"]  # ENTRYPOINT override
    command: ["debug.js"]  # CMD override

Karar Rehberi

Hangisini kullanacağını seçerken şöyle düşün:

Sadece CMD kullan — web servisler, API'ler, microservice'ler. Container'ın tek bir iş yapmasını ama debug için override edilebilmesini istersen.

ENTRYPOINT + CMD kullan — CLI araçları, wrapper script pattern. Container'ın bir aracı sarması ama argümanların değişebilmesini istersen.

Sadece ENTRYPOINT kullan — Argüman almayan, sabit davranışlı container'lar. Nadiren kullanılır.

Emin değilsen CMD ile başla. Çoğu durumda yeterli.


Yaygın Hatalar

Shell Form Kullanımı

# ❌ Shell form — sinyal sorunu
CMD npm start
ENTRYPOINT python app.py

# ✅ Exec form
CMD ["npm", "start"]
ENTRYPOINT ["python", "app.py"]

exec Unutmak (Wrapper Script)

# ❌ exec yok — sh PID 1 kalır
#!/bin/sh
python manage.py migrate
gunicorn app:application  # sh'ın child'ı, PID 1 değil

# ✅ exec var — gunicorn PID 1 olur
#!/bin/sh
python manage.py migrate
exec gunicorn app:application  # PID 1 olarak çalışır

ENTRYPOINT Override Unutmak

# Container'a bash ile girmek istiyorsun
docker run -it myapp bash
# Ama container'da ENTRYPOINT ["python"] var
# Çalışan: python bash → hata!

# Doğru yol:
docker run -it --entrypoint bash myapp

Bu Derste Ne Öğrendik?

  • CMD varsayılan komut — docker run ile override edilebilir. Servisler için ideal.

  • ENTRYPOINT sabit giriş noktası — override için --entrypoint gerekir. CLI araçları için ideal.

  • ENTRYPOINT + CMD kombinasyonu: ENTRYPOINT sabit komut, CMD varsayılan argümanlar. En esnek pattern.

  • Exec form (["cmd", "arg"]) kullan, shell form (cmd arg) kullanma. Sinyal yönetimi kritik.

  • Wrapper script'lerde `exec "$@"` unutma — PID 1 doğru process olmalı.

  • Emin değilsen CMD ile başla. İhtiyaç oldukça ENTRYPOINT ekle.

Bölüm 3 tamamlandı! Dockerfile yazmayı öğrendin. Bir sonraki bölümde container'ların yaşam döngüsünü, ağ bağlantılarını ve kaynak yönetimini öğreneceğiz.