Environment dan Secret

Berkas .env, variabel di compose.yaml, berkas override, dan pengenalan secret untuk data sensitif.

Daftar isi

Nilai konfigurasi seperti kata sandi basis data tidak ditulis langsung ke dalam image, melainkan diberikan pada saat proyek dijalankan. Proyek (project) berarti kesatuan container dari satu compose.yaml. Topik ini membahas tiga cara memberi nilai pada compose.yaml, perbedaan berkas .env dengan env_file:, berkas override yang dimuat otomatis untuk menimpa nilai bawaan, serta pengenalan secret, yaitu mekanisme penanganan data sensitif yang diteruskan sebagai berkas. Setelah membacanya, konfigurasi aplikasi catatan terpisah dari kode, dan kata sandi tidak lagi tertulis pada berkas yang dikommit.

Tiga cara memberi nilai variabel lingkungan#

Kunci environment: menuliskan pasangan kunci dan nilai langsung pada service, yaitu kelompok container yang dideskripsikan compose.yaml, dan cara ini paling sederhana untuk nilai yang tidak sensitif. Notasi ${VAR} melakukan interpolasi, yaitu penggantian variabel pada saat Compose membaca berkas, dengan nilai yang diambil dari berkas .env di direktori proyek. Kunci env_file: memuat satu berkas variabel secara utuh ke dalam container service yang menyebutnya. Ketiganya dapat dipakai bersamaan pada satu service, dan tabel berikut merangkum letak serta tujuan masing-masing.

CaraLetak nilaiNilai dipakai oleh
environment:Langsung pada compose.yamlContainer service tersebut
${VAR}Berkas .env di direktori proyekCompose pada saat membaca berkas
env_file:Berkas lain yang disebut pada serviceContainer service tersebut, dimuat utuh

Perbedaan letak nilai menentukan perbedaan fungsinya. Nilai ${VAR} hanya berlaku di dalam Compose, sehingga berkas .env menjadi sumber interpolasi dan tidak otomatis menjadi variabel di dalam container. Sebaliknya, isi berkas yang disebut env_file: langsung menjadi variabel lingkungan container. Apabila satu kunci muncul di lebih dari satu tempat, nilai pada environment: selalu menang atas nilai dari env_file:.

Interpolasi dari berkas .env#

Berkas compose.yaml pada topik ini memakai ketiga cara sekaligus. Bagian yang berhubungan dengan lingkungan tertulis seperti berikut. Kunci lainnya seperti healthcheck, pemeriksaan kesiapan service, dan volume, penyimpanan yang dikelola Docker, mengikuti pola yang sudah dibahas pada topik depends_on dan Healthcheck.

yaml compose.yaml
services:
  web:
    build: .
    ports:
      - "8000:8000"
    env_file:
      - konfigurasi-app.env
    environment:
      DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/catatan
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - kunci_jwt
  db:
    image: postgres:18-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - catatan-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

volumes:
  catatan-data:

secrets:
  kunci_jwt:
    file: kunci_jwt.txt

Berkas .env menyediakan nilai yang dicari interpolasi. Compose membacanya secara otomatis karena letaknya di direktori proyek, yaitu direktori yang sama dengan compose.yaml.

env .env
# berkas .env untuk interpolasi compose.yaml, tidak dimuat ke container
POSTGRES_PASSWORD=rahasia-db
POSTGRES_DB=catatan

Lingkungan uji modul ini tidak memiliki Docker, tetapi langkah interpolasi dapat ditiru untuk melihat hasilnya. Skrip berikut membaca .env, mengganti setiap ${VAR} pada compose.yaml, lalu mencetak nilai environment service db dan DATABASE_URL service web setelah penggantian.

python interpolasi.py
# tidak-dijalankan: skrip dijalankan melalui blok bash di bawah
import re
import yaml


def baca_env(nama_berkas):
    variabel = {}
    with open(nama_berkas) as berkas:
        for baris in berkas:
            baris = baris.strip()
            if baris and not baris.startswith("#") and "=" in baris:
                kunci, nilai = baris.split("=", 1)
                variabel[kunci] = nilai
    return variabel


variabel = baca_env(".env")
with open("compose.yaml") as berkas:
    teks = berkas.read()

terinterpolasi = re.sub(
    r"\$\{(\w+)\}", lambda m: variabel.get(m.group(1), m.group(0)), teks
)
konfigurasi = yaml.safe_load(terinterpolasi)

print("environment service db setelah interpolasi:")
for kunci, nilai in konfigurasi["services"]["db"]["environment"].items():
    print(f"  {kunci} = {nilai}")
url = konfigurasi["services"]["web"]["environment"]["DATABASE_URL"]
print(f"DATABASE_URL service web: {url}")
bash
# meniru interpolasi Compose untuk melihat hasil penggantiannya
python3 interpolasi.py
text Keluaran
environment service db setelah interpolasi:
  POSTGRES_PASSWORD = rahasia-db
  POSTGRES_DB = catatan
DATABASE_URL service web: postgres://postgres:rahasia-db@db:5432/catatan

Keluaran memperlihatkan dua bentuk penggantian sekaligus. Variabel pada environment service db terisi penuh dari .env, dan ${POSTGRES_PASSWORD} yang tersisip di tengah URL koneksi ikut tergantikan. Skrip di atas hanya meniru langkah Compose, sedangkan Compose sendiri melakukan interpolasi pada saat berkas dibaca, sebelum konfigurasi diteruskan ke daemon, proses latar yang mengeksekusi pekerjaan Docker. Konfigurasi akhir dapat diperiksa kapan saja melalui docker compose config.

env_file untuk variabel aplikasi#

Berkas yang dirujuk env_file: ditulis dengan format yang sama seperti .env, yaitu satu baris KUNCI=nilai untuk setiap variabel dan baris # sebagai komentar. Bedanya terletak pada penerima, karena isi berkas ini dimuat utuh ke dalam container web sebagai variabel lingkungan aplikasi. Kunci env_file: ditulis pada tingkat service dengan daftar berkas yang dimuat, dan lebih dari satu berkas boleh dirujuk sekaligus.

env konfigurasi-app.env
# dimuat ke container web melalui kunci env_file
APP_PORT=8000
APP_MODE=production

Pemisahan berkas ini menjaga perintah dan compose.yaml tetap pendek ketika daftar variabel aplikasi memanjang. Nama berkasnya bebas dipilih karena yang berlaku adalah nama yang dirujuk, berbeda dengan .env yang letak dan namanya memang tetap. Di dalam container, nilai-nilai itu dibaca aplikasi seperti variabel lingkungan biasa, misalnya melalui os.environ pada Python. Apabila kunci yang sama juga ditulis pada environment:, nilai pada kunci itu yang menang, sehingga kasus khusus tetap dapat ditangani tanpa menyunting berkas env-nya.

Berkas override compose.override.yaml#

Berkas bernama compose.override.yaml dimuat otomatis oleh Compose setiap kali perintah compose dijalankan di direktori proyek, lalu digabungkan di atas compose.yaml. Kunci yang sama pada berkas ini menimpa nilai bawaannya, sedangkan kunci baru ikut ditambahkan. Pola yang lazim menjadikan compose.yaml memuat nilai bawaan untuk produksi, sedangkan override memuat pengaturan pengembangan di mesin lokal.

yaml compose.override.yaml
# dimuat otomatis untuk menimpa nilai bawaan saat pengembangan
services:
  web:
    build: .
    volumes:
      - .:/aplikasi
    environment:
      APP_MODE: debug

Pada contoh ini, service web mendapat bind mount, yaitu pemasangan direktori host, mesin tempat Docker berjalan, ke dalam container. Pemasangan itu mengarah ke kode sumber, dan APP_MODE: debug berlaku saat pengembangan. Nilai debug tersebut menimpa APP_MODE=production yang datang dari env_file:, karena kunci environment: selalu menang atas env_file:. Penggabungan dilakukan Compose per kunci, sehingga service yang disebut pada kedua berkas digabungkan, bukan digantikan utuh, dan kunci yang tidak disebut pada override tetap diambil dari berkas dasar. Proyek yang sama karenanya berjalan dalam mode produksi bila berkas override tidak ada, dan beralih ke mode pengembangan secara otomatis di mesin yang memiliki berkas tersebut.

Secret sebagai berkas#

Data yang paling sensitif seperti kunci penandatanganan token sebaiknya tidak melewati variabel lingkungan sama sekali, karena variabel lingkungan terbaca pada docker inspect dan tertulis pada log beberapa alat. Kunci secrets: tingkat atas memperkenalkan jalur lain: secret dinyatakan bersumber dari sebuah berkas, lalu dirujuk oleh service yang membutuhkannya. Berkas berikut memuat kunci milik aplikasi catatan.

text kunci_jwt.txt
b1f9c2e7-4a8d-4e6b-9c31-7d2f5a8e0b46

Deklarasinya pada compose.yaml memakai file: kunci_jwt.txt, sedangkan service web merujuknya seperti berikut.

yaml Contoh: service merujuk secret kunci_jwt
    secrets:
      - kunci_jwt

Pada Compose biasa, secret diteruskan sebagai berkas yang dipasang di dalam container pada jalur /run/secrets/kunci_jwt, bukan sebagai variabel lingkungan. Aplikasi membacanya sebagai berkas biasa pada saat membutuhkan, dan nilai itu tidak pernah tampil pada daftar variabel lingkungan container. Jalur pemasangannya juga hanya ada selama container berjalan, sehingga berkas secret tidak ikut tersimpan pada lapisan image.

Kesalahan umum#

  1. Mengira berkas .env dimuat ke dalam container. Berkas itu hanya sumber interpolasi bagi Compose, sehingga aplikasi tidak melihat isinya meskipun berkasnya berada di direktori proyek. Perbaikannya dengan memakai env_file: untuk variabel container atau environment: untuk kasus khusus.

  2. Menulis kata sandi langsung pada environment:. Nilai itu ikut tersimpan pada compose.yaml yang dikommit dan terbaca pada docker inspect. Perbaikannya dengan memindahkan nilai sensitif ke .env lalu memakai interpolasi, atau ke secrets: untuk yang paling sensitif, dan memastikan berkas hasilnya tidak terkommit pula.

  3. Bingung karena perilaku compose berbeda dari berkasnya. compose.override.yaml ikut dimuat tanpa disebut pada perintah, sehingga hasil gabungannya berbeda dari isi compose.yaml saja. Perbaikannya dengan memeriksa konfigurasi akhir melalui docker compose config sebelum menyimpulkan, karena perintah itu mencetak hasil gabungan seluruh berkas.

Ringkasan#

  • Nilai konfigurasi diberikan melalui environment: yang langsung, ${VAR} yang diinterpolasi dari berkas .env, atau env_file: yang memuat berkas variabel utuh ke container.
  • Berkas .env melayani interpolasi Compose, sedangkan env_file: melayani container, dan environment: menimpa keduanya bila kuncinya bentrok.
  • compose.override.yaml dimuat otomatis dan menimpa nilai bawaan, sehingga pola bawaan produksi dengan override pengembangan bekerja tanpa opsi tambahan.
  • Secret dideklarasikan dengan file: pada kunci secrets: tingkat atas dan diteruskan sebagai berkas di /run/secrets/, bukan sebagai variabel lingkungan.

Referensi#