Docker Container Nasıl Çalışır? Image, Container ve Volume Mantığı
Image katmanları, çalışan container, izolasyon ve kalıcı veriyi birbirinden ayıralım; Node.js örneğiyle build ve işletim kararlarını inceleyelim.
Docker kullanan bir projede en sık karışan üç kavram image, container ve volume'dür. Image uygulamanın paketlenmiş başlangıç içeriğini, container bu içeriğe dayanan çalışan ortamı, volume ise belirli verilerin container yaşam döngüsünden bağımsız tutulmasını sağlar.
Bu ayrım yalnızca terminoloji değildir. “Container'ı yeniden oluşturdum, yüklenen dosyalar kayboldu” veya “aynı image bende çalışıyor ama sunucuda çalışmıyor” gibi sorunları anlamanın başlangıcıdır.
Image: yeniden kullanılabilir paket
Image; uygulama kodu, runtime ve gerekli dosya sistemi içeriğini katmanlar halinde taşır. Dockerfile bu paketin nasıl oluşturulacağını tarif eder. Aynı image'dan birden fazla container başlatılabilir.
Katmanlı yapı build önbelleğine yardımcı olur. Kaynak koddan daha seyrek değişen bağımlılık tanımlarını önce kopyalamak, yalnızca kod değiştiğinde bağımlılık kurulumunun tekrar edilmesini önleyebilir. Image'ın katman ve metadata yapısı Docker image açıklamasında anlatılır.
Tag, örneğin task-api:1.0, bir isimdir ve başka içeriğe yöneltilebilir. Digest ise içeriği tanımlar. Tekrarlanabilir yayın için yalnızca “latest” adına güvenmek yerine hangi digest'in test edildiğini kaydetmek gerekir.
Container: image'dan başlatılan izole süreçler
Container bir sanal makineyle aynı mekanizma değildir. Linux container'ları Linux çekirdeğinin namespace ve cgroup gibi özelliklerinden yararlanır. Namespace'ler görünürlüğü, cgroup'lar kaynak yönetimini düzenlemeye yardımcı olur. Windows veya macOS üzerinde Linux container'ları çalıştırırken arada Linux sanal makinesi bulunabilir.
Container kendi işletim sistemi çekirdeğini image içinde başlatmak zorunda olmadığı için farklı bir maliyet ve izolasyon modeli sunar. Bununla birlikte “container içinde çalışıyor” ifadesi tek başına güçlü bir güvenlik sınırı garantisi değildir. Yetkiler, mount'lar ve host erişimi sonucu belirler. Temel model Docker container belgesinde açıklanır.
Bir Node.js image'ı oluşturalım
Aşağıdaki örnekte server.js doğrudan çalışan bir Node.js uygulaması, production bağımlılıkları da lock dosyasında tanımlıdır. TypeScript build gerektiren projede ayrıca build aşaması gerekir.
FROM node:22-bookworm-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
EXPOSE portu internete açmaz; image hakkında bilgi verir. Host'a erişim için ayrıca port yayınlamanız gerekir. Uygulamanın container içinde yalnızca 127.0.0.1'e değil uygun ağ arayüzüne, çoğunlukla 0.0.0.0'a bağlanması da önemlidir.
docker build -t task-api:local .
docker run --name task-api -p 127.0.0.1:3000:3000 task-api:local
Burada host portu yalnızca yerel loopback üzerinden erişilebilir. Herkese açık bir sunucuda ağ sınırlarını bilinçli tasarlayın. Runtime sürümünü projenizin destek matrisiyle uyumlu tutun; production için taban image güncellemelerini ve digest sabitlemeyi ayrı yönetin.
Build context'e ne gönderiyorsunuz?
.dockerignore dosyası gereksiz veya hassas dosyaların build context'e taşınmasını sınırlar:
node_modules
.git
.env
.env.*
npm-debug.log
coverage
Secret'ı image'a kopyalayıp sonraki adımda silmek yeterli değildir; önceki katmanda kalabilir. Yapılandırma ve sırları runtime sırasında uygun secret mekanizmasıyla sağlayın. Image'ın dağıtılabilir olması, içine production parolalarının da konulması gerektiği anlamına gelmez.
Yazılabilir katman neden kalıcılık sağlamaz?
Container image'ın üzerinde kendine ait yazılabilir bir katman kullanır. Container durdurulup aynı nesne yeniden başlatıldığında bu katman durabilir; fakat container silinip yeniden oluşturulduğunda ona bağlı içerik kaybolur.
Dolayısıyla “restart sonrası duruyor” gözlemi kalıcı depolamanın doğru kurulduğunu kanıtlamaz. Gerçek deployment yeni container oluşturuyorsa kullanıcı dosyalarını yazılabilir katmana bırakmak risklidir.
Volume, veriyi container nesnesinden ayırır. Bind mount ise host'taki belirli bir yolu container içine yansıtır. Development sırasında kaynak kodu paylaşmak için bind mount; yönetilen kalıcı veriler için volume sık kullanılır. Volume belgesi yaşam döngüsü ve mount davranışlarını ayrıntılandırır.
Kalıcılığı küçük bir deneyle gözlemlemek
Aşağıdaki deney yalnızca yeni bir örnek volume oluşturur:
docker volume create notes-demo
docker run --rm -v notes-demo:/data alpine:3.22 sh -c 'echo merhaba > /data/note.txt'
docker run --rm -v notes-demo:/data alpine:3.22 cat /data/note.txt
İlk container silinse de ikinci container aynı volume'daki dosyayı okuyabilir. Ancak volume bir yedekleme sistemi değildir. Disk arızası, yanlış silme veya bozuk uygulama yazımı veriyi yine kaybettirebilir.
Veritabanı için backup almak, canlı veri dizinini rastgele kopyalamaktan daha fazla tutarlılık gerektirebilir. Yedek kadar geri yükleme deneyi de önemlidir.
İşletim tarafında tamamlanması gerekenler
Image'ın küçük olması tek başarı ölçütü değildir. Sürecin root olmaması, kaynak limitleri, sağlık kontrolü, SIGTERM'e doğru yanıt, logların toplanması ve kalıcı verinin yedeklenmesi birlikte değerlendirilmelidir.
Container'ın yaşam döngüsünü geçici kabul edip kalıcı veriyi açıkça ayırdığınızda deployment daha basit hale gelir: uygulama değiştirilebilir, veri ise bilinçli şekilde korunur.
Related Posts
Reverse Proxy Nedir? Nginx ile Gerçek Bir Kullanım Senaryosu
Node.js uygulamasını Nginx arkasına alırken URI yönlendirme, forwarded header, timeout ve 502/504 teşhisinin nasıl birlikte tasarlanacağını inceleyelim.
Monitoring ve Logging Arasındaki Fark: Sistemde Sorun Olduğunu Nasıl Anlarız?
Metrik, log ve trace verilerini kullanıcı etkisi etrafında birleştirelim; SLI/SLO, anlamlı alarmlar ve yüksek cardinality sorununu pratik örneklerle ele alalım.
Terraform ile Infrastructure as Code Mantığını Anlamak
Terraform'u komut otomasyonu yerine istenen durum yönetimi olarak ele alalım; plan, state, drift, bağımlılık grafiği ve güvenli apply sürecini örnekleyelim.