Docker Compose: Bedanya .env, env_file, dan Secrets
Pahami perbedaan .env, env_file, dan environment di Docker Compose, termasuk urutan prioritas saat key sama serta alasan secrets lebih tepat untuk password.
Tiga hal yang disebut orang sebagai file env
Docker Compose memiliki tiga mekanisme terpisah dengan nama yang sangat mirip dan dapat membingungkan. File .env mengisi placeholder ${VARIABLE} di dalam compose.yaml itu sendiri, sebelum Compose mengurai file tersebut. Atribut env_file: memuat file yang berisi pasangan key/value ke dalam environment container. Atribut environment: menetapkan variabel langsung pada container, yang ditulis di dalam file compose. Ketiganya tidak dapat saling menggantikan. Jika dua mekanisme menetapkan key yang sama, pemenangnya ditentukan oleh urutan prioritas yang telah didokumentasikan.
Panduan ini menunjukkan cara kerja masing-masing mekanisme, membuktikan prioritasnya dengan perintah yang dapat Anda jalankan, lalu membahas hal yang lebih penting: variabel environment dapat dibaca oleh siapa pun yang dapat menjalankan docker inspect, sehingga password tidak boleh disimpan di dalamnya. Jika Anda masih baru menggunakan file compose, mulai dengan dasar-dasar Docker Compose pada VPS, lalu kembali ke sini untuk mempelajari konfigurasi.
Berkas .env digunakan oleh berkas compose, bukan oleh container
Buat sebuah direktori, lalu masukkan dua berkas ke dalamnya.
mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .envservices:
demo:
image: alpine:${ALPINE_TAG}
command: printenv ALPINE_TAGSekarang minta Compose menampilkan hasil parsing yang sebenarnya.
docker compose configOutput tersebut menampilkan image: alpine:3.20. Placeholder sudah tidak ada karena interpolasi dilakukan saat parsing. Compose mencari .env di direktori proyek, yaitu direktori yang berisi berkas compose, lalu mengganti setiap ${NAME} yang ditemukannya.
Selanjutnya, jalankan service.
docker compose run --rm demoprintenv ALPINE_TAG keluar dengan status 1 dan tidak mencetak apa pun. Variabel tersebut tidak ada di dalam container. Inilah kesalahpahaman yang paling umum: .env mengonfigurasi berkas compose, bukan prosesnya. Berkas .env yang berisi POSTGRES_PASSWORD=hunter2 tidak melakukan apa pun terhadap database Anda kecuali ada bagian dari berkas compose yang merujuknya.
${NAME:-default} menyediakan nilai cadangan ketika variabel tidak ditetapkan atau nilainya kosong. ${NAME:?message} membuat Compose menolak untuk memulai dan mencetak pesan Anda. Ini merupakan pilihan yang tepat untuk nilai yang tidak memiliki default yang aman.
env_file memuat variabel ke dalam container
Atribut env_file: menentukan satu atau beberapa file yang isinya menjadi variabel lingkungan container.
printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.envservices:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.envdocker compose run --rm demoPerintah ini mencetak from_env_file. Format file tersebut terdiri atas baris KEY=value biasa, satu per baris, dengan # sebagai awalan komentar. Format ini bukan shell. Dalam sebagian besar kasus, tanda kutip tetap menjadi bagian dari nilai, dan awalan export tidak diperlukan. Jangan menambahkan spasi di sekitar tanda =, karena KEY = value menghasilkan variabel yang secara literal bernama KEY dengan spasi di awal nilainya.
Path env_file yang tidak ditemukan merupakan error dan Compose akan berhenti. Tandai file tersebut sebagai opsional jika file itu memang boleh tidak ada:
env_file:
- path: ./app.env
required: falseenvironment menetapkan variabel secara inline
services:
demo:
image: alpine:3.20
command: printenv GREETING
environment:
GREETING: from_environmentDua sintaks didukung: format pemetaan di atas dan format daftar menggunakan - GREETING=from_environment. Keduanya berperilaku sama. Format daftar memiliki satu fitur tambahan: key tanpa value meneruskan variabel dari shell tempat Anda menjalankan docker compose.
environment:
- GREETINGGREETING=from_my_shell docker compose run --rm demoPerintah tersebut mencetak from_my_shell. Jalankan tanpa menetapkan GREETING di shell, lalu Compose tidak menetapkan apa pun dan tidak menampilkan peringatan. Kegagalan penerusan secara diam-diam perlu diperhatikan, karena service yang berjalan dengan variabel password kosong sering kali tetap berhasil start dan menjadi terbuka sepenuhnya.
Yang mana yang berlaku
Docker mendokumentasikan urutan prioritas, dari yang tertinggi: docker compose run -e pada command line, lalu environment atau env_file yang nilainya diinterpolasi dari shell atau file env, kemudian environment biasa dalam file Compose, lalu env_file, dan terakhir direktif ENV yang sudah ditanamkan ke dalam image.
Versi singkat untuk pekerjaan sehari-hari: environment: mengungguli env_file:, sedangkan -e pada command line mengungguli keduanya. Buktikan dalam satu file.
services:
demo:
image: alpine:3.20
command: printenv GREETING
env_file:
- ./app.env
environment:
GREETING: from_environmentdocker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETINGPerintah pertama mencetak from_environment, sehingga environment: menimpa nilai dalam app.env. Perintah kedua mencetak from_cli. Tidak ada apa pun dalam file Compose yang menimpa command line.
Jika container berperilaku seolah-olah konfigurasi Anda tidak diterapkan, jangan menebak. docker compose config mencetak file yang telah diselesaikan sepenuhnya, sedangkan docker compose config --environment mencetak variabel interpolasi yang digunakan Compose. Sebagian besar laporan “file env saya diabaikan” ternyata disebabkan oleh nilai yang ditetapkan dua kali pada dua tingkat berbeda.
Mengapa variabel lingkungan dapat bocor
Tetapkan kata sandi di environment:. Kata sandi tersebut disimpan dalam konfigurasi container di disk dan dapat dilihat oleh setiap pengguna dalam grup docker.
docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'Output tersebut memuat "DB_PASSWORD=hunter2" dalam teks biasa. Tiga path lain juga mengekspos nilai yang sama. docker compose config mencetaknya ke terminal. Inilah cara nilai tersebut akhirnya ditempelkan ke forum dukungan. Setiap proses di dalam container dapat membaca /proc/1/environ, dan setiap proses turunan mewarisi variabel tersebut. Selain itu, penangan crash aplikasi sering kali menyalin seluruh lingkungan ke dalam log atau laporan error.
Keanggotaan dalam grup docker pada dasarnya memberikan akses root pada host. Karena itu, grup ini bukan batas hak akses yang dapat diandalkan. Panduan tentang akun pengguna dengan hak akses minimum pada VPS menjelaskan alasan grup tersebut perlu dibatasi pada server bersama.
Secrets Compose menyimpan nilainya dalam file
Compose mendukung secret berbasis file. Nilainya dipasang ke dalam container sebagai file, bukan disuntikkan ke environment.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txtSecret dipasang pada /run/secrets/db_password di dalam container. Nama setelah garis miring adalah nama secret dari blok secrets: tingkat teratas.
Sufiks _FILE merupakan konvensi yang digunakan oleh Docker Official Images, termasuk postgres, mysql, dan mariadb. Skrip entrypoint tersebut memeriksa VARNAME_FILE, membaca file, lalu menggunakan isinya. Ini bukan fitur Docker, sehingga hanya berfungsi jika image mengimplementasikannya. Periksa dokumentasi image sebelum mengasumsikan bahwa SOMETHING_FILE akan digunakan. Aplikasi yang tidak mendukungnya biasanya dapat membaca file tersebut sendiri saat start, atau Anda dapat meneruskan path dan membiarkan entrypoint Anda sendiri menanganinya.
Verifikasi dari dalam container yang sedang berjalan:
docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORDPerintah pertama mencetak password. Perintah kedua tidak mencetak apa pun karena nilainya tidak pernah masuk ke environment. Itulah tujuannya: docker inspect pada container ini hanya menampilkan path yang tidak sensitif.
Lindungi file sumber pada host karena kerahasiaan secret hanya sebaik perlindungan file yang menjadi sumbernya:
chmod 600 db_password.txtJalan tengah yang pragmatis pada VPS
Banyak image self-hosted tidak mendukung variabel _FILE, sehingga variabel lingkungan menjadi satu-satunya cara untuk memasukkan nilai. Pada satu VPS yang dikelola oleh administrator, tujuan yang realistis adalah mencegah nilai tersebut tersimpan dalam file yang dapat dibaca semua pengguna di direktori project dan menjauhkannya dari git.
sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env env_file:
- /etc/myapp/app.envinstall -m 600 membuat file dengan mode yang sudah ditetapkan, sehingga tidak ada celah waktu ketika file tersebut dapat dibaca semua pengguna. root menjadi pemiliknya, sehingga pengguna non-root pada server tidak dapat membacanya. Namun, siapa pun yang dapat menjalankan docker tetap dapat membaca nilainya dari dalam container. Tambahkan *.env dan .env ke .gitignore, lalu commit file app.env.example yang hanya berisi nama key dengan nilai kosong. Password yang di-commit harus dianggap sebagai password yang perlu dirotasi.
Merotasi suatu nilai berarti me-restart service. Variabel lingkungan dibaca satu kali saat proses container dimulai. Karena itu, mengedit file tidak mengubah apa pun sampai Anda menjalankan docker compose up -d --force-recreate db. Pola yang sama digunakan dalam panduan n8n di balik HTTPS pada VPS, dengan encryption key disimpan di luar file compose.
Memisahkan konfigurasi berdasarkan lingkungan
Compose secara default membaca .env dari direktori project. Arahkan Compose ke lokasi lain dengan --env-file.
docker compose --env-file .env.staging configBeberapa file dibaca secara berurutan. File berikutnya menimpa nilai dari file sebelumnya. Simpan nilai default yang tidak bersifat rahasia dalam file yang di-commit, dan simpan secret dalam file yang tidak pernah meninggalkan server. Hal yang sama berlaku untuk env_file:. Jika terdapat key duplikat, file terakhir yang tercantum akan digunakan.
FAQ
Mengapa file .env saya diabaikan di dalam container?
File tersebut tidak diabaikan. File .env hanya mengganti placeholder ${NAME} di dalam file compose. File tersebut tidak pernah menetapkan variabel di dalam container. Agar nilainya masuk ke container, referensikan nilainya: environment: { KEY: "${NAME}" }, atau gunakan env_file: ./that-file.env.
Apakah environment mengesampingkan env_file, atau sebaliknya?
environment: memiliki prioritas. Urutan yang didokumentasikan Docker menempatkan atribut environment di atas atribut env_file, dan keduanya berada di bawah docker compose run -e pada command line. Jika sebuah key ditetapkan di kedua tempat, nilai dalam env_file tidak digunakan tanpa pesan apa pun.
Bagaimana cara melihat nilai akhir yang akan digunakan Compose?
Jalankan docker compose config untuk menampilkan file compose yang telah diselesaikan sepenuhnya dengan semua interpolasi yang diterapkan. Untuk container yang sudah berjalan, docker inspect <container> --format '{{json .Config.Env}}' menampilkan persis nilai yang diterima prosesnya.
Apakah secret Compose dienkripsi?
Tidak. Secret berbasis file di-mount ke dalam container sebagai file biasa di /run/secrets/<name>, sedangkan file sumbernya tersimpan tanpa enkripsi pada disk host. Manfaatnya adalah pembatasan cakupan, bukan enkripsi: nilainya tidak masuk ke environment container, tidak muncul dalam output docker inspect, dan tidak masuk ke crash dump yang mencetak environment.
Dapatkah saya menggunakan tanda kutip dan spasi dalam file env?
Gunakan KEY=value with spaces dan jangan gunakan tanda kutip. Compose memperlakukan seluruh sisa baris sebagai nilai, sehingga tanda kutip biasanya menjadi karakter literal dalam nilai tersebut. Jangan pernah menambahkan spasi di sekitar =, karena key tersebut kemudian memiliki spasi di akhir dan tidak ada nilai yang cocok.