Docker Compose'ta birden fazla dosyayla çalışmanın üç yolu vardır. Merge, -f ile verdiğiniz dosyaları sırayla üst üste birleştirir; compose.override.yaml bu yolla kendiliğinden okunur. extends, tek bir servis tanımını başka bir servisten veya başka bir dosyadan devralır. include ise başka bir Compose uygulamasını kendi proje dizini ve kendi .env dosyasıyla birlikte modelinize kopyalar. Bunlara ek olarak tek dosya içinde tekrarı azaltmak için YAML fragment'leri ve x- ile başlayan extension alanları kullanılır.

Bu yazı bu dört mekanizmanın kurallarını, birbirinden farklarını ve en çok kafa karıştıran noktaları anlatır. Temel override dosyası kullanımı Docker Compose rehberinde anlatıldığı için burada birleştirme kurallarının ayrıntısına iniyoruz. Örneklerdeki tüm çıktılar Docker Compose v5.5.1 ile docker compose config çalıştırılarak alındı. Serinin tamamı için Docker öğrenme rehberi.

Hangi yöntem ne zaman?

Hangi yöntem ne zaman? Merge (-f, override), extends, include, Fragment ve x- alanları

Belgeler en basit yolun -f ile birleştirme olduğunu, ancak yapılandırma büyüdükçe birleştirme kurallarının yönetimi zorlaştırabileceğini söyler. Ekiplerin ayrı alt dizinlerde kendi Compose dosyalarını tuttuğu bir monorepo için include önerilir, çünkü merge ve extends'in yarattığı göreli yol sorununu çözer.

YöntemBirimiGöreli yollarTipik kullanım
Merge (-f, override)Tüm dosyaİlk (temel) dosyaya göreGeliştirme ve production farkları
extendsTek servisDevralınan dosyadaki yollar dönüştürülürOrtak ayarları paylaşan servisler
includeBütün Compose uygulamasıHer dosya kendi dizinine göreBaşka ekibin veya bileşenin dosyası
Fragment ve x- alanlarıDosya içi YAML parçasıEtkilemezAynı dosyada tekrar eden bloklar

Merge: dosyaları üst üste birleştirmek

Compose varsayılan olarak iki dosya okur: temel yapılandırmayı içeren compose.yaml ve isteğe bağlı compose.override.yaml. Başka adlı dosyalar veya ikiden fazla dosya için -f bayrağını ya da COMPOSE_FILE değişkenini kullanırsınız. Dosyalar komut satırında verildikleri sırayla birleştirilir; sonraki dosya öncekine ekleme yapabilir, onu ezebilir. -f verilmezse Compose çalışma dizininde ve üst dizinlerde bu iki dosyayı arar; aynı düzeydeyse ikisini birleştirir. -f - yazarak yapılandırmayı standart girdiden de okutabilirsiniz; bu durumda tüm yollar bulunduğunuz dizine göre çözülür.

Compose belirtimi birleştirme kurallarını şöyle tanımlar:

  • Mapping (eşleme): Eksik anahtarlar eklenir, çakışan anahtarlarda sonraki dosyanın değeri geçerli olur. environment ve labels bu yüzden değişken veya etiket adına göre birleşir.
  • Sequence (dizi): Sonraki dosyadaki öğeler öncekinin sonuna eklenir. Örneğin iki dosyadaki dns listeleri birleşir.
  • Komut istisnası: command, entrypoint ve healthcheck.test dizi olsa da eklenmez; son dosyadaki değer öncekinin yerine geçer.
  • Benzersiz kaynaklar: ports, volumes, secrets ve configs dizi olarak yazılır ama benzersiz anahtara göre birleşir. volumes, secrets ve configs için anahtar target, ports için ip, target, published ve protocol dörtlüsüdür. Aynı anahtarlı girdi birleşir, farklı olan eklenir.

Aşağıdaki temel dosya geliştirme için yazılmış; production dosyası ise portu yalnızca yerel adrese açıyor, DEBUG değişkenini kaldırıyor, komutu değiştiriyor ve veritabanı portunu tamamen siliyor:

yaml
services:
  web:
    image: example/my_web_app:latest
    ports:
      - "8080:80"
    environment:
      DEBUG: "true"
      LOG_LEVEL: info
    command: ["npm", "run", "dev"]
    depends_on:
      - db
  db:
    image: postgres:18
    ports:
      - "5432:5432"
yaml
services:
  web:
    ports: !override
      - "127.0.0.1:8080:80"
    environment:
      DEBUG: !reset null
      LOG_LEVEL: warn
    command: ["npm", "start"]
    restart: always
  db:
    ports: !reset []

docker compose -f compose.yaml -f compose.prod.yaml config çıktısında web servisi şöyleydi; db servisinde ise ports anahtarı hiç yoktu:

yaml
  web:
    command:
      - npm
      - start
    depends_on:
      db:
        condition: service_started
        required: true
    environment:
      LOG_LEVEL: warn
    image: example/my_web_app:latest
    networks:
      default: null
    ports:
      - mode: ingress
        host_ip: 127.0.0.1
        target: 80
        published: "8080"
        protocol: tcp
    restart: always

Burada üç kural birlikte çalıştı. command eklenmedi, değiştirildi. environment anahtar bazında birleşti: LOG_LEVEL yeni değeri aldı, !reset null ile DEBUG silindi. !override etiketi ise ports listesini birleştirme kurallarını atlayıp tamamen değiştirdi. Aynı production dosyasından !override etiketini kaldırdığımızda çıktıda iki port girdisi vardı: 8080:80 ve 127.0.0.1:8080:80. İkisinin ip değeri farklı olduğu için benzersiz anahtar da farklıdır ve liste birleşmiştir. Port yayınlamanın güvenlik tarafı için Docker port yayınlama ve Docker ve UFW yazılarına bakın.

!reset etiketi bir değeri temizler: geçerli bir değer yazmanız gerekir ama bu değer yok sayılır ve öznitelik kendi türünün varsayılanına veya null değerine çekilir. Okunabilirlik için belirtim !reset null veya !reset [] yazmanızı önerir. !override ise özniteliği olduğu gibi değiştirir; aynı adla tamamen farklı bir tanım kullanmak istediğinizde işe yarar.

extends: bir servisin ayarlarını devralmak

extends, ortak seçenekleri bir yerde tanımlayıp birden fazla serviste kullanmanızı sağlar. Değeri zorunlu service ve isteğe bağlı file anahtarlarından oluşan bir eşlemedir. file yoksa aynı dosyadaki bir servis kastedilir; göreli file yolu ana Compose dosyasının konumuna göre çözülür. Başka bir dosyadan devraldığınızda devralınan servis son projeye girmez; yalnızca özellikleri kopyalanır. Aynı dosya içinde devraldığınızda ise iki servis de son yapılandırmada yer alır.

Belgelerdeki web uygulaması ve kuyruk işçisi örneğinden uyarladığımız ortak dosya şöyle:

yaml
services:
  app:
    build: .
    environment:
      CONFIG_FILE_PATH: /code/config
      LOG_LEVEL: info
    cpu_shares: 5
yaml
services:
  webapp:
    extends:
      file: common.yaml
      service: app
    command: /code/run_web_app
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      - queue

  queue_worker:
    extends:
      file: common.yaml
      service: app
    command: /code/run_worker
    environment:
      LOG_LEVEL: debug
    depends_on:
      - queue

  queue:
    image: redis:latest

docker compose config --services çıktısında yalnızca queue, queue_worker ve webapp vardı; app servisi projeye girmedi. queue_worker için birleşik ortam şuydu:

yaml
    environment:
      CONFIG_FILE_PATH: /code/config
      LOG_LEVEL: debug

extends'in birleştirme kuralı merge'e benzer ama aynı değildir. Eşlemelerde ana servisteki anahtar devralınanı ezer; environment, labels, healthcheck, deploy.resources.limits gibi anahtarlar bu gruptadır. volumes ve devices da container içindeki hedef yol anahtar kabul edilerek eşleme gibi birleşir. Dizi kabul edilen anahtarlarda (cap_add, ports, secrets, security_opt gibi) devralınan öğeler önce, ana servisinkiler sonra gelir ve tekrar eden öğeler silinir. Bunların dışındaki her şey skalerdir ve ana servis kazanır. Devralınan servisin kendisi de extends içeriyorsa süreç hiç extends kalmayana kadar tekrarlanır; döngüsel referans ise hata verir.

extends'in en sık yanlış anlaşılan kısıtı, devralınan servisin başvurduğu kaynakların otomatik gelmemesidir. Ortak dosyadaki servis bir named volume kullanıyor ve bu volume yalnızca o dosyanın en üst düzeyinde tanımlıysa, devralan dosya bunu bilmez. Denememizde Compose bu durumda şu hatayı verdi:

bash
docker compose config --quiet
service "cli" refers to undefined volume veri: invalid compose project

Çözüm, kullanılan volume, network, secret ve config tanımlarını extends kullanan dosyaya da yazmaktır. Göreli yollar konusunda extends yardımcı olur: başka bir klasördeki dosyadan devraldığınızda o servisteki göreli yollar, aynı dosyayı göstermeye devam edecek şekilde dönüştürülür. Son olarak extends, docker stack deploy ile dağıtımda desteklenmez (bkz. Docker stack deploy).

include: başka bir Compose uygulamasını içe almak

include, en üst düzeyde bir liste olarak yazılır. Listede verilen her yol, kendi proje dizinine sahip ayrı bir Compose uygulaması olarak yüklenir ve göreli yollar o dosyanın kendi konumuna göre çözülür. Yüklenen uygulamanın tüm kaynak tanımları sizin modelinize kopyalanır; içe alınan servisleri, volume'ları ve network'leri kendi dosyanızda tanımlanmış gibi kullanabilirsiniz. include özyinelemelidir: içe alınan dosyanın kendi include bölümü varsa onlar da yüklenir.

Aşağıdaki yapıda veritabanını yöneten dosya veritabani/ klasöründe, kendi .env ve db.env dosyalarıyla birlikte duruyor. veritabani/.env içinde PG_VERSION=17 yazıyor.

yaml
services:
  db:
    image: "postgres:${PG_VERSION}"
    env_file: ./db.env
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:
yaml
include:
  - ../veritabani/compose.yaml
services:
  web:
    build: .
    depends_on:
      - db

app/ klasöründen çalıştırdığımız komutların çıktıları şöyleydi:

bash
docker compose config --services
db
web
docker compose config --images
postgres:17
app-web
PG_VERSION=18 docker compose config --images
postgres:18
app-web

Bu çıktılar include'un üç özelliğini gösteriyor. db servisi yerel dosyada tanımlanmış gibi modele girdi ve depends_on ile kullanılabildi. PG_VERSION değeri, içe alınan dosyanın kendi klasöründeki .env dosyasından geldi; ./db.env yolu da veritabani/ klasörüne göre çözüldü. Yerel projenin ortamı ise o varsayılanı ezdi: kabukta PG_VERSION=18 verince imaj değişti. Belgeler include yolunda interpolasyon kullanılabileceğini de söyler ve bu durumda değişkeni :? ile zorunlu yapmayı önerir.

Uzun sözdizimi üç alan sunar. path zorunludur ve tek bir dosya ya da birleştirilecek dosyaların listesi olabilir. project_directory göreli yolların çözüleceği temel dizini belirler; varsayılanı içe alınan dosyanın dizinidir. env_file interpolasyon varsayılanlarını verecek ortam dosyasıdır; varsayılanı project_directory içindeki .env dosyasıdır ve liste olarak birden fazla dosya verilebilir. Üçüncü taraf bir dosyayı kendi ihtiyacınıza göre değiştirmek için path listesine bir override dosyası ekleyebilirsiniz:

yaml
include:
  - path:
      - third-party/compose.yaml
      - override.yaml
    env_file: ./cache.env
services:
  web:
    image: nginx:latest
    depends_on:
      - cache

Üçüncü taraf dosyada image: "redis:${REDIS_TAG:-7}" ve "6379:6379" portu, override.yaml dosyasında ports: !override ile "127.0.0.1:6379:6379", cache.env dosyasında REDIS_TAG=8 vardı. Birleşik çıktıda cache servisi image: redis:8 ve yalnızca host_ip: 127.0.0.1 olan tek port girdisiyle geldi. Belgelerin belirttiği sınırlama, her include için ayrı bir override dosyası tutmanın karmaşık projelerde çok sayıda dosyaya yol açmasıdır. Diğer seçenek compose.override.yaml kullanmaktır: include sonucu oluşan birleşik model, genel override dosyasıyla yine değiştirilebilir.

Ad çakışmaları konusunda dikkatli olun. Belirtim, içe alınan bir kaynakla aynı adı taşıyan yerel bir tanım olduğunda Compose'un bunları birleştirmeye çalışmayıp uyarı verdiğini, how-to sayfası ise bu durumu hata olarak bildirdiğini söyler. Bizim Compose v5.5.1 denememizde içe alınan dosyada ve yerel dosyada aynı adlı redis servisi varken docker compose config hata ya da uyarı vermedi ve iki tanımı birleştirerek yerel dosyadaki imajı kullandı. Bu davranışa güvenmeyin; servis adlarını benzersiz tutun ve sonucu config ile kontrol edin. Belgeler include'un OCI artifact veya Git deposu gibi uzak kaynaklardan dosya alabildiğini de belirtir; böyle bir kaynağı kullanmadan önce içeriğini incelemeniz iyi olur.

Fragment ve extension alanları: dosya içinde tekrarı azaltmak

Aynı dosyada tekrar eden blokları YAML anchor ve alias'larıyla tek yerde tanımlayabilirsiniz. Anchor &ad ile oluşturulur, *ad ile kullanılır; işaret ile ad arasında boşluk olmamalıdır. Bu blokları servislerin dışında tutmak için x- ile başlayan extension alanları kullanılır; Compose bu alanları yok sayar. YAML merge anahtarı << ile bir anchor'ı kısmen ezebilir veya ona yeni anahtarlar ekleyebilirsiniz.

yaml
x-ortak: &ortak
  restart: unless-stopped
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"

x-env: &varsayilan-env
  TZ: Europe/Istanbul
  LOG_LEVEL: info

services:
  api:
    <<: *ortak
    image: example/api:1.4
    environment:
      <<: *varsayilan-env
      LOG_LEVEL: debug
  worker:
    <<: *ortak
    image: example/worker:1.4
    environment: *varsayilan-env

Çözülmüş modelde iki servis de restart: unless-stopped ve aynı log ayarlarını aldı; api için LOG_LEVEL: debug, worker için LOG_LEVEL: info geldi. Log sürücüsü seçenekleri Docker log boyutu sınırlama yazısında anlatılıyor. YAML merge yalnızca eşlemelerde çalışır, dizilerde çalışmaz. Bu yüzden ortam değişkenlerini fragment ile paylaşacaksanız FOO: BAR eşleme biçimini kullanmalısınız; - FOO=BAR liste biçimindeki bir anchor'ı << ile birleştirmeye çalıştığımızda Compose dosyayı map merge requires map or sequence of maps as the value hatasıyla reddetti. Anchor çözümlemesi interpolasyondan önce yapıldığı için anchor adlarını değişkenle de oluşturamazsınız (bkz. Compose değişken interpolasyonu).

Sık yapılan hatalar

Sık yapılan hatalar: Override dosyasındaki yolu kendi konumuna göre yazmak., Port listesinin değiştiğini sanmak., command listesinin eklendiğini sanmak., extends ile volume ve...
  • Override dosyasındaki yolu kendi konumuna göre yazmak. Merge'de tüm yollar temel dosyaya göredir.
  • Port listesinin değiştiğini sanmak. ports birleşir; eski portu kaldırmak için !override veya !reset gerekir.
  • command listesinin eklendiğini sanmak. command, entrypoint ve healthcheck.test her zaman son dosyadakiyle değişir.
  • extends ile volume ve network'lerin geleceğini varsaymak. Başvurulan kaynakları devralan dosyada da tanımlayın.
  • include ile aynı adlı servis tanımlamak. Belgeler bunu çakışma sayar; sürüm davranışına güvenmek yerine adları ayırın.
  • Liste biçimli anchor'ı birleştirmek. << yalnızca eşlemelerle çalışır.
  • Swarm'da extends kullanmak. docker stack deploy extends desteklemez.

Sık Sorulan Sorular

compose.override.yaml her zaman okunur mu?

-f vermediğinizde Compose, compose.yaml ile aynı düzeyde bulunan compose.override.yaml dosyasını kendiliğinden birleştirir. -f compose.yaml -f compose.prod.yaml gibi açıkça dosya verdiğinizde yalnızca verdiğiniz dosyalar kullanılır.

include mı extends mi kullanmalıyım?

Bir servisin ayarlarını başka servislerle paylaşmak istiyorsanız extends; başka bir ekibin veya bileşenin bütün Compose uygulamasını, kendi yollarını ve değişkenlerini koruyarak kullanmak istiyorsanız include uygundur.

Birleşik sonucu nasıl görürüm?

Aynı -f bayraklarıyla docker compose config çalıştırın. Yalnızca servis adları için --services, imajlar için --images bayrakları kısa ve okunaklı çıktı verir.

x- alanları container'a bir şey ekler mi?

Hayır. Compose x- ile başlayan alanları yok sayar; bu alanlar yalnızca anchor ile paylaşılacak blokları tutmak için kullanılır.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Docker öğrenme rehberi · Docker Compose rehberi · Compose değişken interpolasyonu · Compose secrets ve configs · Compose deploy ve kaynak limitleri