İçeriğe geç
  • Hafta içi 08:00 – 18:00 · Destek 7/24
  1. Ana Sayfa
  2. Blog
  3. Barındırma

Ubuntu'da Docker kurulumu: resmî depo, ilk ayarlar ve güvenlik duvarı tuzağı

Docker'ı Ubuntu deposundan değil resmî depodan kurmak, konteyneri kök yetkisiz çalıştırmak, günlüklerin diski doldurmasını engellemek ve en önemlisi: Docker'ın yayımladığı portların güvenlik duvarını neden deldiğini ölçerek görmek.

Sunucu üzerinde birbirinden yalıtılmış konteynerleri ve dışarı açılan tek kapıyı anlatan izometrik çizim

Docker, uygulamayı bağımlılıklarıyla birlikte paketleyip sunucuda yalıtılmış biçimde çalıştırır. Kurulumu birkaç komuttur; asıl iş, kurulumdan sonra yapılan üç ayardır: yetkiyi doğru dağıtmak, günlüklerin diski doldurmasını engellemek ve güvenlik duvarının gerçekten çalıştığından emin olmak. Son madde, bu yazıdaki en önemli bölüm: ölçtük ve Docker'ın varsayılan davranışı güvenlik duvarınızı sessizce devre dışı bırakıyor.

1. Resmî depodan kurulum

Ubuntu'nun kendi deposundaki paket yerine Docker'ın resmî deposunu ekleyin; güncel sürümü ve compose eklentisini birlikte getirir. Önce depo anahtarı:

apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

Sonra depo satırı ve kurulum:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
  > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Komuttaki $VERSION_CODENAME dağıtımın kod adını (24.04 için noble) kendisi bulur; elle yazmayın, yükseltme sonrası yanlış depoya bakan bir satır kalmasın. Kurulumu doğrulayın:

docker --version
docker compose version
docker info --format '{{.ServerVersion}} storage={{.Driver}} cgroup={{.CgroupVersion}}'

Deneme sunucumuzda çıktı Docker version 29.9.1, Docker Compose version v5.6.0 ve storage=overlayfs cgroup=2 oldu. docker-compose (tireli) eski bir ayrı araçtır; bugün doğru kullanım tiresiz docker compose alt komutudur.

2. Yetki: docker grubu kök yetkisidir

Her komutun başına sudo yazmamak için kullanıcıyı docker grubuna eklemek yaygın bir alışkanlıktır. Bunun ne anlama geldiğini bilerek yapın: bu gruptaki bir kullanıcı, ana sistemin kök dizinini bir konteynere bağlayıp içindeki her dosyayı kök yetkisiyle değiştirebilir. Yani docker grubu, pratikte parolasız sudo ile aynı kapıyı açar.

Sunucuda zaten tam yetkisi olan yöneticiler için bu kabul edilebilir. Daha dar yetki gerekiyorsa iki yol var: belirli komutları sudo kuralıyla açmak ya da Docker'ı kök yetkisiz (rootless) kipte kurmak. Rootless kip 1024'ün altındaki portları doğrudan yayımlayamaz; önüne bir ters vekil koyarsanız bu çoğu zaman sorun olmaz.

3. Günlük dönüşünü baştan açın

Docker'ın varsayılan günlük sürücüsü json-file'dır ve dönüş (rotation) varsayılan olarak kapalıdır. Deneme sunucumuzda /etc/docker/daemon.json dosyası hiç yoktu; yani çok konuşan tek bir konteyner, tek bir günlük dosyasını diski dolduruncaya kadar büyütebilir. Kurulumdan hemen sonra sınır koyun:

cat > /etc/docker/daemon.json <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
EOF
systemctl restart docker

Bu ayar yalnız yeni oluşturulan konteynerler için geçerlidir; çalışanları yeniden oluşturmanız gerekir. Mevcut durumu docker inspect <ad> --format '{{.HostConfig.LogConfig}}' ile görebilirsiniz. Disk kullanımını zaman zaman docker system df ile kontrol edin; kullanılmayan imaj ve katmanlar docker system prune ile temizlenir.

4. Güvenlik duvarı tuzağı: Docker UFW'yi atlar

Bu bölüm, kurulumdan sonra yapılacak en kritik kontroldür. Deneme sunucumuzda UFW'yi açtık ve yalnız SSH'a izin verdik. Sonra basit bir konteyneri 8080 portuyla yayımladık:

ufw allow OpenSSH && ufw enable
docker run -d --name dz -p 8080:80 nginx:alpine

UFW durumunda 8080 için hiçbir kural yoktu. Buna rağmen sunucunun dışından yapılan istek cevap verdi:

curl -o /dev/null -w "%{http_code}\n" http://SUNUCU_IP:8080/
200

Sebebi şudur: Docker, yayımlanan portlar için iptables'ın NAT tablosuna kendi kurallarını yazar ve bu kurallar paketi UFW'nin filtre zincirine varmadan yönlendirir. UFW arayüzünde her şey kapalı görünür, gerçekte açıktır. Bu yüzden "güvenlik duvarım var" diye bırakılan veritabanı ve yönetim arayüzü konteynerleri internete açık kalır.

Çözüm 1: portu yalnız yerele yayımlayın

Konteynere dışarıdan doğrudan erişilmesi gerekmiyorsa (önünde bir ters vekil varsa ya da yalnız başka bir konteyner kullanıyorsa) portu yerel adrese bağlayın:

docker run -d --name dz -p 127.0.0.1:8080:80 nginx:alpine

Ölçtüğümüzde bu biçimde yayımlanan port yerelden 200 döndü, dışarıya hiç açılmadı. Veritabanları, önbellek servisleri ve yönetim panelleri için varsayılan davranışınız bu olmalı. Compose dosyasında da aynı biçim geçerlidir: ports: ["127.0.0.1:8080:80"]. Aynı ağdaki konteynerler birbirine zaten servis adıyla ulaşır; onlar için port yayımlamanıza hiç gerek yoktur.

Çözüm 2: DOCKER-USER zinciri

Portun gerçekten dışarı açık olması ama belirli adreslerle sınırlanması gerekiyorsa, Docker'ın bu iş için bıraktığı DOCKER-USER zincirini kullanın. Bu zincir, Docker'ın kendi kurallarından önce değerlendirilir ve Docker yeniden başlasa da korunur:

iptables -I DOCKER-USER 1 ! -s 203.0.113.10 -p tcp --dport 80 -j DROP

Kuralı ekledikten sonra aynı dış isteği tekrarladık; bağlantı artık hiç kurulmadı (curl zaman aşımına uğradı). Burada iki ayrıntı önemli: hedef port olarak konteynerin içindeki portu (örnekte 80) yazarsınız, yayımlanan portu değil; ve iptables kuralları yeniden başlatmada kaybolacağı için iptables-persistent gibi bir paketle kalıcı hâle getirmeniz gerekir. Kendi IP adresinizi IP adresim aracıyla öğrenebilirsiniz.

Kurulumdan sonra bir kez yapın: sunucunun dışından kendi IP adresinize bir port taraması çalıştırın ya da yayımladığınız her portu dışarıdan curl ile deneyin. "UFW açık" demek Docker varken yeterli bir kanıt değildir; ölçüm yeterlidir.

5. İlk compose dosyası

Tek tek docker run komutları yerine servisleri bir dosyada tanımlayın; böylece yapılandırma sürüm kontrolüne girer ve sunucu değiştiğinde aynı biçimde kurulur.

services:
  web:
    image: nginx:alpine
    restart: unless-stopped
    ports: ["127.0.0.1:8080:80"]
    volumes:
      - ./site:/usr/share/nginx/html:ro
volumes: {}

restart: unless-stopped konteynerin sunucu yeniden başladığında kendiliğinden kalkmasını sağlar. Kalıcı veriyi mutlaka bir birime (volume) ya da bağlı dizine yazın; konteyner silindiğinde içindeki dosyalar da gider. docker compose up -d ile başlatır, docker compose logs -f ile izler, docker compose down ile durdurursunuz.

Etiket disiplini ve kaynak sınırları

Kurulum bittikten sonra üretimde fark yaratan iki alışkanlık var; ikisi de ilk günden kurulmazsa sonradan sorun olarak geri döner.

latest etiketi bir sürüm değildir

nginx:alpine ya da n8nio/n8n:latest gibi etiketler sabit bir sürümü göstermez; bugün çektiğiniz imajla üç ay sonra çektiğiniz imaj farklı olabilir. Bu, "dün çalışıyordu" ile başlayan arızaların klasik kaynağıdır: siz hiçbir şey değiştirmeden sunucuyu yeniden kurduğunuzda ya da konteyneri yeniden oluşturduğunuzda altınızdaki uygulama sürümü değişmiştir. Üretimde sürümü sabitleyin:

image: nginx:1.27-alpine
# ya da tam kesinlik için:
image: nginx@sha256:...

Sabitlemek güncelleme yapmayacağınız anlamına gelmez; güncellemeyi sizin kararınız hâline getirir. Sürümü yükselttiğinizde değişiklik notlarını okur, önce deneme ortamında çalıştırır, sonra üretime alırsınız.

Bir konteyner sunucunun tamamını yiyebilir

Varsayılan olarak konteynerin kullanabileceği bellek ve işlemci sınırsızdır. Bellek sızdıran ya da beklenmedik bir yükle karşılaşan tek bir konteyner, sunucudaki diğer her şeyi (SSH dahil) kullanılamaz hâle getirebilir. Sınırları compose dosyasında tanımlayın:

services:
  uygulama:
    image: ornek:1.2
    mem_limit: 512m
    cpus: 1.0
    restart: unless-stopped

Sınır koymanın ikinci faydası teşhistir: belleği biten konteyner çekirdek tarafından sonlandırılır ve bunu docker inspect çıktısındaki OOMKilled alanında görürsünüz. Sınır yoksa aynı durum, sunucunun rastgele bir sürecinin öldürüldüğü çok daha kafa karıştırıcı bir arızaya dönüşür. Konteynerlerin anlık kaynak kullanımını docker stats ile izleyebilirsiniz.

Docker her iş için doğru araç mı?

Konteyner, bağımlılıkları karışan ve farklı sürümleri yan yana çalıştırması gereken kurulumlarda büyük kolaylık sağlar. Buna karşılık tek bir web sitesi ve tek bir PHP sürümü çalıştıracaksanız, Docker katmanı size taşıyabileceğinizden fazla işletim yükü getirebilir: imaj güncellemeleri, birim yönetimi, günlük dönüşü ve yukarıda anlatılan güvenlik duvarı davranışı artık sizin sorumluluğunuzdadır. Böyle bir kurulumda paketleri doğrudan kurmak (nginx ve PHP-FPM kurulumu gibi) daha az hareketli parça demektir.

Karar ölçütü şudur: aynı sunucuda birbiriyle çakışan bağımlılıklara sahip birden çok uygulama çalıştıracaksanız, kurulumu başka sunuculara birebir taşımanız gerekiyorsa ya da kullandığınız yazılım resmî olarak konteyner imajıyla dağıtılıyorsa Docker doğru araçtır. Tek ve sabit bir yığın çalıştıracaksanız kazancı sınırlıdır.

Kurulum sonrası kontrol listesi

  • docker compose version çalışıyor mu?
  • /etc/docker/daemon.json içinde günlük sınırı var mı?
  • Yayımlanan her port gerçekten dışarı açık olmalı mı? Değilse 127.0.0.1: önekiyle bağlayın.
  • Dışarı açık kalan portları dışarıdan bir istekle doğruladınız mı?
  • Kalıcı veriler birimde mi, yoksa konteynerin içinde mi duruyor?
  • Sunucunun temel sertleştirmesi yapıldı mı? İlk gün yapılacak on iş bu kurulumun altındaki zemindir.

Docker üzerinde gerçek bir uygulamayı HTTPS'li biçimde yayına almak için n8n kurulumu yazısındaki compose ve ters vekil örneğine bakabilirsiniz.

Sık sorulan sorular

Docker'ı Ubuntu'nun kendi deposundan kurmak yeterli mi?

Çalışır, ama Ubuntu deposundaki docker.io paketi genellikle daha eski bir sürümdür ve docker compose eklentisi ayrı gelir. Resmî Docker deposu güncel sürümü ve compose eklentisini birlikte verir; belgelerdeki komutlar da bu kuruluma göre yazılmıştır.

docker grubuna kullanıcı eklemek güvenli mi?

Bu grup pratikte kök yetkisine eşittir: gruptaki bir kullanıcı ana sistemin dosya sistemini bir konteynere bağlayıp her dosyayı değiştirebilir. Yalnız sunucuda zaten tam yetkisi olan kişilere verin; sınırlı yetkili kullanıcılar için sudo ile belirli komutlara izin vermek ya da kök yetkisiz (rootless) kurulum daha doğrudur.

Docker neden UFW kurallarını dinlemiyor?

Docker yayımlanan portlar için iptables'a kendi NAT kurallarını yazar ve bu kurallar UFW'nin filtre zincirinden önce değerlendirilir. Sonuç olarak -p 8080:80 ile açılan bir port, UFW'de hiçbir izin olmasa bile dışarıdan erişilebilir olur.

Docker günlükleri diski doldurur mu?

Varsayılan json-file sürücüsünde günlük dönüşü kapalıdır; çok konuşan bir konteyner tek bir dosyayı gigabaytlara çıkarabilir. /etc/docker/daemon.json içinde boyut ve dosya sayısı sınırı tanımlayarak bunu baştan önleyin.

Docker konteyneri sanal makineden farkı nedir?

Sanal makine kendi işletim sistemi çekirdeğini çalıştırır; konteyner ana sistemin çekirdeğini paylaşır ve yalnız uygulamayı bağımlılıklarıyla yalıtır. Bu yüzden konteyner saniyeler içinde başlar ve daha az kaynak tüketir, ama yalıtımı sanal makine kadar güçlü değildir: çekirdek düzeyinde bir açık tüm konteynerleri etkiler.

Docker konteynerindeki veriler kalıcı mı?

Hayır; konteyner silindiğinde yazdığı dosyalar da gider. Kalıcı olması gereken her şey bir birime (volume) ya da ana sistemden bağlanan bir dizine yazılmalıdır. Güncelleme sırasında konteynerler yeniden oluşturulduğu için bu ayrım üretimde kritiktir.

  • #docker
  • #ubuntu
  • #konteyner
  • #güvenlik duvarı

Konteyner çalıştıracak bir sunucu mu arıyorsunuz? VDS paketlerimizde kök erişimi sizdedir; işletim sistemini ve Docker sürümünü siz seçersiniz.

VDS paketlerine bakın
Devamı

İlgili yazılar