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 sehingga mudah membingungkan. File .env mengisi placeholder ${VARIABLE} di dalam compose.yaml itu sendiri, setelah Compose membaca YAML dan sebelum melakukan validasi atau menjalankan apa pun. Atribut env_file: memuat pasangan key/value ke dalam environment container. Atribut environment: menetapkan variabel langsung pada container, yang ditulis di file compose. Ketiganya tidak dapat saling menggantikan. Jika dua mekanisme menetapkan key yang sama, pemenangnya ditentukan oleh urutan prioritas yang terdokumentasi.
Panduan ini menunjukkan cara kerja masing-masing mekanisme, membuktikan prioritas tersebut 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 secara umum, mulai dengan Dasar-dasar Docker Compose pada VPS, lalu kembali ke sini untuk konfigurasi.
File .env digunakan oleh compose file, bukan oleh container
Buat sebuah direktori, lalu masukkan dua file 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 menampilkan image: alpine:3.20. Placeholder tersebut sudah hilang karena interpolasi dilakukan saat Compose memuat file. Compose mencari .env di direktori proyek: direktori yang diberikan dengan --project-directory jika Anda menetapkannya; jika tidak, direktori dari file pertama yang diteruskan dengan -f; dan jika itu juga tidak ada, direktori saat ini. Compose mengganti placeholder ${NAME} yang ditemukan dalam nilai YAML. Key tidak pernah diinterpolasi, sedangkan $$ ganda menghasilkan tanda dolar literal, bukan substitusi.
Selanjutnya, jalankan service tersebut.
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 compose file, bukan prosesnya. File .env yang berisi POSTGRES_PASSWORD=hunter2 sama sekali tidak berpengaruh pada database Anda, kecuali ada bagian dari compose file yang mereferensikannya.
${NAME:-default} memberikan nilai cadangan ketika variabel tidak ditetapkan atau bernilai kosong. ${NAME:?message} membuat Compose menolak untuk start dan mencetak pesan Anda. Ini adalah pilihan yang tepat untuk nilai yang tidak memiliki default yang aman.
env_file memuat variabel ke dalam container
Atribut env_file: menetapkan 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 berupa baris KEY=value biasa, satu per baris, dengan # sebagai awal komentar. Format ini bukan shell, meskipun Compose menguraikannya dengan beberapa aturan yang serupa shell. Tanda kutip di sekitar nilai dihapus, sehingga KEY="value" dan KEY=value menghasilkan nilai yang sama. Nilai yang diapit tanda kutip ganda dan nilai tanpa tanda kutip akan menjalani interpolasi placeholder ${...}, sedangkan nilai yang diapit tanda kutip tunggal digunakan apa adanya. Awalan export diterima, tetapi tidak diperlukan. Spasi di sekitar tanda = dihapus. Perhatikan komentar inline: pada nilai tanpa tanda kutip, spasi yang diikuti # memulai komentar. Karena itu, apit tanda kutip pada nilai yang memuat #.
Path env_file yang tidak ada menyebabkan error dan Compose 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 diterima: bentuk pemetaan di atas dan bentuk daftar yang menggunakan - GREETING=from_environment. Keduanya berperilaku sama. Keduanya memiliki satu fitur tambahan: kunci tanpa nilai meneruskan variabel dari shell tempat Anda menjalankan docker compose. Dalam bentuk daftar, sintaksnya adalah - GREETING tanpa nilai. Dalam bentuk pemetaan, sintaksnya adalah GREETING: tanpa apa pun setelah titik dua.
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 pass-through yang tidak terlihat perlu diperhatikan, karena service yang berjalan dengan variabel kata sandi kosong sering kali berhasil start dan pada dasarnya 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 dalam environment:, dan kata sandi tersebut disimpan di konfigurasi container pada disk. Nilainya dapat dilihat oleh pengguna mana pun 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 berisi "DB_PASSWORD=hunter2" dalam teks biasa. Tiga path lain juga mengekspos nilai yang sama. docker compose config mencetaknya ke terminal. Inilah cara nilai tersebut dapat ditempelkan ke forum dukungan. Proses anak mewarisi variabel tersebut secara default, kecuali proses induk sengaja menjalankannya dengan environment pengganti. Proses yang berjalan sebagai pengguna yang sama dengan PID 1, atau yang memiliki CAP_SYS_PTRACE, dapat membacanya dari /proc/1/environ. Dalam container yang seluruh prosesnya berjalan sebagai root, hal ini berlaku untuk setiap proses. Aplikasi yang berjalan sebagai pengguna tanpa hak istimewa, sementara PID 1 berjalan sebagai root, akan menerima error izin saat mengakses file tersebut. Namun, variabel itu tetap ada dalam environment milik aplikasi tersebut. Selain itu, handler crash aplikasi biasanya menulis seluruh environment ke dalam log atau laporan error.
Keanggotaan dalam grup docker pada dasarnya memberikan hak root pada host. Karena itu, grup ini bukan batas hak istimewa yang dapat diandalkan. Panduan akun pengguna dengan hak minimum pada VPS menjelaskan alasan grup tersebut perlu dibatasi pada server bersama mana pun.
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 menggantikan placeholder ${NAME} dalam file compose dan juga dapat menetapkan variabel milik Compose, seperti COMPOSE_PROJECT_NAME atau COMPOSE_FILE. File tersebut tidak pernah menetapkan variabel di dalam container. Agar nilainya masuk ke container, referensikan dengan environment: { KEY: "${NAME}" } atau gunakan env_file: ./that-file.env.
Apakah environment mengoverride env_file, atau sebaliknya?
environment: yang berlaku. 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.
Bagaimana cara melihat nilai akhir yang akan digunakan Compose?
Jalankan docker compose config untuk menampilkan file compose yang sudah sepenuhnya di-resolve dengan semua interpolasi yang diterapkan. Untuk container yang sudah berjalan, docker inspect <container> --format '{{json .Config.Env}}' menampilkan secara tepat apa 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 berada di disk host tanpa enkripsi. 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?
Ya. Compose menghapus tanda kutip di sekeliling nilai, sehingga KEY="value with spaces" dan KEY=value with spaces sama-sama menghasilkan value with spaces. Gunakan tanda kutip tunggal jika nilainya berisi $ yang tidak boleh diinterpolasi, dan kutip nilai apa pun yang berisi # karena tanpa tanda kutip, karakter tersebut mengawali komentar. Spasi di sekitar = akan dihapus, sehingga KEY = value tetap menetapkan KEY.