SSD Nodes Learn RAM 8GB — $66/tahun
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-01

Docker Compose: Gabungkan Banyak File dengan Benar

Pelajari cara compose.override.yaml dimuat otomatis, urutan file yang menentukan hasil merge, jebakan ports yang tetap membuka port, serta include untuk dev dan prod.

Cara Compose menangani lebih dari satu file

Docker Compose dapat membangun satu proyek dari beberapa file. Compose membacanya sesuai urutan file yang diterima, lalu menggabungkannya menjadi satu model. Dengan demikian, file berikutnya menentukan nilai yang berkonflik. Ada dua mekanisme untuk melakukannya dari baris perintah: file override yang dimuat Compose secara otomatis dan flag -f yang Anda teruskan secara manual. Mekanisme ketiga berada di dalam file itu sendiri, yaitu elemen include. Mekanisme ini bekerja secara berbeda dari kedua mekanisme sebelumnya.

Penggabungan bukan sekadar penimpaan biasa. Mapping digabungkan berdasarkan setiap kunci, sequence ditambahkan, dan sejumlah kecil field diganti seluruhnya. Perbedaan ini menimbulkan kejutan. Daftar ports adalah bagian yang paling sering menyebabkan masalah.

Semua penjelasan berikut mengasumsikan Compose v2, yaitu plugin docker compose, bukan skrip docker-compose yang lama. Jalankan docker compose version untuk memeriksanya. Jika Anda belum menulis file Compose, mulai dengan panduan dasar Docker Compose lalu kembali ke sini.

File override yang dimuat Compose tanpa perlu ditentukan

Jalankan docker compose up tanpa flag -f. Compose akan mencari compose.yaml atau docker-compose.yaml di direktori kerja, lalu di direktori induknya. Jika file override berada di samping file dasar, Compose akan memuatnya setelah file dasar secara otomatis.

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

Jika kedua file tersedia, hasilnya sama seperti memasukkan keduanya secara manual.

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

Nama yang dikenali Compose adalah compose.override.yaml, compose.override.yml, serta docker-compose.override.yml dan docker-compose.override.yaml yang lebih lama. Nama lain, misalnya compose.dev.yaml, hanya dimuat jika Anda menentukannya dengan -f.

Saat Anda memberikan satu -f, pemuatan otomatis berhenti. docker compose -f compose.yaml up hanya membaca file tersebut dan mengabaikan file override. Inilah perilaku yang menjadi dasar pola dev dan prod di bagian panduan berikutnya.

Pada server, perilaku ini dapat menimbulkan masalah. File override yang tertinggal di direktori deployment akan dimuat oleh setiap perintah docker compose tanpa argumen yang dijalankan dari direktori tersebut, termasuk perintah yang dijalankan oleh tugas cron Anda. Akibatnya, stack produksi dapat melakukan bind mount pada direktori sumber yang tidak dimaksudkan untuk dirilis. Jalankan docker compose config setelah setiap deployment, lalu periksa hasilnya.

Urutan dengan -f dan lokasi penyelesaian jalur relatif

Compose membangun konfigurasi sesuai urutan file yang Anda berikan. File berikutnya menimpa dan menambahkan konfigurasi sebelumnya. Dari kiri ke kanan, file terakhir menjadi acuan.

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

Setiap perintah dalam project tersebut memerlukan daftar file yang sama. Jika Anda menjalankan up dengan dua file dan logs dengan satu file, Anda berinteraksi dengan model gabungan yang berbeda. Ini dapat menyebabkan Compose menyatakan bahwa suatu service tidak ada. Tetapkan daftar tersebut sekali menggunakan variabel lingkungan COMPOSE_FILE.

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

Pemisahnya adalah : di Linux, dan COMPOSE_PATH_SEPARATOR mengubahnya. COMPOSE_FILE juga dapat ditempatkan dalam file .env project, sehingga menjadi bagian dari checkout, bukan bagian dari riwayat shell. Nilai yang ditetapkan secara eksplisit pada baris perintah mengalahkan variabel lingkungan.

Berikut aturan yang dapat merusak bind mount. Saat menggunakan beberapa file dengan -f, semua jalur relatif dalam semua file tersebut diselesaikan berdasarkan direktori file pertama, bukan berdasarkan file yang memuat jalur tersebut. Tulis ./data:/var/lib/postgresql/data di dalam deploy/prod/compose.prod.yaml, dan Compose tetap mencari ./data di sebelah file dasar. Docker kemudian membuat direktori kosong pada jalur yang keliru tersebut, lalu container dimulai tanpa isi di dalamnya. Kondisi ini terlihat seperti kehilangan data, tetapi sebenarnya bukan. Teruskan --project-directory untuk menetapkan jalur dasar sendiri, atau gunakan include, yang menyelesaikan setiap file berdasarkan direktorinya sendiri.

Nama project berasal dari direktori dasar yang sama. Karena itu, mengubah file yang menjadi file pertama dapat mengganti nama project. Project yang berganti nama akan memiliki nama container dan nama volume baru. Volume lama tetap berada di disk dengan nama lama. Tetapkan nama tersebut menggunakan name: tingkat teratas dalam file dasar.

name: myapp

Kolom mana yang digabungkan dan mana yang diganti

Compose menggabungkan berdasarkan jenis nilai, bukan nama kolom.

  • Kolom bernilai tunggal diganti. image, command, entrypoint, dan mem_limit langsung menggunakan nilai yang lebih baru. Anda tidak dapat menambahkan satu argumen ke command, karena penggantian menulis ulang seluruh baris.
  • Pemetaan digabungkan berdasarkan setiap kunci. environment, labels, volumes, dan devices mempertahankan semua kunci dari kedua file. File yang lebih baru menang untuk setiap kunci yang terdapat di kedua file. Untuk environment dan labels, kuncinya adalah nama variabel atau label. Untuk volumes dan devices, kuncinya adalah path container.
  • Urutan ditambahkan. dns, dns_search, expose, tmpfs, dan external_links digabungkan. Base yang berisi expose: ["3000"] jika digabungkan dengan override yang berisi ["4000", "5000"] akan menghasilkan ["3000", "4000", "5000"].

Empat urutan memiliki kunci identitas. Karena itu, entri yang cocok berdasarkan kunci tersebut digabungkan, bukan ditambahkan. volumes, secrets, dan configs dicocokkan berdasarkan target. ports dicocokkan berdasarkan kombinasi ip, target, published, dan protocol.

Baca kembali aturan ports tersebut, karena bagian ini sering menimbulkan kesalahan. Dua entri port dianggap sebagai entri yang sama hanya jika keempat bagian tersebut cocok. Jika salah satunya berubah, Compose menganggapnya sebagai port kedua yang tidak berkaitan. Karena itu, keduanya dipertahankan.

Mengapa port Anda masih dipublikasikan setelah override

File dasar yang memublikasikan layanan pada setiap antarmuka:

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

Override yang ditulis untuk mengikatnya hanya ke localhost karena reverse proxy akan ditempatkan di depannya:

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

Periksa hasilnya sebelum menganggap konfigurasi tersebut berhasil.

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

Kedua entri muncul dalam output. Bagian ip berbeda, yaitu 0.0.0.0 dan 127.0.0.1, sehingga keduanya dianggap sebagai port yang berbeda dalam proses penggabungan. Binding publik yang ingin Anda hapus masih ada dalam model konfigurasi. Hal ini lebih penting pada Docker dibandingkan lingkungan lain karena port yang dipublikasikan ditulis ke iptables sebelum aturan firewall Anda. Mekanismenya dibahas dalam mengapa port Docker yang dipublikasikan melewati ufw.

Ada dua solusi. Solusi eksplisit menggunakan tag !override, yang mengganti seluruh atribut dan melewati aturan penggabungan:

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

!override memerlukan Compose v2.24.4 atau yang lebih baru. Solusi portabel tidak memerlukan tag: jangan sertakan ports dalam file dasar dan deklarasikan hanya dalam file khusus lingkungan. Jika tidak ada yang digabungkan, tidak ada yang dapat bocor. Pola ini digunakan dalam contoh lengkap di bawah.

Menghapus nilai yang ditetapkan file dasar

!reset menghapus atribut dan mengembalikannya ke nilai default atau null. Perintah ini memerlukan sebuah nilai, tetapi mengabaikannya. Jadi, tulis nilai yang valid dan kosong.

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

!reset memerlukan Compose v2.24 atau yang lebih baru. Gunakan perintah ini jika Anda tidak dapat mengedit file dasar, misalnya fragmen dari vendor yang Anda sertakan.

include, untuk stack yang dirakit dari beberapa bagian

include menarik aplikasi Compose lain ke dalam model Anda. Elemen ini berada di tingkat teratas, bukan flag.

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

Setiap path dalam include dimuat sebagai model aplikasi Compose tersendiri, dengan direktori proyeknya sendiri. Karena itu, path relatif di dalam file tersebut mengacu ke direktori file itu sendiri. Inilah perbedaan utama dari -f, dan alasan include merupakan alat yang tepat ketika fragmen berada di folder lain atau repositori lain.

Bentuk panjangnya memiliki subopsi.

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

path menerima daftar, dan file-file tersebut digabungkan sesuai aturan normal sebelum hasilnya ditambahkan ke model Anda. project_directory menetapkan path dasar yang digunakan untuk menyelesaikan path relatif dalam file yang disertakan. env_file memberi file yang disertakan variabelnya sendiri untuk interpolasi. Dengan demikian, fragmen bersama tidak membaca .env milik proyek Anda secara diam-diam. include memerlukan Compose v2.20.0 atau yang lebih baru.

Nama resource yang sama antara file Anda dan file yang disertakan dilaporkan sebagai error, bukan digabungkan secara diam-diam. Hal ini memang disengaja. Untuk mengubah deklarasi dari file yang disertakan, letakkan perubahan tersebut dalam compose.override.yaml. Override diterapkan pada model yang telah dirakit, sehingga dapat mengubah resource yang disertakan tanpa menimbulkan konflik.

Singkatnya: include menyusun aplikasi terpisah, sedangkan -f menambahkan lapisan konfigurasi ke satu aplikasi.

Pemisahan dev dan prod pada satu VPS

Berikut seluruh pola tersebut dalam tiga file. File dasar menyatakan konfigurasi yang berlaku di semua lingkungan dan sama sekali tidak memublikasikan port.

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:

Kondisi depends_on membuat app menunggu database yang merespons, bukan container yang hanya ada, seperti dijelaskan dalam healthcheck dan kondisi depends_on. POSTGRES_PASSWORD diinterpolasikan dari file proyek .env, yang tidak boleh dimasukkan ke git. Lihat file env dan secret Compose untuk varian yang lebih aman.

Berikutnya, compose.override.yaml, yang dimuat Compose secara otomatis. Ini adalah file untuk 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"

Di laptop, docker compose up tanpa argumen menggabungkan kedua file tersebut. command menggantikan nilai default image karena nilainya tunggal. LOG_LEVEL menggantikan info karena environment digabungkan berdasarkan key. Bind mount dan dua port yang dipublikasikan hanya merupakan penambahan, sedangkan port database diikat ke localhost agar laptop pada jaringan bersama tidak mengekspos PostgreSQL ke jaringan lokal.

Terakhir, compose.prod.yaml. Namanya bukan nama yang dicari Compose, sehingga file ini tidak pernah dimuat secara tidak sengaja.

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

Di VPS, Anda menyebutkan kedua file tersebut, dan tindakan penyebutan itu secara tepat mengecualikan 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 harus menampilkan kedua service sebagai running, dengan db yang menampilkan (healthy). Karena Anda meneruskan -f, compose.override.yaml tidak dibaca. Dengan demikian, perintah dev, source bind mount, dan port publik 3000 tidak dapat masuk ke production, meskipun file tersebut berada di direktori yang sama. Port 8000 hanya tersedia pada localhost dan siap digunakan oleh proxy. Lihat menjalankan beberapa app di balik Traefik saat Anda menambahkan service kedua.

Atur COMPOSE_FILE=compose.yaml:compose.prod.yaml di .env server, lalu perintah lainnya kembali menggunakan docker compose logs -f app biasa.

Baca model gabungan sebelum melakukan deployment

docker compose config mencetak model yang telah digabungkan dan diinterpolasi sepenuhnya. Ini bukan pratinjau. Model ini merupakan input persis yang akan diproses Compose. Jadi, jika output berbeda dari perkiraan Anda, output tersebut yang benar.

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 membiarkan ${VAR} tetap tidak diperluas. Gunakan perintah ini sebelum menempelkan output di mana pun, karena config biasa mencetak setiap secret yang telah di-resolve dalam teks biasa. --services hanya mencantumkan nama service. Ini merupakan cara cepat untuk memastikan bahwa include memuat service yang Anda harapkan.

Mode kegagalan dan hal yang akan Anda lihat

no configuration file provided: not found. Compose tidak menemukan apa pun untuk dibaca. Anda berada di luar direktori proyek, atau COMPOSE_FILE menentukan jalur yang tidak ada. Compose mencari file dasar default di direktori induk, tetapi tidak mencari di lokasi mana pun untuk file yang Anda tentukan sendiri.

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. Interpolasi diselesaikan berdasarkan file proyek .env dan lingkungan shell. Direktori proyek di sini adalah direktori tempat file -f pertama berada. Jika deployment dijalankan dari direktori yang berbeda dari direktori tempat .env berada, Anda akan menerima peringatan ini, lalu database tidak akan menerima koneksi apa pun.

Edit override Anda tidak muncul di docker compose config. Anda mungkin meneruskan -f, yang menonaktifkan pemuatan override otomatis, atau Compose menemukan compose.yaml di direktori induk dan file override Anda tidak berada di sebelahnya. Menjalankan docker compose config tanpa argumen lain akan menunjukkan model yang sebenarnya sedang dibuat Compose.

Bind mount kosong dan Docker membuat direktori yang tidak Anda minta. Jalur relatif diselesaikan berdasarkan direktori file pertama. Perbaiki jalurnya, teruskan --project-directory, atau pindahkan fragmen tersebut ke belakang include.

Container kembali dengan nama baru dan volume tampak kosong. Nama proyek berubah karena nama proyek mengikuti direktori file pertama. Tambahkan name: tingkat teratas ke file dasar agar penamaan tidak lagi berubah. Volume lama masih ada dengan prefix lama, dan docker volume ls akan menampilkannya.

Port yang Anda hapus dalam override masih terbuka. Penggabungan ports menambahkan entri, bukan menggantinya. Konfirmasikan dengan docker compose config, lalu gunakan !override atau pindahkan ports dari file dasar.

FAQ

Apakah Compose memuat compose.override.yaml secara otomatis?

Ya, saat Anda menjalankan docker compose tanpa flag -f. Compose mencari compose.yaml atau docker-compose.yaml di direktori kerja dan direktori induknya. Jika file override berada di lokasi yang sama, file tersebut dimuat setelahnya. Nama yang dikenali adalah compose.override.yaml, compose.override.yml, docker-compose.override.yml, dan docker-compose.override.yaml. Pemberian -f apa pun menonaktifkan perilaku ini, sehingga docker compose -f compose.yaml up hanya membaca satu file.

Dalam urutan apa beberapa file -f digabungkan?

Dari kiri ke kanan. Compose membangun konfigurasi sesuai urutan file yang Anda berikan. Setiap file mengganti dan menambahkan konfigurasi dari file sebelumnya. Karena itu, file terakhir pada baris perintah memenangkan setiap konflik. Daftar yang sama harus digunakan untuk setiap perintah dalam project tersebut. Untuk itulah COMPOSE_FILE=compose.yaml:compose.prod.yaml digunakan.

Mengapa port saya masih dipublikasikan setelah saya menggantinya?

Karena entri ports diidentifikasi berdasarkan seluruh rangkaian ip, target, published, dan protocol. Penggantian 127.0.0.1:8080:80 terhadap konfigurasi dasar 8080:80 berbeda pada bagian ip. Karena itu, Compose memperlakukannya sebagai port kedua dan mempertahankan keduanya. Jalankan docker compose config untuk melihat kedua entri tersebut. Gunakan ports: !override pada Compose v2.24.4 atau yang lebih baru, atau jangan cantumkan ports dalam file dasar agar tidak ada nilai yang dapat digabungkan.

Apa perbedaan antara include dan -f?

-f melapiskan beberapa file ke dalam satu aplikasi. Semua path relatif dalam setiap file ditafsirkan berdasarkan direktori file pertama. include menyertakan aplikasi Compose terpisah. Setiap path yang disertakan tetap menggunakan direktori project-nya sendiri, sehingga path relatifnya ditafsirkan berdasarkan direktori tersebut. Gunakan -f untuk lapisan environment pada stack Anda sendiri, dan include untuk fragmen yang dikelola di lokasi lain. include memerlukan Compose v2.20.0 atau yang lebih baru.

Bagaimana cara menghapus nilai yang ditetapkan file dasar?

Gunakan tag !reset pada Compose v2.24 atau yang lebih baru. Tulis ports: !reset [] atau MY_VAR: !reset null dalam file pengganti. Atribut tersebut akan kembali ke nilai default atau menjadi null. Nilai yang diberikan kepada tag wajib ada, tetapi diabaikan. Jika Anda ingin mengganti atribut, bukan menghapusnya, !override dapat digunakan. Fitur ini memerlukan v2.24.4 atau yang lebih baru.