Berkas sepuluh baris pada chapter sebelumnya sudah terbukti bekerja: dua service menyala, satu network tercipta, dan seluruh output berjalan sesuai janji. Tetapi berkas yang kita tulis itu baru memakai satu blok — services — sementara compose.yaml yang utuh mengenal tujuh blok top-level dengan aturan mainnya masing-masing. Chapter ini membedah anatomi lengkap berkas tersebut: prioritas nama berkas, peta blok top-level, identitas project lewat name, penghematan pengulangan dengan x- fragment, aturan indentasi, hingga kunci version: yang sudah pensiun. Di ujungnya kita akan menguji semua pengetahuan itu dengan satu perintah pemeriksa yang merender berkas final tanpa menjalankan satu container pun.

Empat Nama Berkas dan Prioritasnya #

Compose mencari berkas definisi di folder tempat perintah dijalankan, dengan urutan pencarian yang pasti. Ketika dijalankan tanpa flag -f, Compose memeriksa empat nama berikut dan memakai yang pertama ditemukan:

PrioritasNama berkasStatus
1compose.yamlnama resmi — selalu pilih ini untuk berkas baru
2compose.ymlalternatif sah bila konvensi tim memakai .yml
3docker-compose.yamlnama warisan era Compose v1 — masih dikenali
4docker-compose.ymlnama warisan era Compose v1 — masih dikenali

Kedua nama warisan memakai tanda hubung di antara kata docker dan compose, mewarisi nama perintah generasi pertama Compose; banyak proyek lama di lapangan masih menyimpannya. Kebiasaan kita sepanjang Course ini cukup satu: menulis compose.yaml.

Peta Blok Top-Level #

Tujuh blok sah boleh hadir di level teratas sebuah compose.yaml. Peta berikut merangkum semuanya sebelum kita memasuki satu per satu:

BlokWajibPeran
servicesya — satu-satunya blok wajibcetakbiru container yang akan dijalankan
nametidakidentitas project: prefix nama container, network, dan volume
networkstidaknetwork yang dirujuk service, di luar bawaan project
volumestidakvolume bernama yang dirujuk service
configstidakberkas konfigurasi yang disuntikkan ke service
secretstidakberkas rahasia yang disuntikkan ke service
x-<apa pun>tidakgudang nilai untuk dipakai ulang — diawali x-

Susunan lengkapnya terbaca paling jelas lewat satu kerangka. Peta berikut meminjam isi dari chapter-chapter mendatang supaya seluruh blok tampil sekaligus dalam satu potret:

yaml
name: katalog

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

services:
  web:
    <<: *umum
    image: nginx:1.29-alpine
    ports:
      - "8080:80"
    networks:
      - frontend

  db:
    <<: *umum
    image: postgres:18-alpine
    environment:
      - POSTGRES_PASSWORD=rahasia123
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

volumes:
  db-data:

configs:
  nginx-conf:
    file: ./nginx.conf

secrets:
  kunci-api:
    file: ./kunci-api.txt

Mari kita telaah struktur kerangka tersebut:

  1. name: katalog — identitas project yang akan menjadi prefix seluruh objek kelolaannya; perannya kita bedah tuntas di bagian berikutnya.
  2. x-umum dengan &umum dan <<: *umum — pasangan penanda (anchor) dan rujukan (alias) milik YAML untuk menyalin konfigurasi berulang tanpa duplikasi; mekanismenya mendapat bagian khusus setelah ini.
  3. services — jantung berkas; setiap anak dari blok ini adalah satu service, dan hanya blok ini yang wajib ada.
  4. networks dan volumes top-level — mendaftarkan objek yang dirujuk dari dalam service: frontend dan backend akan lahir sebagai katalog_frontend dan katalog_backend, sedangkan db-data adalah volume bernama untuk data PostgreSQL. Pembahasan lengkapnya menanti Chapter Port dan Network dan Volume dan Bind Mount.
  5. configs dan secrets — dua blok untuk menyuntikkan berkas ke dalam container; keduanya sengaja diletakkan paling bawah karena membutuhkan berkas sumber di build context.

name: Identitas Project #

Tanpa blok name, Compose menamai project mengikuti nama folder tempat berkas berada — folder katalog melahirkan project katalog, dan itulah yang kita saksikan pada chapter sebelumnya ketika network tercipta bernama katalog_default. Menuliskan name: katalog secara eksplisit melepaskan project dari kebetulan nama folder: nama project menjadi bagian dari deklarasi, bukan warisan struktur direktori.

Nama project itu menempel pada tiga jenis objek sekaligus. Container menjalankan pola <project>-<service>-<index> seperti katalog-db-1, network bawaan bernama <project>_default seperti katalog_default, dan volume bernama kelak mengikuti <project>_<volume>. Identitas yang sama juga menjadi kunci perintah-perintah manajemen: docker compose ps dan docker compose down mencari container berdasarkan label project ini.

Dua jalur lain bisa memaksa nama project menimpa nilai yang tertulis di berkas: flag -p pada perintah, misalnya docker compose -p katalog-uji up, dan environment variable COMPOSE_PROJECT_NAME di shell. Kedua jalur itu berguna saat satu berkas yang sama perlu berjalan sebagai dua project berdampingan — skenario yang kita peragakan pada Chapter Service.

x- Fragments: Menyalin Tanpa Menulis Ulang #

Konfigurasi yang sama sering dibutuhkan lebih dari satu service, dan YAML menyediakan dua mekanisme salin-menyalin untuk itu. Yang pertama adalah penanda dan rujukan: nilai yang ditandai &nama boleh dirujuk di tempat lain dengan *nama. Compose menambahkan konvensi x- untuk blok gudang — blok yang diawali x- diabaikan Compose sebagai konfigurasi, sehingga aman dipakai sebagai tempat menaruh nilai:

yaml
x-logging-umum: &logging-umum
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  web:
    image: nginx:1.29-alpine
    ports:
      - "8080:80"
    logging: *logging-umum

  db:
    image: postgres:18-alpine
    environment:
      - POSTGRES_PASSWORD=rahasia123
    logging: *logging-umum

Mari kita telaah mekanisme salinnya: blok x-logging-umum menyimpan peta konfigurasi logging lengkap dengan penanda &logging-umum, lalu kedua service merujuk peta yang sama dengan logging: *logging-umum. Perubahan satu nilai max-size di satu tempat langsung berlaku untuk dua service — penghematan yang persis semangat DRY pada kode program.

Mekanisme kedua adalah merge key <<: yang kita lihat pada kerangka peta di atas. Alih-alih menyalin satu kunci, merge key menyuntikkan seluruh pasangan kunci-nilai dari rujukan ke dalam pemetaan yang menerima:

yaml
x-umum: &umum
  restart: unless-stopped
  logging:
    driver: json-file

services:
  web:
    <<: *umum
    image: nginx:1.29-alpine
  db:
    <<: *umum
    image: postgres:18-alpine

Kedua service menerima restart dan logging sekaligus tanpa menulisnya, sementara kunci miliknya sendiri seperti image tetap ditulis biasa. Kunci yang ditulis ulang di bawah <<: akan menimpa nilai hasil merge, sehingga pengecualian tetap mungkin. Peringatannya soal keterbacaan: pembaca berkas harus melompat ke definisi penanda untuk mengetahui konfigurasi final sebuah service, dan tumpukan merge bertingkat menjadikan berkas cepat sulit dibaca manusia. Gunakan kedua mekanisme ini untuk blok yang benar-benar seragam, dan biarkan perbedaan antar service tertulis eksplisit — docker compose config di bawah selalu siap menunjukkan hasil akhir penggabungannya.

Indentasi Dua Spasi dan Kunci version: yang Pensiun #

YAML memakai indentasi sebagai struktur: kedalaman baris menentukan kepemilikan kunci. Compose mengikuti kebiasaan ekosistem Docker dengan indentasi dua spasi per tingkat, seperti seluruh contoh pada chapter ini. Aturan ketatnya satu: karakter tab dilarang untuk indentasi — parser YAML menolaknya, dan kita akan melihat wujud galatnya pada bagian terakhir chapter.

Satu kunci lama perlu dimakamkan dengan sadar: version:. Berkas-berkas warisan era awal kerap diawali baris yang menyatakan versi skema — nilai seperti "3.8" — dan pada generasi tersebut ketidakcocokan angka dengan fitur yang dipakai bisa menggugurkan perintah dengan galat validasi. Compose modern menjadikan kunci ini obsolete: skema yang berlaku sudah tunggal, sehingga baris tersebut tidak lagi berarti apa pun. Inilah perubahan yang dianjurkan pada berkas warisan yang kita temui:

diff
-version: "3.8"
 services:
   web:
     image: nginx:1.29-alpine
     ports:
       - "8080:80"

Bila baris itu dibiarkan, Compose masih mau memproses berkas, tetapi setiap perintah diawali peringatan yang menyebut jalurnya persis:

text
WARN[0000] /home/budi/katalog/compose.yaml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion

Mari kita telaah tiga keterangan dari peringatan itu: pertama, statusnya peringatan bukan galat — berkas tetap diproses dan kunci diabaikan; kedua, Compose menyebut berkas dan atribut yang jadi sumbernya sehingga sumber kebisingan mudah ditemukan; ketiga, sarannya tegas — hapus barisnya, seperti sisi kanan diff di atas. Sepanjang Course ini kita tidak pernah menulis kunci tersebut.

YAML atau JSON #

JSON sah dipakai sebagai isi berkas karena YAML 1.2 merangkum JSON sebagai subset — Compose akan menerimanya. Kedua format itu tetap tidak setara dalam kenyamanan sehari-hari:

AspekYAMLJSON
Keterbacaan manusiastruktur tampak dari indentasi dua spasipadat tanda kurung kurawal dan tanda kutip
Komentardidukung dengan awalan #tidak didukung sama sekali
Penghematan pengulanganpenanda, alias, dan merge keytidak tersedia
Kedudukan di Composeformat asli seluruh contoh resmisah sebagai subset — nyaris tak dipakai komunitas

Kehilangan komentar saja sudah cukup sebagai alasan: berkas deklarasi adalah dokumen yang dibaca manusia, dan kemampuan mencatat alasan di samping nilai konfigurasi termasuk yang paling berharga.

docker compose config: Merender Berkas Final #

Sejauh ini kita memeriksa berkas dengan menjalankannya — cara yang mahal. Compose menyediakan pemeriksa yang jauh lebih murah: docker compose config membaca berkas, menyelesaikan seluruh mekanisme penggabungan, lalu mencetak konfigurasi final yang akan dipakai daemon. Mari kita jalankan pada berkas katalog yang kini sudah memakai name::

yaml
name: katalog

services:
  web:
    image: nginx:1.29-alpine
    ports:
      - "8080:80"
  db:
    image: postgres:18-alpine
    environment:
      - POSTGRES_PASSWORD=rahasia123
bash
$ docker compose config

Output yang Dihasilkan:

yaml
name: katalog
services:
  db:
    environment:
      POSTGRES_PASSWORD: rahasia123
    image: postgres:18-alpine
    networks:
      default: null
  web:
    image: nginx:1.29-alpine
    networks:
      default: null
    ports:
      - target: 80
        published: "8080"
        protocol: tcp
        mode: ingress
networks:
  default:
    name: katalog_default

Mari kita telaah enam perubahan yang dilakukan renderer:

  1. name: katalog tercetak pertama — identitas project dari blok name ikut menjadi bagian konfigurasi final; bila blok itu tidak ditulis, renderer akan mengisinya dengan nama folder sebagai sumber cadangan.
  2. Urutan kunci disusun ulang — service db tampil mendahului web meskipun berkas menulis web lebih dulu; hasil render adalah konfigurasi yang sudah dinormalisasi Compose, bukan salinan teks berkas.
  3. environment berubah bentuk — daftar - POSTGRES_PASSWORD=rahasia123 dirender sebagai pemetaan POSTGRES_PASSWORD: rahasia123; kedua bentuk sah untuk ditulis, dan renderer menyeragamkannya ke bentuk pemetaan.
  4. Network implisit menjadi eksplisit — setiap service kini mencantumkan networks: default: null, dan blok networks top-level lahir dengan default: name: katalog_default; inilah bukti render bahwa network bawaan project memang diberikan Compose tanpa kita deklarasikan.
  5. ports diperluas menjadi long syntax — string pendek "8080:80" dibuka menjadi empat kunci: target 80 di container, published “8080” di host, protocol tcp, dan mode ingress; bentuk panjang ini bisa pula kita tulis sendiri, materi Chapter Port dan Network.
  6. published dirender dengan tanda kutip — renderer sengaja menjaga nomor port sebagai string "8080"; alasan di balik tanda kutip itu adalah kejelian YAML terhadap pola xx:yy yang akan kita bedah pada chapter yang sama.

Untuk skrip dan pipeline CI, flag --quiet membuat perintah ini memvalidasi berkas tanpa mencetak apa pun: keberhasilan dilaporkan lewat exit code 0, sehingga docker compose config --quiet layak menjadi gerbang pemeriksaan sebelum deployment di skrip otomatis.

Dua Kesalahan yang Paling Sering Terjadi #

Dua kesalahan tulis mengalahkan pemula lebih sering daripada materi lainnya, dan keduanya sama-sama mudah dikenali dari pesannya. Yang pertama: menulis services sebagai daftar, terbawa kebiasaan menulis elemen bertanda hubung:

yaml
services:
  - web:
      image: nginx:1.29-alpine
  - db:
      image: postgres:18-alpine
text
validating /home/budi/katalog/compose.yaml: services must be a mapping

Perbaikannya adalah menghapus tanda hubung dan menjadikan kedua service anak langsung dari services: — pemetaan nama-ke-konfigurasi, bukan daftar. YAML menerima kedua bentuk itu sebagai dokumen yang sah, sehingga tanggung jawab menolak jatuh ke validasi Compose; pesan services must be a mapping menyebut blok yang salah bentuk dengan tepat.

Yang kedua: indentasi dengan karakter tab, biasanya disisipkan diam-diam oleh editor:

yaml
services:
        web:
                image: nginx:1.29-alpine
text
yaml: line 2: found character that cannot start any token

Perbaikannya adalah mengganti setiap tab dengan dua spasi pada baris yang disebut — dan pada seluruh berkas, karena tab berikutnya akan memicu galat yang sama di barisnya masing-masing. Perhatikan bahwa pesan ini datang dari parser YAML itu sendiri sebelum Compose sempat memvalidasi apa pun: nomor barisnya adalah petunjuk lokasi pertama karakter tab ditemukan.

Anatomi berkas kini terpetakan: nama berkas dan prioritasnya, tujuh blok top-level, identitas project, mekanisme salin x-, indentasi dua spasi, hingga cara merender dan memvalidasinya. Namun dari seluruh blok itu, hanya satu yang benar-benar menjalankan container: services. Chapter Service membedah blok tersebut — hubungan service dengan container yang ia instansiasi, pola penamaan <project>-<service>-<index>, dan delapan belas kunci konfigurasi yang paling sering dipakai sehari-hari.

Referensi #