SSD Nodes Learn 8GB RAM — $66/साल
गाइड Matt Connorलेखक: Matt Connor · अपडेट किया गया: 2026-08-01

Docker Compose में कई फ़ाइलें कैसे मर्ज होती हैं

जानें compose.override.yaml अपने आप कैसे लोड होती है, फ़ाइल क्रम में क्या मर्ज होता है, ports से पोर्ट खुला क्यों रहता है और dev-prod split के लिए include कैसे उपयोग करें।

एक से अधिक फ़ाइलों के साथ Compose क्या करता है

Docker Compose कई फ़ाइलों से एक प्रोजेक्ट बना सकता है। यह फ़ाइलों को प्राप्त होने वाले क्रम में पढ़ता है और उन्हें एकल मॉडल में मर्ज करता है। इसलिए बाद वाली फ़ाइल में मौजूद कोई भी विरोधी मान प्रभावी होता है। कमांड लाइन से ऐसा करने के लिए 2 तंत्र हैं: override फ़ाइल, जिसे Compose स्वयं लोड करता है, और -f फ़्लैग, जिसे आप स्वयं पास करते हैं। तीसरा तंत्र स्वयं फ़ाइल के अंदर मौजूद include एलिमेंट है। यह दोनों से अलग तरीके से काम करता है।

मर्ज केवल साधारण overwrite नहीं है। Mappings कुंजी-दर-कुंजी मर्ज होते हैं, sequences में आइटम जुड़ते हैं, और कुछ फ़ील्ड पूरी तरह replace हो जाते हैं। यही अंतर अप्रत्याशित परिणामों का कारण बनता है। ports सूची लगभग सभी के लिए समस्या पैदा करती है।

नीचे दी गई सभी जानकारी Compose v2 पर आधारित है। इसमें पुराने docker-compose script के बजाय docker compose plugin का उपयोग किया गया है। जाँच करने के लिए docker compose version चलाएँ। यदि आपने अभी तक Compose फ़ाइल नहीं लिखी है, तो Docker Compose की मूल बातें गाइड से शुरू करें और फिर यहाँ लौटें।

वह override file जिसे Compose बिना बताए लोड करता है

-f flag के बिना docker compose up चलाने पर Compose working directory, फिर उसकी parent directories में compose.yaml या docker-compose.yaml खोजता है। यदि override file base file के साथ रखी हो, तो Compose उसे अपने-आप दूसरी file के रूप में लोड करता है।

ls compose.yaml compose.override.yaml
docker compose up -d

दोनों files मौजूद हों, तो इसका परिणाम उन्हें हाथ से लिखकर देने जैसा ही होता है।

docker compose -f compose.yaml -f compose.override.yaml up -d

Compose जिन नामों को पहचानता है, वे compose.override.yaml, compose.override.yml और पुराने docker-compose.override.yml तथा docker-compose.override.yaml हैं। कोई दूसरा नाम, जैसे compose.dev.yaml, तभी लोड होता है जब आप उसे -f के साथ नाम से देते हैं।

जैसे ही आप कोई -f देते हैं, automatic loading बंद हो जाता है। docker compose -f compose.yaml up केवल उसी एक file को पढ़ता है और override को अनदेखा करता है। इस guide में बाद में दिया गया dev और prod pattern इसी गुण पर आधारित है।

Server पर इसका प्रभाव दोनों तरह का हो सकता है। deploy directory में रखी override file उस directory से चलाए गए हर bare docker compose command के साथ लोड होती है। इसमें cron job द्वारा चलाया गया command भी शामिल है। इसी कारण production stack अनजाने में source directory को bind-mount कर सकता है। हर deploy के बाद docker compose config चलाएँ और उसके output को पढ़ें।

-f के साथ क्रम और relative paths कहाँ resolve होते हैं

Compose configuration को उसी क्रम में बनाता है, जिस क्रम में आप files देते हैं। बाद की files अपनी पिछली files को override करती हैं और उनमें नई settings जोड़ती हैं। क्रम बाएँ से दाएँ होता है और अंतिम setting प्रभावी होती है।

docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -d

उस project में हर command के लिए यही file list आवश्यक है। up को दो files के साथ और logs को एक file के साथ चलाने पर आप अलग merged model के साथ काम कर रहे होते हैं। इससे ऐसी service मिलने की संभावना बढ़ जाती है, जिसके बारे में Compose कहता है कि वह मौजूद नहीं है। इसके बजाय COMPOSE_FILE environment variable से list को एक बार set करें।

export COMPOSE_FILE=compose.yaml:compose.prod.yaml
docker compose config
docker compose up -d

Linux पर separator : है और COMPOSE_PATH_SEPARATOR इसे बदलता है। COMPOSE_FILE project की .env file में भी रखा जा सकता है। इससे यह आपके shell history के बजाय checkout का हिस्सा बन जाता है। Command line पर स्पष्ट रूप से set की गई कोई भी value environment variable की value पर प्राथमिकता रखती है।

अब वह नियम देखें, जो bind mounts को प्रभावित करता है। जब आप -f के साथ multiple files का उपयोग करते हैं, तो उन सभी files में दिए गए relative paths पहली file की directory के आधार पर resolve होते हैं। वे उस file की directory के आधार पर resolve नहीं होते, जिसमें paths लिखे हैं। deploy/prod/compose.prod.yaml के अंदर ./data:/var/lib/postgresql/data लिखने पर भी Compose ./data को base file के पास खोजता है। इसके बाद Docker उस गलत path पर empty directory बना देता है और container उसके अंदर कुछ भी न होने के साथ start होता है। यह data loss जैसा दिखता है, लेकिन data loss नहीं होता। Base path स्वयं set करने के लिए --project-directory pass करें। या include का उपयोग करें, जो प्रत्येक file के paths को उसकी अपनी directory के आधार पर resolve करता है।

Project name भी इसी base directory से आता है। इसलिए पहली file बदलने पर project का नाम बदल सकता है। Renamed project का अर्थ नए container names और नए volume names हैं। पुराना volume पुराने name के अंतर्गत disk पर बना रहता है। इसके बजाय base file में top-level name: से project name को स्थिर करें।

name: myapp

कौन-से फ़ील्ड मर्ज होते हैं और कौन-से बदले जाते हैं

Compose फ़ील्ड के नाम के आधार पर नहीं, बल्कि मान के प्रकार के आधार पर मर्ज करता है।

  • एकल-मान वाले फ़ील्ड बदले जाते हैं। image, command, entrypoint और mem_limit बाद वाले मान को सीधे स्वीकार करते हैं। आप किसी command में एक और आर्ग्युमेंट नहीं जोड़ सकते, क्योंकि ओवरराइड पूरी लाइन को फिर से लिख देता है।
  • मैपिंग कुंजी-दर-कुंजी मर्ज होती हैं। environment, labels, volumes और devices दोनों फ़ाइलों की सभी कुंजियाँ रखती हैं। दोनों में मौजूद किसी कुंजी पर बाद वाली फ़ाइल का मान लागू होता है। environment और labels के लिए कुंजी वेरिएबल या लेबल का नाम है। volumes और devices के लिए कुंजी कंटेनर पथ है।
  • सीक्वेंस जोड़े जाते हैं। dns, dns_search, expose, tmpfs और external_links को संयोजित किया जाता है। expose: ["3000"] रखने वाले बेस को ["4000", "5000"] रखने वाले ओवरराइड के साथ मर्ज करने पर ["3000", "4000", "5000"] बनता है।

चार सीक्वेंस एक पहचान कुंजी रखते हैं। इसलिए उस कुंजी से मेल खाने वाली एंट्रियाँ जोड़ने के बजाय मर्ज होती हैं। volumes, secrets और configs का मिलान target के आधार पर होता है। ports का मिलान ip, target, published और protocol के संयोजन के आधार पर होता है।

उस ports नियम को दो बार पढ़ें, क्योंकि यही सामान्य गलती का कारण है। दो पोर्ट एंट्रियाँ तभी एक ही एंट्री मानी जाती हैं, जब उन चारों हिस्सों के मान समान हों। इनमें से किसी एक को बदलने पर Compose उसे दूसरी, असंबंधित पोर्ट एंट्री मानता है और दोनों को रखता है।

ओवरराइड के बाद भी आपका पोर्ट प्रकाशित क्यों है

एक base file, जो हर interface पर service प्रकाशित करती है:

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"

एक override, जो इसे केवल localhost से bind करने के लिए लिखा गया है, क्योंकि इसके सामने reverse proxy चलेगा:

services:
  web:
    ports:
      - "127.0.0.1:8080:80"

यह मानने से पहले परिणाम जाँचें कि यह काम कर गया है।

docker compose -f compose.yaml -f compose.prod.yaml config

आउटपुट में दोनों entries हैं। ip वाला भाग अलग है—0.0.0.0 बनाम 127.0.0.1। इसलिए merge के अनुसार ये दो अलग-अलग ports हैं, और जिस public binding को हटाने का आपने प्रयास किया था, वह अभी भी model में मौजूद है। Docker में इसका प्रभाव अन्य जगहों की तुलना में अधिक महत्वपूर्ण है, क्योंकि published port आपके firewall rules से पहले iptables में लिख दिया जाता है। इस mechanism का विवरण published Docker ports ufw को पार क्यों करते हैं में दिया गया है।

इसके दो समाधान हैं। स्पष्ट समाधान !override tag है। यह पूरे attribute को replace करता है और merge rules को छोड़ देता है:

services:
  web:
    ports: !override
      - "127.0.0.1:8080:80"

!override के लिए Compose v2.24.4 या बाद का संस्करण आवश्यक है। Portable समाधान में किसी tag की आवश्यकता नहीं है: ports को base file से पूरी तरह हटाएँ और इसे केवल environment-specific files में घोषित करें। Merge करने के लिए कुछ नहीं होगा, तो leak होने के लिए भी कुछ नहीं होगा। नीचे दिए गए worked example में यही pattern उपयोग किया गया है।

आधार फ़ाइल द्वारा सेट किया गया मान हटाना

!reset किसी attribute को हटाकर उसे उसके डिफ़ॉल्ट मान या null पर वापस सेट करता है। यह एक मान लेता है और उसे अनदेखा करता है, इसलिए कोई मान्य लेकिन खाली मान लिखें।

services:
  web:
    ports: !reset []
    environment:
      DEBUG: !reset null

!reset के लिए Compose v2.24 या नया संस्करण आवश्यक है। इसका उपयोग तब करें जब आधार फ़ाइल को संपादित करना आपके अधिकार में न हो, उदाहरण के लिए जब वह आपके द्वारा शामिल किया गया vendor fragment हो।

parts से assembled stacks के लिए include

include आपके model में एक अन्य Compose application को शामिल करता है। यह top-level element है, flag नहीं।

include:
  - path: ../commons/compose.yaml

include का प्रत्येक path अपने अलग Compose application model के रूप में load होता है। उसका अपना project directory होता है। इसलिए उस file के अंदर के relative paths उसी file की अपनी directory के आधार पर resolve होते हैं। यही -f से वास्तविक अंतर है। इसी कारण fragment किसी अन्य folder या repository में होने पर include सही tool है।

Long form में sub-options होते हैं।

include:
  - path:
      - ../monitoring/compose.yaml
      - ../monitoring/compose.vps.yaml
    project_directory: ../monitoring
    env_file: ../monitoring/.env

path एक list स्वीकार करता है। उन files को सामान्य rules के अनुसार एक साथ merge किया जाता है। इसके बाद परिणाम आपके model में शामिल होता है। project_directory included file में relative paths को resolve करने के लिए उपयोग होने वाला base path सेट करता है। env_file included file को interpolation के लिए अपने variables देता है। इससे shared fragment आपके project के .env को बिना स्पष्ट रूप से बताए पढ़ नहीं पाता। include के लिए Compose v2.20.0 या नया version आवश्यक है।

आपकी file और included file के बीच duplicate resource names मिलने पर उन्हें चुपचाप merge करने के बजाय error report किया जाता है। यह व्यवहार जानबूझकर है। Included file द्वारा घोषित किसी चीज़ को बदलने के लिए बदलाव compose.override.yaml में रखें। Override assembled model पर लागू होता है। इसलिए वह included resources में टकराव के बिना बदलाव कर सकता है।

संक्षेप में: include अलग-अलग applications को compose करता है, जबकि -f एक application पर configuration की परत जोड़ता है।

एक VPS पर dev और prod का विभाजन

यह पूरा पैटर्न तीन फ़ाइलों में है। base फ़ाइल हर जगह लागू होने वाली बातें घोषित करती है और कोई भी port publish नहीं करती।

name: myapp

services:
  app:
    image: ghcr.io/example/app:1.4.2
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
      LOG_LEVEL: info
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  db_data:

depends_on condition से app ऐसे database का इंतज़ार करता है जो response देता है, न कि केवल मौजूद container का। इसका विवरण healthchecks और depends_on conditions में है। POSTGRES_PASSWORD को project की .env फ़ाइल से interpolate किया जाता है। यह फ़ाइल कभी भी git में नहीं होनी चाहिए। अधिक सुरक्षित विकल्पों के लिए env files और Compose secrets देखें।

अब compose.override.yaml है, जिसे Compose अपने-आप load करता है। यह developer की फ़ाइल है।

services:
  app:
    build: .
    command: npm run dev
    environment:
      LOG_LEVEL: debug
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src

  db:
    ports:
      - "127.0.0.1:5432:5432"

laptop पर बिना अतिरिक्त विकल्प वाला docker compose up इन दोनों फ़ाइलों को merge करता है। command image default को replace करता है, क्योंकि इसकी केवल एक value होती है। LOG_LEVEL, info को replace करता है, क्योंकि environment key के आधार पर merge करता है। bind mount और प्रकाशित किए गए दोनों ports केवल अतिरिक्त entries हैं। database port localhost से bind है, इसलिए shared network पर मौजूद laptop PostgreSQL को पूरे local network में उपलब्ध नहीं कराता।

अंत में compose.prod.yaml है। इसका नाम उन नामों में नहीं है जिन्हें Compose खोजता है, इसलिए यह गलती से कभी load नहीं होती।

services:
  app:
    ports:
      - "127.0.0.1:8000:3000"
    deploy:
      resources:
        limits:
          memory: 512M

VPS पर आप दोनों फ़ाइलों के नाम देते हैं। यही नाम देना override को बाहर रखने का तरीका है।

docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.prod.yaml ps

ps में दोनों services के running होने की सूची होनी चाहिए और db में (healthy) दिखना चाहिए। आपने -f दिया है, इसलिए compose.override.yaml पढ़ी ही नहीं गई। इस कारण dev command, source bind mount और public port 3000 production तक नहीं पहुँच सकते, भले ही फ़ाइल उसी directory में मौजूद हो। Port 8000 केवल localhost पर है और proxy के लिए तैयार है। दूसरा service जोड़ने पर Traefik के पीछे कई apps चलाना देखें।

Server की .env में COMPOSE_FILE=compose.yaml:compose.prod.yaml सेट करें। इसके बाद आपके बाकी commands फिर से सामान्य docker compose logs -f app बन जाएंगे।

परिनियोजन से पहले मर्ज किया गया मॉडल पढ़ें

docker compose config पूरी तरह मर्ज किए गए और इंटरपोलेट किए गए मॉडल को प्रिंट करता है। यह पूर्वावलोकन नहीं है। यही वह सटीक इनपुट है जिस पर Compose कार्रवाई करेगा। इसलिए जब आउटपुट आपकी अपेक्षा से अलग हो, तो आउटपुट सही है।

docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml config --no-interpolate
docker compose -f compose.yaml -f compose.prod.yaml config --services

--no-interpolate ${VAR} को अनएक्सपैंडेड रखता है। आउटपुट को कहीं भी पेस्ट करने से पहले इसका उपयोग करें, क्योंकि साधारण config हर resolved secret को स्पष्ट टेक्स्ट में प्रिंट करता है। --services केवल service names की सूची देता है। यह तुरंत पुष्टि करने का तरीका है कि किसी include ने आपकी अपेक्षा के अनुसार कॉन्फ़िगरेशन शामिल किया है।

विफलता की स्थितियाँ और आपको क्या दिखाई देगा

no configuration file provided: not found Compose को पढ़ने के लिए कुछ नहीं मिला। आप project directory के बाहर हैं, या COMPOSE_FILE में ऐसा path है जो मौजूद नहीं है। Compose default base file के लिए parent directories में खोज करता है, लेकिन आपके द्वारा स्वयं बताए गए file के लिए कहीं और खोज नहीं करता।

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. Interpolation project की .env file और shell environment के आधार पर होता है। यहाँ project directory पहली -f file की directory है। .env वाली directory से अलग directory में deployment करने पर यह warning दिखाई देती है और उसके बाद ऐसा database मिलता है जो हर connection अस्वीकार करता है।

आपका override edit docker compose config में दिखाई नहीं देता। या तो आपने -f पास किया है, जिससे automatic override loading बंद हो जाती है, या Compose को parent directory में compose.yaml मिला और आपकी override file उसके पास नहीं है। बिना किसी अन्य argument के docker compose config चलाने पर पता चलता है कि Compose वास्तव में कौन-सा model बना रहा है।

Bind mount खाली है और Docker ने ऐसी directory बना दी है जिसकी आपने माँग नहीं की थी। Relative path पहली file की directory के आधार पर resolve हुआ। Path ठीक करें, --project-directory पास करें, या fragment को include के पीछे ले जाएँ।

Containers नए नामों के साथ वापस आते हैं और volume खाली दिखाई देता है। Project name बदल गया, क्योंकि project name पहली file की directory पर निर्भर करता है। Base file में top-level name: जोड़ें, ताकि नाम बदलना बंद हो जाए। पुराना volume अभी भी पुराने prefix के अंतर्गत मौजूद है और docker volume ls उसे दिखाएगा।

Override में हटाया गया port अभी भी खुला है। ports merge ने replace करने के बजाय append किया। docker compose config से पुष्टि करें, फिर !override का उपयोग करें या ports को base file से बाहर ले जाएँ।

FAQ

क्या Compose, compose.override.yaml को अपने-आप लोड करता है?

हाँ, जब आप docker compose को बिना -f flag के चलाते हैं। Compose, working directory और उसकी parent directories में compose.yaml या docker-compose.yaml खोजता है। यदि override file उसके साथ मौजूद हो, तो वह file दूसरे क्रम पर लोड होती है। मान्य नाम compose.override.yaml, compose.override.yml, docker-compose.override.yml और docker-compose.override.yaml हैं। कोई भी -f देने पर यह व्यवहार disabled हो जाता है। इसलिए docker compose -f compose.yaml up केवल एक file पढ़ता है।

कई -f files किस क्रम में merge होती हैं?

बाएँ से दाएँ। Compose, आपके द्वारा files देने के क्रम में configuration बनाता है। प्रत्येक file, उससे पहले वाली files को override और extend करती है। इसलिए line में सबसे अंत वाली file किसी भी conflict में प्रभावी होती है। उस project के हर command में यही list उपयोग करनी होगी। इसी के लिए COMPOSE_FILE=compose.yaml:compose.prod.yaml है।

Override करने के बाद भी मेरा port published क्यों है?

क्योंकि ports entries की पहचान ip, target, published और protocol के पूरे set से होती है। 8080:80 के base पर 127.0.0.1:8080:80 का override ip भाग में अलग है। इसलिए Compose इसे दूसरे port के रूप में मानता है और दोनों को बनाए रखता है। docker compose config चलाने पर आपको दोनों entries दिखाई देंगी। Compose v2.24.4 या नए version पर ports: !override का उपयोग करें। वैकल्पिक रूप से, base file में ports न रखें, ताकि merge करने के लिए कुछ न हो।

include और -f में क्या अंतर है?

-f कई files को एक application पर layer करता है। हर file में relative path, पहली file की directory के आधार पर resolve होता है। include एक अलग Compose application को शामिल करता है। प्रत्येक included path अपनी project directory बनाए रखता है, इसलिए उसके relative paths उसी के आधार पर resolve होते हैं। अपने stack की environment layers के लिए -f का उपयोग करें। कहीं और maintain किए गए fragment के लिए include का उपयोग करें। include के लिए Compose v2.20.0 या नया version आवश्यक है।

Base file द्वारा सेट value को कैसे हटाएँ?

Compose v2.24 या नए version पर !reset tag का उपयोग करें। Overriding file में ports: !reset [] या MY_VAR: !reset null लिखें। इससे attribute अपने default या null पर लौट जाता है। Tag को दी गई value आवश्यक है, लेकिन उसे ignore किया जाता है। यदि attribute को clear करने के बजाय replace करना हो, तो !override ऐसा करता है। इसके लिए v2.24.4 या नया version आवश्यक है।