Docker Compose: Gabungkan Banyak File dengan Benar
Pahami cara compose.override.yaml dimuat sendiri, urutan file yang digabungkan, jebakan ports yang membuat port tetap terbuka, serta include untuk dev dan prod.
Yang dilakukan Compose terhadap lebih dari satu file
Docker Compose dapat membangun satu project dari beberapa file. Compose membacanya sesuai urutan file diterima, lalu menggabungkannya menjadi satu model. Karena itu, file berikutnya akan mengutamakan nilainya jika terjadi konflik. Ada dua mekanisme untuk melakukan ini dari command line: file override yang dimuat Compose secara otomatis dan flag -f yang Anda berikan secara manual. Mekanisme ketiga berada di dalam file itu sendiri, yaitu elemen include, dan cara kerjanya berbeda dari keduanya.
Penggabungan bukan sekadar penimpaan. Mapping digabungkan berdasarkan setiap key, sequence ditambahkan, sedangkan sejumlah kecil field diganti seluruhnya. Perbedaan ini dapat menimbulkan hasil yang tidak terduga, dan daftar ports adalah bagian yang paling sering menyebabkan masalah.
Semua penjelasan berikut mengasumsikan Compose v2, yaitu plugin docker compose, bukan script lama docker-compose. 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 dan direktori induknya. Jika file override berada di sebelah file dasar, Compose akan memuatnya secara otomatis setelah file dasar.
ls compose.yaml compose.override.yaml
docker compose up -dJika kedua file tersedia, hasilnya sama seperti menuliskannya secara manual.
docker compose -f compose.yaml -f compose.override.yaml up -dNama 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 meneruskan 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 dalam panduan ini.
Di server, perilaku ini dapat menimbulkan dampak yang berlawanan. 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 cron. Akibatnya, stack produksi dapat melakukan bind mount ke direktori source yang tidak dimaksudkan untuk dikirimkan. Jalankan docker compose config setelah setiap deployment dan baca hasilnya. Untuk deployment tanpa pengawasan, pemeriksaan ini hanya berguna jika ada mekanisme yang memberi tahu Anda saat terjadi kesalahan. Tugas tersebut dapat ditangani oleh push channel seperti server ntfy yang di-host sendiri, yang dapat menerima posting dari cron atau unit systemd OnFailure.
Urutan dengan -f dan resolusi path relatif
Compose menyusun konfigurasi sesuai urutan file yang Anda berikan. File berikutnya menimpa dan menambahkan konfigurasi dari file sebelumnya. Dari kiri ke kanan, konfigurasi terakhir yang berlaku.
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -dSetiap perintah dalam project tersebut harus menggunakan daftar file yang sama. Jalankan up dengan dua file dan logs dengan satu file, maka Anda berinteraksi dengan model gabungan yang berbeda. Ini adalah cara cepat membuat Compose menyatakan bahwa suatu service tidak ada. Dampaknya lebih besar pada stack yang menjalankan upgrade sebagai perintah satu kali, seperti langkah migrasi database pada helpdesk Chatwoot yang di-host sendiri. Dalam kasus tersebut, docker compose run yang dijalankan dengan daftar file yang salah dapat secara diam-diam menargetkan model yang berbeda dari model yang sedang digunakan oleh service Anda. Tetapkan daftar tersebut satu kali menggunakan variabel lingkungan COMPOSE_FILE.
export COMPOSE_FILE=compose.yaml:compose.prod.yaml
docker compose config
docker compose up -dPemisahnya adalah : di Linux, sedangkan COMPOSE_PATH_SEPARATOR mengubahnya. COMPOSE_FILE juga dapat disimpan dalam file .env project. Dengan begitu, pengaturan tersebut menjadi bagian dari checkout, bukan bagian dari riwayat shell Anda. Apa pun yang ditetapkan secara eksplisit pada command line akan mengesampingkan variabel lingkungan.
Berikutnya, aturan yang sering menyebabkan masalah pada bind mount. Saat menggunakan beberapa file dengan -f, semua path relatif dalam semua file tersebut di-resolve berdasarkan direktori file pertama, bukan berdasarkan file yang memuat path 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 path yang salah tersebut. Container berjalan tanpa isi di dalamnya. Kondisi ini terlihat seperti kehilangan data, padahal bukan. Berikan --project-directory untuk menetapkan base path secara manual, atau gunakan include, yang me-resolve 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 menggunakan nama container dan nama volume baru, sedangkan volume lama tetap tersimpan di disk dengan nama lama. Tetapkan nama tersebut secara eksplisit menggunakan name: tingkat teratas dalam file dasar.
name: myappField mana yang digabungkan dan mana yang digantikan
Compose menggabungkan berdasarkan tipe nilai, bukan berdasarkan nama field.
- Field dengan satu nilai akan digantikan.
image,command,entrypoint, danmem_limitlangsung menggunakan nilai yang lebih baru. Anda tidak dapat menambahkan satu argumen kecommand, karena override menulis ulang seluruh baris. - Mapping digabungkan berdasarkan setiap key.
environment,labels,volumes, dandevicesmempertahankan semua key dari kedua file. File yang lebih baru menjadi sumber nilai untuk key yang terdapat di kedua file. Untukenvironmentdanlabels, key-nya adalah nama variabel atau label. Untukvolumesdandevices, key-nya adalah path container. - Sequence ditambahkan.
dns,dns_search,expose,tmpfs, danexternal_linksdigabungkan secara berurutan. Base yang berisiexpose: ["3000"]jika digabungkan dengan override yang berisi["4000", "5000"]akan menghasilkan["3000", "4000", "5000"].
Empat sequence memiliki key identitas. Karena itu, entri yang memiliki key sama akan digabungkan, bukan ditambahkan. volumes, secrets, dan configs dicocokkan berdasarkan target. ports dicocokkan berdasarkan gabungan ip, target, published, dan protocol.
Baca kembali aturan ports dua kali 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 terkait sehingga keduanya tetap dipertahankan.
Mengapa port masih dipublikasikan setelah override
File dasar yang memublikasikan service pada semua interface:
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 configKedua entri muncul dalam output. Bagian ip berbeda, yaitu 0.0.0.0 dibandingkan dengan 127.0.0.1, sehingga keduanya dianggap sebagai dua port berbeda dalam proses merge. 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. Mekanisme ini dibahas dalam mengapa port Docker yang dipublikasikan melewati ufw.
Ada dua solusi. Solusi eksplisit menggunakan tag !override, yang mengganti seluruh atribut dan melewati aturan merge:
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 apa pun: jangan masukkan ports ke file dasar, dan deklarasikan hanya di file khusus lingkungan. Jika tidak ada yang perlu 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 menerima sebuah nilai lalu mengabaikannya, jadi tuliskan 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 ketika Anda tidak dapat mengedit file dasar, misalnya fragmen vendor yang Anda tarik. Stack upstream yang dipublikasikan adalah contoh tepat untuk kasus ini: file Compose di balik workspace AFFiNE yang di-hosting sendiri mendeklarasikan empat container yang tidak Anda tulis, dan !reset memungkinkan Anda mengosongkan satu atribut pada salah satunya tanpa melakukan fork terhadap file tersebut dan tanpa harus melacak perubahannya sendiri.
include, untuk stack yang dirakit dari beberapa bagian
include menarik aplikasi Compose lain ke dalam model Anda. Elemen ini berada di tingkat teratas, bukan sebuah flag.
include:
- path: ../commons/compose.yamlSetiap path dalam include dimuat sebagai model aplikasi Compose tersendiri, dengan direktori project masing-masing. Karena itu, path relatif di dalam file tersebut di-resolve terhadap direktori file itu sendiri. Inilah perbedaan utama dari -f, sekaligus alasan include menjadi alat yang tepat ketika fragment berada di folder atau repository lain. Ini adalah pola umum untuk stack vendor yang tidak Anda tulis sendiri: file Compose multi-service di balik instalasi Authentik SSO yang di-host sendiri dapat berada di direktorinya sendiri dengan path relatifnya tetap valid, sementara file Anda tetap hanya mengatur service milik Anda.
Bentuk panjangnya menerima beberapa sub-option.
include:
- path:
- ../monitoring/compose.yaml
- ../monitoring/compose.vps.yaml
project_directory: ../monitoring
env_file: ../monitoring/.envpath menerima sebuah list, dan file-file tersebut digabungkan menggunakan aturan normal sebelum hasilnya dimasukkan ke model Anda. project_directory menetapkan base path yang digunakan untuk me-resolve path relatif dalam file yang di-include. env_file memberikan variabelnya sendiri kepada file yang di-include untuk interpolation. Dengan begitu, shared fragment tidak diam-diam membaca .env milik project Anda. include memerlukan Compose v2.20.0 atau yang lebih baru. Option yang sama sesuai untuk add-on single-container pada stack yang sudah Anda jalankan, misalnya Halcyon, yang mengubah tampilan library Jellyfin menjadi toko penyewaan tahun 90-an: file tersebut mempertahankan image tag dan env_file miliknya sendiri, sehingga proses upgrade tidak pernah mengharuskan Anda mengubah file tempat media stack Anda berada.
Nama resource yang sama antara file Anda dan file yang di-include akan dilaporkan sebagai error, bukan digabungkan secara diam-diam. Perilaku ini memang disengaja. Untuk mengubah sesuatu yang dideklarasikan oleh file yang di-include, letakkan perubahan tersebut di compose.override.yaml. Override diterapkan pada model yang telah dirakit, sehingga dapat mengubah resource yang di-include tanpa menimbulkan konflik. Kebiasaan ini sangat berguna untuk stack yang file upstream-nya ditulis ulang pada setiap release, seperti server foto multi-container yang dibandingkan dalam PhotoPrism versus Immich. Binding localhost atau volume tambahan sebaiknya diletakkan dalam override, bukan dalam file yang akan digantikan oleh upgrade berikutnya.
Versi singkatnya: include menyusun beberapa aplikasi terpisah, sedangkan -f menambahkan lapisan konfigurasi pada satu aplikasi.
Pemisahan dev dan prod pada satu VPS
Berikut pola lengkapnya dalam tiga file. File dasar mendeklarasikan hal-hal 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 aplikasi menunggu database yang benar-benar merespons, bukan sekadar container yang sudah ada. Penjelasannya ada di healthcheck dan kondisi depends_on. POSTGRES_PASSWORD diinterpolasikan dari file .env project, 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 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 akan menggabungkan kedua file tersebut. command menggantikan nilai default image karena nilainya tunggal. LOG_LEVEL menggantikan info karena environment menggabungkan berdasarkan key. Bind mount dan dua port yang dipublikasikan merupakan penambahan murni. Port database di-bind ke localhost agar laptop pada jaringan bersama tidak menawarkan PostgreSQL ke perangkat lain.
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: 512MDi VPS, Anda menyebutkan kedua file tersebut. Tindakan menyebutkan file itulah yang 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 psps seharusnya menampilkan kedua service sebagai sedang berjalan, dengan db yang menampilkan (healthy). Karena Anda meneruskan -f, compose.override.yaml tidak dibaca. Dengan demikian, command 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 aplikasi di belakang Traefik saat Anda menambahkan service kedua.
Atur COMPOSE_FILE=compose.yaml:compose.prod.yaml di .env server, lalu command lainnya dapat kembali menggunakan docker compose logs -f app biasa.
Stack dengan satu service memiliki pola yang sama, karena pelacak latihan openGym yang di-host sendiri harus merespons melalui TLS di belakang proxy sebelum Anda mendaftarkan passkey pertama. File dasar tanpa ports mencegah binding publik yang tidak disengaja mendahului proxy.
Baca model yang telah digabungkan sebelum melakukan deployment
docker compose config mencetak model yang telah digabungkan dan diinterpolasi sepenuhnya. Ini bukan pratinjau. Model ini merupakan input persis yang akan digunakan Compose, sehingga jika output tidak sesuai dengan ekspektasi Anda, output tersebut adalah hasil 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} tanpa ekspansi. 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 hal 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 menunjuk ke path yang tidak ada. Compose mencari file dasar default di direktori induk, tetapi tidak mencari ke lokasi lain untuk file yang Anda tentukan sendiri.
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. Interpolasi mengacu pada file .env proyek dan environment shell. Direktori proyek di sini adalah direktori tempat file -f pertama berada. Men-deploy dari direktori yang berbeda dari direktori yang berisi .env akan menampilkan peringatan ini, kemudian menghasilkan database yang menolak setiap koneksi.
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 dibangun oleh Compose.
Bind mount kosong dan Docker membuat direktori yang tidak Anda minta. Path relatif di-resolve berdasarkan direktori file pertama. Perbaiki path tersebut, teruskan --project-directory, atau pindahkan fragmen ke belakang include.
Container kembali dengan nama baru dan sebuah volume tampak kosong. Nama proyek berubah karena nama proyek mengikuti direktori file pertama. Tambahkan name: tingkat teratas ke file dasar agar penamaan tidak terus berubah. Volume lama masih ada di bawah prefix lama, dan docker volume ls akan menampilkannya.
Port yang Anda hapus di override masih terbuka. Merge ports menambahkan, bukan mengganti. Konfirmasikan dengan docker compose config, kemudian 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 direktori 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. Penggunaan -f 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 menyusun konfigurasi sesuai urutan file yang Anda berikan. Setiap file menimpa dan menambahkan konfigurasi dari file sebelumnya. Karena itu, file terakhir pada baris perintah akan menangani setiap konflik. Daftar yang sama harus digunakan untuk setiap perintah dalam project tersebut. Fitur inilah yang ditangani oleh COMPOSE_FILE=compose.yaml:compose.prod.yaml.
Mengapa port saya masih dipublikasikan setelah saya menimpanya?
Karena entri ports diidentifikasi berdasarkan seluruh rangkaian ip, target, published, dan protocol. Override 127.0.0.1:8080:80 terhadap nilai dasar 8080:80 berbeda pada bagian ip. Karena itu, Compose menganggapnya 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. Alternatifnya, jangan masukkan ports ke file dasar agar tidak ada nilai yang perlu digabungkan.
Apa perbedaan antara include dan -f?
-f melapiskan beberapa file ke satu aplikasi. Semua path relatif dalam setiap file ditentukan berdasarkan direktori file pertama. include menyertakan aplikasi Compose terpisah. Setiap path yang disertakan tetap menggunakan direktori project-nya sendiri, sehingga path relatifnya ditentukan berdasarkan direktori tersebut. Gunakan -f untuk lapisan environment pada stack Anda sendiri. Gunakan 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 override. Atribut tersebut akan kembali ke nilai default atau menjadi null. Nilai yang Anda berikan pada tag wajib ada, tetapi akan diabaikan. Jika ingin mengganti atribut, bukan menghapusnya, gunakan !override. Fitur ini memerlukan v2.24.4 atau yang lebih baru.