What is Docker Image?
Docker Image, bir uygulamayı çalıştırmak için gereken her şeyin paketlenmiş halidir. İçinde şunlar vardır:
- İşletim sistemi katmanı (ör: Alpine, Ubuntu)
- Uygulama dosyaları
- Gerekli kütüphaneler
- Varsayılan ayarlar
Docker Pull | Push
Docker imajlar üzerinde işlem yaparken bazı bilgileri bilmemiz gerekmektedir. Öncelikle Docker Hub, Docker tarafından sağlanan varsayılan container image deposudur (registry).
Yani bir imaj indirdiğiniz zaman varsayılan olarak buradan indirilir. Docker hub image’larının, saklandığı, paylaşıldığı ve indirildiği merkezi platformdur.
Docker hub üzerinden imaj indirmeden önce tag mantığını bilmemiz gerekir. Docker imaj üzerindeki tag, image’in versiyonunu belirtir. Yani, tag kullanmak ben neyi çalıştırdığımı biliyorum demektir. Tag kullanılmaz ise Docker image varsayılan olarak latest etiketiyle çekilir ve bu da hangi sürümün çalıştığının belirsiz olmasına, güncellemeler sonrası beklenmeyen hatalara ve canlı sistemlerde kontrol kaybına neden olur.
latest en güncel demek değildir, sadece “etiketi belirtilmemiş” demektir.
Örneğin, aşağıdaki gibi komut ile bir container ayağa kaldırırsanız,
docker run ubuntu
Aslında arka planda latest tag eklenerek çalışır.
docker image pull ubuntu:latest
Aşağıda resimdeki çıktıda görünen her satırı, tek tek maddeler halinde ve sade anlamlarıyla inceleyelim.
- docker image pull ubuntu→ Ubuntu image’ı çekme komutu çalıştırıldı (tag belirtilmedi)
- Using default tag: latest→ Tag yazılmadığı için Docker otomatik olarak latest etiketini kullandı
- latest: Pulling from library/ubuntu → Resmi Docker Hub deposundaki (library/ubuntu) latest image’ı çekiliyor
- a3629ac5b9f4: Pull complete→ Image’a ait bir katman (layer) başarıyla indirildi
- Digest: sha256:7a398144c5a2fa7dbd9362e460779dc6659bd9b19df50f724250c62ca7812eb3 → İndirilen image’ın değişmez, benzersiz kimliği (hash)
- Status: Downloaded newer image for ubuntu:latest → Sistemdeki ubuntu:latest image’ı daha yeni bir sürümle güncellendi.
- docker.io/library/ubuntu:latest→ İndirilen image’ın tam depo yolu ve etiketi. (Varsayılan depo docker.io istenirse başka yerde kullanılabilir)

Mevcut yapınızdaki tüm image'ları ve tag'ları görmek isterseniz aşağıdaki komutu çalıştırmanız yeterli olacaktır.
docker image ls

Sonuç olarak yazılımlarınızı her eklediğiniz değişiklikte imaj isimlerini değiştirmek yerine tag'lar kullanıyoruz. Tag mantığını öğrendikten sonra artık imaj indirebiliriz. İmaj indirmek için docker hub sitesine giriş yapıyoruz indirmek istediğimiz imajı yazıyoruz. Burada Tag summary kısmından mevcut versiyonları görebilirsiniz. Seçtiğiniz versiyona göre docker pull komutu güncellenecektir. Bu komutu kopyalayın.

Docker yüklü sistemde komutu çalıştırdığımız zaman imaj indirme işlemi başlar.
docker pull postgres:18.1-alpine3.23
Aşağıdaki çıktıda docker kurulduğunda hiçbir ayar yapmadan imaj indirme işlemi başlatıldığında registry adı yazılmadığı için docker otomatik olarak Docker Hub’a gider ve aslında docker.io/library/image_name işlemi gerçekleşir.

Aynı şekilde indirme yapıldığı gibi sizde imajlarınızı docker hub'a gönderebilirsiniz. Ancak bunun için Bir Docker Registry Hesabınız olması gerekmektedir. Hesap açtıktan sonra docker yüklü olan ortamda docker hub'ta oturum açmanız gerekmektedir. Bunun için docker login komutunu kullanmanız yeterlidir. Succes yazısını görürseniz işlem başarılı olmuştur.
docker login -u account

Hesaptan çıkmak isterseniz aşağıdaki komutu kullanmanız yeterlidir.
docker logout
İmajı docker hub üzerine göndermek için aslında akış aşağıdaki gibi olmalıdır.
- docker login
- docker build -t myapp .
- docker tag myapp whoami/myapp:1.0
- docker push whoami/myapp:1.0
Ancak biz daha kendimiz imaj oluşturma işlemini görmediğimiz için hazır imajı docker hub'a nasıl göndereceğimize bakacağız. Zaten mantık aynı. Docker Hub'a imaj göndermek için kendi namespace’in altında olmalıdır. Namespace ismini hesap adınız ile aynıdır.

Kendi imajlarınız etiketlenmeden docker hub'a push edilemez. O yüzden indirdiğimiz veya hazırladığımız imajı ilk önce namespace adımızı kullanarak etiketleyelim.
docker tag postgres:18.1-alpine3.23 mehmetzo/postgres:1.0
Etiketleme işleminden sonra yine namespace adımızı kullanarak push edelim.
docker push mehmetzo/postgres:1.0

Yükleme tamamlandıktan sonra ister indirebilir, istersenizde docker hub üzerinden görüntüleyebilirsiniz.

Create Docker Image
Docker imajı oluşturmanın iki temel yolu vardır. Birincisi, çalışan container’dan image oluşturmaktır. Bu yöntem önerilmez, çünkü, tekrar üretilemez, versiyon kontrolü yok, otomasyon zor ve nasıl oluştuğu bilinmez. Bu yüzden genelde test veya geçici çözümler için kullanılır.
Profesyonel ve doğru yöntem Dockerfile kullanmaktır. Bu sayede, tekrar üretilebilir, otomasyona uygun, CI/CD ile uyumlu ve versiyonlanabilir.
- Dockerfile, Docker imajının nasıl oluşturulacağını adım adım tanımlayan metin dosyasıdır. Her satır bir layer'dan oluşur, uzantısı yoktur ve dosya adı Dockerfile olmalıdır. Kısaca Dockerfile = Image’in tarifidir.
- Docker image oluşturulurken. Bir layer oluşturur ve cache mekanizması kullanılır. Değişmeyen layer’lar tekrar build edilmez. Bu yüzden, değişmeyen satırlar üste, sık değişenler alta yazılır.
- Docker image oluşturduktan sonra bunları, Local Docker host, Docker Hub ve Private Registry (Harbor, GitLab Registry, ECR) gibi ortamlarda saklayabilirsiniz.
- FROM → komutu en önemli ve zorunlu olarak ilk satırda olmalıdır. Burada tabanda kullanılacak imaj belirtilir. Genelde küçük boyutlu olması için Alpine tercih edilir. (Örnek, FROM alpine:3.23.2)
- RUN → komutu Image oluşturulurken çalışır ve paket kurulumları için kullanılır. Her bir run komutu yeni bir layer oluşturur. (Örnek, RUN apt update && apt install -y nginx)
- COPY ve ADD → komutu Host’tan container’a dosya kopyanmasını sağlar. Genelde COPY tercih edilir. ADD ise buna ek olarak URL’den indirme ve otomatik arşiv (tar) açma özelliğine sahiptir. (Örnek, COPY app/ /app/)
- WORKDIR → komutu çalışma dizinini belirler. (Örnek, WORKDIR /app)
- ENV → komutu ortam değişkeni tanımlar ve container içinde kalıcıdır. (Örnek,ENV APP_ENV=production)
- EXPOSE → komutu container kullanacağı portu belirtir. Ancak bu Bilgilendirme amaçlıdır yani portu açmaz. (Örnek,EXPOSE 8080)
- LABEL → komutu ise uygulamayı tanımlar (versiyon, ekip, ortam). Container’ları filtrelemeyi ve yönetmeyi kolaylaştırır ve otomasyon, monitoring ve dokümantasyon için kullanılır. Performansı etkilemez ve uygulamayı çalıştırmaz. (Örnek, LABEL maintainer="devops@firma.com" app="web-api" version="1.2.0" environment="production")
- CMD → komutu Container başlarken çalışır ve runtime yani çalıştırılan process sonradan değiştirilebilir. (Örnek,CMD ["node", "app.js"]) (Varsayılan olarak python app.py çalışır, ama docker run image test.py dersen sadece CMD değişir.)
- ENTRYPOINT → komutu ise ise container’ın ana çalıştırılacak komutunu sabitler ve değiştirilemez hale getirir. Genelde beraber kullanılır ve doğru yöntem ENTRYPOINT ana komut, CMD varsayılan parametre olur. (Örnek, ENTRYPOINT ["python", "app.py"]) (Örnek beraber kullanım, ENTRYPOINT ["python"] CMD ["app.py"] )
Docker üzerinde bir uygulama çalıştırırken iki yöntem kullanılabilir. Bunlardan ilki Exec form doğrudan prosesi (PID 1) başlatır ve sinyalleri doğru alır. Diğeri ise Shell form komutu bir kabuk (/bin/sh -c) üzerinden çalıştırdığı için sinyal ve process yönetimi sorunları doğurabilir.
- Shell form örneği: CMD python app.py örneği için docker bunu aslında /bin/sh -c "python app.py" olarak çalıştırır. Sonuç olarak, PID 1 → sh python child process olur veSIGTERM doğru iletilmeyebilir. Bu sebepten container geç kapanabilir
- Exec form örneği (önerilen): CMD ["python", "app.py"] örneği için docker bunu PID 1 → python olarak çalıştırır. Sinyaller direkt uygulamaya gider. Graceful shutdown düzgün çalışır.
Sonuç olarak, Uygulama çalıştırıyorsan EXEC, kabuk mantığı gerekiyorsa SHELL.
Dockerfile ile Image build ederken ilk olarak Dockerfile dosyası okunur. Akabinde, FROM ile base image çekilir, her komut sırasıyla çalışır, her adım layer olarak kaydedilir ve sonunda image oluşur.
İmajları oluştururken, yazılımına uygun (Python, PHP, Node.js vb.) resmi Docker imajlarını kullanmak; daha güvenli, daha küçük, daha hızlı ve bakımı kolay olduğu için en doğru yaklaşımdır. Yani bir ubuntu imajını alıp içine pyhton kurmaya çalışmayın.
Docker, imajı build sırasında Dockerfile’ı satır satır yukarıdan aşağıya okur. Her satır için şunu sorar, bu satırı ve önceki tüm satırları daha önce birebir aynı şekilde build etmiş miydim? Cevap Evet ise Cache kullanılır, Hayır ise O satır ve altındaki HER ŞEY yeniden çalışır. Bu kısımda da eğer RUN ile bir yükleme veya güncelleme yapıyorsanız. Bu RUN komutları, konfigürasyon veya kopyaladığınız dosyanın üstünde olması gerekir. Aksi halde her değişiklik yaptığınız yükleme ve güncellemeler hep en baştan yapılacaktır.
Bu özet bilgilerden sonra basit bir Dockerfile yapalım. Ben proje1 adında klasör oluşturdum ve her şeyi burada yapacağım. Öncelikle bir tane uygulamamız olması gerekiyor ki bunu image haline getirelim. Uygulamayı kendiniz yazabilir, github üzerinden bulabilir veya yapay zeka araçlarına yazdırabilirsiniz.
Ben pyhton kullanarak tarayıcı üzerinde çalışan dijital bir saat yaptım. Projenin son hali aşağıdaki gibidir.

Dockerfile içeriği aşağıdaki gibidir. Yukarıda hangi komutların ne işe yaradığını anlattığımız için bu kısımda tekrar bahsetmeyeceğim.
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]
Artık tek yapmamız gereken proje1 klasörü içinde docker image oluşturmak için build komutunu kullanmak ve -t ile bir etiketleme yapmak.
docker build -t digital-clock:1.0 .

Artık tek yapmamız gereken container çalıştırmak.
docker run --rm -p 5000:5000 digital-clock:1.0
Container çalıştı ancak, proje düzgün çalışmadı.

Sorunlar gidererek tekrar imajı build yapıyorum ve işlem bu sefer çok kısa bir şekilde bitiyor. Çünkü cache kullanıyor.

Tekrar yeni bir container çalıştırdığım zaman, projenin başarılı bir şekilde container haline geldiğini görebiliyoruz.
docker run --rm -p 5000:5000 digital-clock:1.0

Her build sonra yeni bir container oluşturmak gerekir. Çünkü her container image bağlı olsada çalıştıktan sonra kendine özeldir.
Container HealtCheck
Docker healthcheck, bir container’ın gerçekten sağlıklı çalışıp çalışmadığını kontrol etmek için Docker’a tanımlanan bir mekanizmadır. Container çalışıyor olsada, uygulama bozuk / kilitlenmiş / cevap vermiyor olabilir. İşte healthcheck bu farkı anlamak için vardır.
Normal şartlarda Docker varsayılan olarak sadece process’e bakar. Yani PID 1 çalışıyormu kontrol eder. Ama şunları kontrol etmez:
Web servisi cevap veriyor mu? | Veritabanına bağlanabiliyor mu? | API endpoint çalışıyor mu? | Uygulama kilitlenmiş mi? | Arka planda hata verip “zombi” halde mi?
Sonuç olarak, Healthcheck varsa Docker, process değil senin verdiğin testi kontrol eder.
Aşağıya Nginx için gerçekçi bir Docker healthcheck örneği ve tam Dockerfile yazalım.
FROM nginx:alpine
# Basit bir index.html ekleyelim (test için)
RUN echo "<h1>Nginx is running</h1>" > /usr/share/nginx/html/index.html
# Healthcheck tanımı
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
CMD curl -f http://localhost || exit 1
# Nginx'i foreground modda çalıştır
CMD ["nginx", "-g", "daemon off;"]
- interval=30s → 30 saniyede bir kontrol
- timeout=5s → 5 saniye içinde cevap bekle
- retries=3 → 3 kez başarısız olursa unhealthy
- start-period=10s → başlangıçta grace süresi
- curl -f → HTTP 200 değilse hata ver
Yapay zeka ile, Database, Python, Node, PHP için healthcheck yazdırabilirsiniz.
Şimdi bu Dockerfile kullanarak bu imajı oluşturalım.
docker build -t nginx-health .

nginx-health imajdan container oluşturalım.
docker run -d -p 8080:80 --name nginx-test nginx-health

Healthcheck durumunu görmek için aşağıdaki komutları kullanabilirsiniz.
docker ps
docker inspect nginx-test --format='{{.State.Health.Status}}'

Multi-Stage Build
Docker'da multi-stage build (çok aşamalı derleme), imaj boyutlarını küçültmek ve güvenliği artırmak için kullanılan en etkili yöntemlerden biridir. Temel mantık şudur: Uygulamayı derlemek için gereken tüm ağır araçları (compiler, SDK vb.) bir aşamada kullanırız, ancak son imaja sadece çalışacak olan uygulamayı (binary veya artifact) kopyalarız.
- Image boyutunu ciddi şekilde küçültmek (GB → MB)
- Daha güvenli image üretmek (compiler, secret, build tool yok)
- Daha hızlı deploy & pull süreleri
- Temiz ve standart Dockerfile (build + run ayrımı net)
- CI/CD’de tekrar üretilebilir ve sade image’lar
- Node.js → node_modules + build araçları sadece build aşamasında
- Java → Maven/Gradle sadece build’te, final image JRE
- Go → Derleme build’te, final image tek binary
- Frontend → npm build sonrası sadece dist/ klasörü
Kısaca multi-stage build, derlemek için her şey var ama çalıştırmak için sadece gereken kalsın mantığıdır.
Aşağıda, Pyhton programlama dili ile yazılmış olan Flask web uygulamasını örnek alarak klasik bir senaryo hazırladım. Proje yapısı için bir klasör oluşturun ve içine aşağıdaki dosyaları oluşturun.
app.py dosyasına aşağıdaki içeriği kopyalayın.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Multi-Stage Build Olmadan Çalışıyor!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
requirements.txt dosyasına aşağıdaki içeriği kopyalayın.
flask
Flask örneğini ilk olarak Dockerfile dosyamıza multi-stage olmadan yazalım. Bu sürümde bağımlılıklar ve her şey aynı imajın içinde kalır ve genelde daha şişkin olur.
FROM python:3.12-slim
WORKDIR /app
# (opsiyonel) güvenlik: root olmayan kullanıcı
RUN useradd -m appuser
COPY requirements.txt .
# Tek stage olduğu için paketler doğrudan imaja kurulur
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
USER appuser
EXPOSE 5000
CMD ["python", "app.py"]
Günün sonunda aşağıdaki dosyalara sahip olmanız gerekmektedir.

İmajı build işlemi gerçekleştirin ve isteğe bağlı olarak çalıştırın.
docker build -t flask-single .
docker run -p 5000:5000 flask-single
İmage boyutuna baktığımız zaman 132MB olduğunu görüyoruz.
docker images | grep flask

Şimdi Multi-Stage Build, tek Dockerfile içinde birden fazla imaj aşaması tanımlayıp sadece gerekli çıktıları son imaja taşıyarak daha küçük, güvenli ve hızlı imaj yapma çalışalım. Bunun için app.py dosyasına aşağıdaki içeriği kopyalayın.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Multi-Stage Build Çalışıyor!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Dockerfile dosyasını açıp aşağıdaki komutlar ile değiştirilem.
################################
#BUILD STAGE (Derleme Aşaması)
################################
FROM python:3.12-slim AS builder
# Çalışma dizini
WORKDIR /app
# Gereksinimleri kopyala
COPY requirements.txt .
# Bağımlılıkları yükle
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
################################
#RUNTIME STAGE (Çalışma Aşaması)
################################
FROM python:3.12-alpine
# Güvenlik: root olmayan kullanıcı
RUN adduser -D appuser
WORKDIR /app
# Sadece gerekli dosyaları builder’dan al
COPY --from=builder /install /usr/local
COPY app.py .
USER appuser
EXPOSE 5000
CMD ["python", "app.py"]
İmajı build işlemi gerçekleştirin ve isteğe bağlı olarak çalıştırın.
docker build -t flask-multistage .
docker run -p 5000:5000 flask-multistage
İmage boyutuna baktığımız zaman 53.6MB olduğunu görüyoruz.
docker images | grep flask

Sonuç olarak aşağıdakileri başarmış olduk.
- Build ve runtime ayrıldı
- Gereksiz bağımlılıklar final imaja girmedi
- İmaj boyutu küçüldü
- Prod’a uygun, temiz Dockerfile oluştu
Build ARG
Docker Build ARG (Build Arguments), Docker imajını derlerken (build time) dışarıdan veri göndermenizi sağlayan değişkenlerdir. Yani, Dockerfile dosyanızın içine "sabit" bir değer yazmak yerine, bu değeri docker build komutunu çalıştırırken terminalden vermenizi sağlar. Bu sayede aynı Dockerfile'ı farklı versiyonlar veya ayarlar için tekrar tekrar değiştirmeden kullanabilirsiniz.
ARG komutu Dockerfile içinde tanımlanır. Kullanıcı build alırken bu değişkene bir değer atar. Aşağıdaki örnekte Python sürümünü ARG ile VERSION adında bir değişken belirledik. Akabinde bu değişkeni ADD komutunun içine değişecek yerlere ${VERSION} olarak yazdık.
FROM ubuntu:latest
WORKDIR /app
ARG VERSION
ADD https://www.python.org/ftp/python/${VERSION}/Python-${VERSION}.tar.bz2 .
CMD ["ls", "-al"]
Build işlemi yaparken --build-arg VERSION=2.5 yazarak VERSION yazan yerler 2.5 olarak güncellenerek imaj oluşturulacaktır.
docker build -t app2.5 --build-arg VERSION=2.5 .
Aşağıdaki görseldeki gibi değişken olan yerlerin 2.5 olarak değiştiğini görebilirsiniz.

Dockerfile içeriğini değiştirmeden bu sefer --build-arg VERSION=3.0 yazıyoruz.
docker build -t app3.0 --build-arg VERSION=3.0 .
Aşağıdaki görseldeki gibi değişken olan yerlerin 3.0 olarak değiştiğini görebilirsiniz.

Son olarak, ARG ile ENV çok karıştırılır. ARG ile ENV en kritik farklar aşağıdaki gibidir.
| Özellik | ARG | ENV |
|---|---|---|
| Kullanım zamanı | Build-time | Run-time |
| docker build ile set edilir | ✅ | ❌ |
| docker run ile set edilir | ❌ | ✅ |
| Container içinde erişim | ❌ | ✅ |
| Image metadata’da görünür | ⚠️ Evet (history) | ✅ |
docker commit
Docker commit çalışan veya durmuş bir container’ın o anki dosya sisteminin snapshot’ını alıp yeni bir Docker image oluşturma işlemidir. Kısaca Containerı Image dönüştürme işlemidir.
Container içinde elle değişiklik yaptın veya apt install, config dosyası düzenledikten sonra bu hali kaybolmasın diyorsan kullabilirsin.
Şimdi en basit hali ile bir örnek yapalım. Bunun için bir ubuntu ayağa kaldıralım.
docker run -it --name commit-test ubuntu bash
Container içinde güncelleme yapalım ve curl aracını yükleyelim.
apt update -yapt i
apt install -y curl
exit

Şimdi bu containerin son halini image haline getirelim.
docker commit <containerid> ubuntu_curl
docker images

Aşağıdaki örneği baz alarak, isteğe bağlı CMD veya ENTRPOINT ekleyebilirsiniz.
docker commit \
--change='CMD ["python","app.py"]' \
container_id myimage:v2
Docker commit canlı ortamlarda önerilmez. Aşağıda docker build ve docker commit karşılaştırmasını inceleyebilirsiniz.
| docker build | docker commit | |
|---|---|---|
| Kaynak | Dockerfile | Çalışan container |
| Tekrar üretilebilir | ✅ | ❌ |
| CI/CD uyumlu | ✅ | ❌ |
| Debug için uygun | ⚠️ | ✅ |
| Prod için uygun | ✅ | ❌ |
docker save-load
Yüksek seviyeli güvenlik ortamlarda sistemler internete kapalıdır. İnternet yoksa, registry yoksa veya air-gapped ortamdaysan docker save/docker load hayat kurtarır. Bu sayede, Docker image’ları dosya olarak dışarı çıkarıp başka bir sisteme taşımak için kullanabilirsiniz. Image dosyasını kaydederken içinde Image layer’ları, Manifest, Tag bilgileri, Metadata gibi bilgiler yer alırken Container’lar, Volume’lar, Network’ler yer almaz.
En basit haliyle bir Image’ı dosyaya çıkarmak içinde aşağıdaki komutu kullanabilirsiniz.
docker save nginx:latest -o nginx.tar
Aldığınız dosyayı başka bir makineye yüklemek için aşağıdaki komutu kullanabilirsiniz.
docker load -i nginx.tar
Aynı anda birden fazla imaj kayıt etmek için aşağıdaki komutu kullanabilirsiniz.
docker save \
nginx:latest \
redis:7 \
-o images.tar
Aynı şekilde yüklemek için aşağıdaki komutu kullanabilirsiniz.
docker load -i images.tar
Yine karıştırılmaması için ️docker save ve docker export komut farklarını karşılaştırabilirsiniz.
| docker save | docker export | |
|---|---|---|
| Nesne | Image | Container |
| Layer bilgisi | ✅ | ❌ |
| History korunur | ✅ | ❌ |
| Tag korunur | ✅ | ❌ |
| Prod kullanımı | ✅ | ❌ |