İçeriğe Atla
Mustafa Erbay
Rehberler · 11 dk okuma · görüntülenme Read in English

Docker Compose ile Self-Hosted Uygulamaları Hızla Çalıştırmanın 5 Yolu

Self-hosted uygulamaları Docker Compose ile hızlıca ayağa kaldırmak için minimal dosyalardan sağlık kontrollerine kadar 5 etkili yolu deneyimlerimle…

100%

Geçenlerde kendi sunucumda yeni bir yan ürünün prototipini geliştirirken, her seferinde servisleri manuel olarak ayağa kaldırmak veya karmaşık deployment script’leriyle uğraşmak yerine daha hızlı bir yol aradım. Docker Compose, özellikle single-host ortamlar için, uygulamaları hızla devreye almak ve yönetmek konusunda benim için hep pratik bir çözüm olmuştur. Bu yazıda, self-hosted uygulamalarınızı Docker Compose ile daha verimli ve güvenilir bir şekilde çalıştırmak için benim de sıklıkla kullandığım beş temel yaklaşımı paylaşacağım.

Docker Compose, birden fazla container’ı tanımlayan ve çalıştıran bir araçtır. Bir YAML dosyası kullanarak uygulamanızın tüm servislerini, ağlarını ve depolama birimlerini tek bir yerden yapılandırmanıza olanak tanır. Bu sayede, karmaşık bağımlılıkları olan uygulamaları tek bir komutla ayağa kaldırabilir, durdurabilir ve yönetebilirsiniz. Bu yaklaşım, özellikle hızlı prototipleme, geliştirme ortamları ve küçük ölçekli üretim dağıtımları için büyük kolaylık sağlar.

1. Minimal Compose Dosyalarıyla Başlangıç Maliyetini Azaltmak

Self-hosted bir uygulama geliştirirken veya dağıtırken, başlangıçtaki kurulum karmaşasını minimize etmek çok önemlidir. Çoğu zaman, bir servisi ayağa kaldırmak için ihtiyacımız olan tek şey bir image tanımı ve belki birkaç port ve volume ayarıdır. İlk başta her detayı düşünerek devasa bir docker-compose.yaml dosyası oluşturmaya çalışmak, hem zaman kaybı hem de gereksiz bir yorgunluktur. Ben genellikle en temel gereksinimlerle başlar, uygulama çalıştıkça ve ihtiyaçlar netleştikçe dosyayı iteratif olarak geliştiririm.

Bir veritabanı, bir API servisi ve bir frontend’den oluşan tipik bir web uygulaması için ilk docker-compose.yaml dosyam genelde şöyle görünür:

version: '3.8'
services:
  database:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: myapp_db
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    volumes:
      - db_data:/var/lib/postgresql/data
  api:
    build: ./api
    restart: unless-stopped
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgres://myuser:mypassword@database:5432/myapp_db
    depends_on:
      - database
  frontend:
    build: ./frontend
    restart: unless-stopped
    ports:
      - "3000:3000"
    depends_on:
      - api
volumes:
  db_data:

Bu örnekte, database, api ve frontend servislerini tanımladım. Her servisin temel bir image veya build komutu, restart politikası, gerekli environment değişkenleri ve ports var. Özellikle depends_on ile servisler arası bağımlılıkları belirtmek, servislerin doğru sırada başlamasını sağlıyor. volumes tanımı ise veritabanı verilerinin kalıcı olmasını güvence altına alıyor. Bu sade yaklaşım, uygulamanın çekirdek fonksiyonlarını hızla test etmeme ve ilk iterasyonlarda gereksiz detaylarla boğulmamamı sağlıyor.

Bu minimal yapı, bir üretim ERP’sinin yeni bir modülünü test ederken veya kendi Android uygulamamın backend’ini geliştirirken sıkça başvurduğum bir yöntemdir. Özellikle bir konsepti hızla doğrulamam gerektiğinde, karmaşık YAML dosyalarının getirdiği ek yükten kaçınmak, projenin momentumunu korumama yardımcı oluyor. İlk başta her şeyi mükemmel yapmaya çalışmak yerine, çalışır bir iskeletle yola çıkmak, sonraki adımları daha net görmemi sağlıyor.

2. Ortam Değişkenlerini Akıllıca Yönetmek: .env ve Compose Değişkenleri

Uygulamalarımı self-host ederken, hassas bilgilerin (API anahtarları, veritabanı şifreleri) docker-compose.yaml dosyası içinde açıkça yazılı kalmasını asla istemem. Bu hem güvenlik riski taşır hem de farklı ortamlar (geliştirme, test, üretim) arasında yapılandırmayı yönetmeyi zorlaştırır. İşte bu noktada .env dosyaları ve Docker Compose’un değişken yönetimi yetenekleri devreye giriyor. Benim için bu, esnek ve güvenli bir dağıtımın temel taşlarından biridir.

Bir .env dosyası, docker-compose.yaml dosyanızın bulunduğu dizinde yer alan basit bir metin dosyasıdır ve içinde anahtar-değer çiftleri bulunur:

# .env dosyası
POSTGRES_PASSWORD=mysupersecretpassword
API_KEY=anothersecretkey
FRONTEND_PORT=3000

docker-compose.yaml dosyasında bu değişkenleri $VARIABLE_NAME veya ${VARIABLE_NAME} şeklinde referans alabilirim:

version: '3.8'
services:
  database:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp_db
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # .env dosyasından çekilir
    volumes:
      - db_data:/var/lib/postgresql/data
  api:
    build: ./api
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgres://myuser:${POSTGRES_PASSWORD}@database:5432/myapp_db
      API_SECRET: ${API_KEY}
    depends_on:
      - database
  frontend:
    build: ./frontend
    ports:
      - "${FRONTEND_PORT}:${FRONTEND_PORT}" # .env dosyasından çekilir
    depends_on:
      - api
volumes:
  db_data:

Bu sayede, hassas bilgileri YAML dosyasından ayırabilir ve .env dosyasını git gibi versiyon kontrol sistemlerinden hariç tutabilirim (genellikle .gitignore içine eklerim). Ayrıca, farklı ortamlarda farklı .env dosyaları kullanarak aynı docker-compose.yaml dosyasını yeniden kullanabilirim. Örneğin, geliştirme ortamı için dev.env ve üretim için prod.env oluşturup docker compose --env-file prod.env up -d gibi komutlarla spesifik ortamları ayağa kaldırabilirim.

Bir üretim ERP’sinde çalışırken, test ve üretim ortamları arasında veritabanı bağlantı bilgilerini veya harici servis anahtarlarını yönetmek her zaman bir meydan okumaydı. .env dosyaları, bu geçişleri sorunsuz hale getirerek, aynı Compose dosyasını farklı ortamlar için güvenle kullanmamıza olanak tanıdı. Bu sayede, “çalışan kod” kavramını sadece uygulama koduyla sınırlı tutmayıp, dağıtım yapılandırmasını da bu kapsamda değerlendirmiş oluyoruz.

3. Persistent Data İçin Volume’ları Doğru Kullanmak

Container’lar doğası gereği efemerdir; yani durdurulduklarında veya silindiklerinde içindeki tüm veriler kaybolur. Self-hosted uygulamalarımda veritabanları, kullanıcı yüklemeleri veya yapılandırma dosyaları gibi kalıcı verilere ihtiyacım olduğunda, Docker Volume’ları kullanmak kaçınılmazdır. Yanlış volume yapılandırması, bir sunucunun yeniden başlatılmasında tüm verilerin kaybolması gibi ciddi sorunlara yol açabilir. Bu yüzden volume’ları doğru şekilde yapılandırmak, uygulamanın sürekliliği ve veri güvenliği açısından kritik öneme sahiptir.

Docker Compose’da iki ana volume türü kullanıyorum:

  1. Named Volumes (İsimli Volume’lar): Docker tarafından yönetilen ve genellikle docker volume ls komutuyla görebileceğiniz volume’lardır. Bunlar, verilerin container’dan bağımsız olarak saklanması için en iyi yoldur ve genellikle veritabanları gibi kritik veriler için tercih edilir.

    version: '3.8'
    services:
      database:
        image: postgres:15-alpine
        volumes:
          - db_data:/var/lib/postgresql/data # Named volume
    volumes:
      db_data: # Volume tanımı
  2. Bind Mounts (Bağlantı Mount’ları): Host makinenin belirli bir dizinini doğrudan container içine bağlar. Bu, geliştirme sırasında kod değişikliklerini anında yansıtmak veya host üzerindeki yapılandırma dosyalarını container’a sağlamak için kullanışlıdır. Ancak, üretimde veritabanları için Named Volumes daha güvenli ve yönetilebilir bir seçenektir.

    version: '3.8'
    services:
      api:
        build: ./api
        volumes:
          - ./api:/app # Host dizinini container'a bağlar

db_data gibi named volume’ları kullandığımda, docker compose down komutuyla servisleri durdursam bile veritabanı verilerim güvende kalır. Bu, özellikle bir güncelleme veya hata durumunda servisi yeniden başlatmam gerektiğinde büyük bir rahatlık sağlar.

Bir müşteri projesinde, yanlışlıkla bir bind mount kullanarak veritabanı verilerini host üzerindeki geçici bir dizine bağladığımı hatırlıyorum. Sunucu yeniden başlatıldığında tüm veriler uçup gitmişti. Bu olay bana Named Volumes ve Bind Mounts arasındaki farkı ve her birinin ne zaman kullanılması gerektiğini acı bir şekilde öğretti. O zamandan beri, kritik veriler için her zaman Named Volumes kullanmaya özen gösteriyorum ve hatta yedekleme stratejimi de bu volume’ları göz önünde bulundurarak tasarlıyorum.

4. Ağ Yapılandırmasını Optimize Etmek: Custom Networks ve Port Mapping

Docker Compose ile birden fazla servisi çalıştırdığımda, servisler arası iletişimin hem güvenli hem de verimli olması gerekir. Varsayılan olarak Docker Compose, uygulamanız için tek bir varsayılan ağ oluşturur ve tüm servisleri bu ağa bağlar. Ancak, daha fazla izolasyon, güvenlik veya karmaşık bir ağ topolojisi gerektiğinde, custom networks tanımlamak ve port mapping stratejilerini optimize etmek çok işime yarar.

Örneğin, bir frontend servisi sadece Nginx reverse proxy tarafından erişilebilirken, backend servisi sadece frontend’den veya başka bir internal servisten erişilebilir olmalıdır. Bu senaryoda, farklı ağlar tanımlayarak servisler arası erişimi kısıtlayabilirim:

version: '3.8'
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - external_network
      - internal_network
  frontend:
    build: ./frontend
    networks:
      - internal_network
  api:
    build: ./api
    networks:
      - internal_network
  database:
    image: postgres:15-alpine
    networks:
      - internal_network
    environment:
      POSTGRES_DB: myapp_db
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    volumes:
      - db_data:/var/lib/postgresql/data

networks:
  external_network:
    driver: bridge
  internal_network:
    driver: bridge
volumes:
  db_data:

Bu yapılandırmada, nginx servisi hem external_network (internete açık) hem de internal_network (uygulama servisleri arası iletişim) üzerinde yer alıyor. frontend, api ve database servisleri ise sadece internal_network üzerinde çalışıyor. Bu sayede, database servisine dışarıdan doğrudan erişim engellenmiş oluyor. Ayrıca, servisler kendi aralarında isimleriyle (örneğin, api servisi database servisine database:5432 üzerinden erişebilir) iletişim kurabilir, bu da yapılandırmayı basitleştirir.

Bir bankanın iç platformunu geliştirirken, farklı servislerin farklı güvenlik bölgelerinde çalışması gerekiyordu. Custom networks kullanarak bu bölgeler arasında sıkı bir izolasyon sağladık. Sadece belirli servislerin birbirleriyle konuşmasına izin vererek, olası bir güvenlik ihlalinin yayılmasını kısıtlamış olduk. Bu, sadece Docker Compose ortamlarında değil, genel olarak ağ segmentasyonu ve Zero-Trust mimarisi prensiplerinin bir yansımasıdır. Nginx’in sadece dışarıya açık olması, diğer servislerin ise kendi iç ağlarında güvenli bir şekilde barınması, bu tür mimarilerde temel bir adımdır.

5. Sağlık Kontrolleri (Health Checks) ve Restart Politikaları ile Güvenilirliği Artırmak

Self-hosted bir uygulamanın sadece “çalışır” olması yetmez; aynı zamanda “sağlıklı” olması ve beklenmedik durumlarda otomatik olarak toparlanabilmesi gerekir. Docker Compose, healthcheck mekanizması ve restart politikaları ile bu güvenilirliği artırmamıza olanak tanır. Bir servisin gerçekten çalışır durumda olup olmadığını anlamak ve çöktüğünde otomatik olarak yeniden başlatılmasını sağlamak, operasyonel yükümü ciddi oranda azaltır.

Bir healthcheck tanımı, Docker’ın bir container’ın içindeki uygulamanın durumunu periyodik olarak kontrol etmesini sağlar. Örneğin, bir web servisi için belirli bir HTTP endpoint’ine istek atarak yanıt alıp almadığını kontrol edebiliriz:

version: '3.8'
services:
  api:
    build: ./api
    ports:
      - "8000:8000"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s
    restart: unless-stopped
    depends_on:
      - database

Bu örnekte, api servisi için healthcheck tanımladım. Docker, her 30 saniyede bir http://localhost:8000/health adresine curl ile istek atacak. Eğer 10 saniye içinde yanıt alamazsa veya HTTP hata kodu dönerse (curl’ün -f bayrağı ile), bu bir başarısızlık olarak sayılacak. Üç başarısız denemeden sonra container sağlıksız (unhealthy) olarak işaretlenecek. start_period, uygulamanın başlaması için yeterli zaman tanır; bu süre içinde sağlık kontrolü başarısız olsa bile yeniden başlatma tetiklenmez.

restart politikaları ise bir container’ın ne zaman otomatik olarak yeniden başlatılacağını belirler:

  • no: Otomatik yeniden başlatma yok.
  • on-failure: Container bir hata koduyla (sıfır olmayan) çıkarsa yeniden başlatır.
  • always: Container her zaman yeniden başlatılır (manuel olarak durdurulana kadar).
  • unless-stopped: Container manuel olarak durdurulmadığı sürece her zaman yeniden başlatılır (benim en çok kullandığım).

Kendi yan ürünümün backend’inde bir Redis servisi kullanıyordum ve bazen Redis container’ı bellek yetersizliği (OOM) nedeniyle çöküyordu. restart: unless-stopped politikası sayesinde, Redis otomatik olarak yeniden başlıyor ve uygulamamın kesintisiz çalışmasını sağlıyordu. Ancak, healthcheck ekleyerek Redis’in gerçekten çalışır durumda olduğunu doğrulamak, sadece container’ın yaşıyor olmasından öte, uygulamanın gerçekten hizmet verebildiğinden emin olmamı sağladı. Bu ikili yaklaşım, self-hosted sistemlerimin dayanıklılığını artırmak için vazgeçilmez bir kombinasyondur.

graph TD;
  A["Kullanıcı Erişimi"] --> B["Nginx Reverse Proxy"];
  B --> C{"External_Network"};
  C --> N["Nginx"];
  N --> D{"Internal_Network"};
  D --> E["Frontend Servisi"];
  E --> F["API Servisi"];
  F --> G["Veritabanı Servisi (PostgreSQL)"];
  style A fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;
  style B fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;
  style C fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;
  style N fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;
  style D fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;
  style E fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;
  style F fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;
  style G fill:#D4E6F1,stroke:#3498DB,stroke-width:2px;

Sonuç

Docker Compose, self-hosted uygulamaları hızlı ve güvenilir bir şekilde ayağa kaldırmak için benim gibi saha tecrübesi olan birisi için bile paha biçilmez bir araçtır. Minimalist docker-compose.yaml dosyalarıyla hızlıca başlamak, ortam değişkenlerini .env ile akıllıca yönetmek, kritik veriler için Named Volumes kullanmak, ağ yapılandırmasını Custom Networks ile optimize etmek ve son olarak Health Checks ile Restart Policies kullanarak uygulamanın dayanıklılığını artırmak, benim sıkça başvurduğum temel stratejilerdir.

Bu yaklaşımlar, sadece bir uygulamayı çalıştırmakla kalmaz, aynı zamanda onu daha yönetilebilir, güvenli ve esnek hale getirir. Her bir adım, yaşadığım gerçek dünya sorunlarından ve çözümlerden süzülerek gelmiştir. Umarım bu rehber, kendi self-hosted projelerinizde Docker Compose’dan en iyi şekilde faydalanmanıza yardımcı olur ve daha az operasyonel baş ağrısı yaşamanızı sağlar. Bir sonraki yazımda belki Docker Compose ile CI/CD entegrasyonu üzerine deneyimlerimi paylaşırım.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

Sıkça Sorulanlar

Bu makale ile ilgili okurların sorduğu yaygın sorular.

Docker Compose ile self-hosted uygulamaları hızlıca çalıştırmak için hangi araçları ve yöntemleri kullanmalıyım?
Ben genellikle minimal Compose dosyalarıyla başlangıç maliyetini azaltmak için gereken araçları ve yöntemleri kullanırım. Docker Compose, özellikle single-host ortamlar için, uygulamaları hızla devreye almak ve yönetmek konusunda benim için hep pratik bir çözüm olmuştur. Bir YAML dosyası kullanarak uygulamanızın tüm servislerini, ağlarını ve depolama birimlerini tek bir yerden yapılandırmanıza olanak tanır.
Docker Compose ile self-hosted uygulamaları çalıştırırken hangi avantajlara sahip olurum?
Docker Compose ile self-hosted uygulamaları çalıştırırken, hızlı prototipleme, geliştirme ortamları ve küçük ölçekli üretim dağıtımları için büyük kolaylık sağlar. Ayrıca, karmaşık bağımlılıkları olan uygulamaları tek bir komutla ayağa kaldırabilir, durdurabilir ve yönetebilirsiniz. Benim deneyimime göre, bu yaklaşım özellikle hızlı ve verimli bir şekilde uygulamaları çalıştırmak için idealdir.
Docker Compose dosyalarını oluştururken hangi best practice'leri takip etmeliyim?
Ben genellikle en temel gereksinimlerle başlar, uygulama çalıştıkça ve ihtiyaçlar netleştikçe dosyayı iteratif olarak geliştiririm. İlk başta her detayı düşünerek devasa bir `docker-compose.yaml` dosyası oluşturmaya çalışmak, hem zaman kaybı hem de gereksiz bir yorgunluktur. Bu nedenle, minimum gereksinimlerle başlamak ve gerektikçe dosyayı geliştirmek en iyi yaklaşım olduğunu düşünüyorum.
Docker Compose ile self-hosted uygulamaları çalıştırırken karşılaşabileceğim hataları nasıl çözerim?
Benim deneyimime göre, Docker Compose ile self-hosted uygulamaları çalıştırırken karşılaşabileceğiniz hataları çözmek için, öncelikle hata mesajlarını dikkatlice incelemeli ve ilgili servislerin durumunu kontrol etmelisiniz. Ayrıca, Docker Compose'un sağladığı sağlık kontrolleri ve logging özellikleri de hata ayıklama sürecini kolaylaştırabilir. Ben genellikle bu araçları kullanarak hataları hızlı bir şekilde tespit eder ve çözerim.
ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Yeni yazılardan haberdar olun

Yeni içerikler ve teknik notlar e-postanıza gelsin.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar