SSD Nodes Learn RAM 8GB — $66/tahun
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-01

Docker Compose: Perbedaan .env, env_file, dan secrets

Pahami perbedaan .env, env_file, dan environment di Docker Compose, urutan prioritas saat key sama, serta alasan password sebaiknya disimpan sebagai secrets.

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 memproses file tersebut. 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 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 secara umum, mulai dengan Dasar-dasar Docker Compose di VPS, lalu kembali ke sini untuk mempelajari konfigurasi.

File .env digunakan untuk compose file, bukan container

Buat sebuah direktori dan masukkan dua file ke dalamnya.

mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .env
services:
  demo:
    image: alpine:${ALPINE_TAG}
    command: printenv ALPINE_TAG

Sekarang minta Compose menampilkan hasil parsing yang sebenarnya.

docker compose config

Output tersebut menampilkan image: alpine:3.20. Placeholder sudah hilang karena interpolasi dilakukan saat parsing. Compose mencari .env di direktori project, yaitu direktori yang berisi compose file, lalu mengganti setiap ${NAME} yang ditemukan.

Selanjutnya, jalankan service.

docker compose run --rm demo

printenv ALPINE_TAG keluar dengan status 1 dan tidak menampilkan apa pun. Variabel tersebut tidak ada di dalam container. Inilah kesalahpahaman yang paling umum: .env mengonfigurasi compose file, bukan process. File .env yang berisi POSTGRES_PASSWORD=hunter2 sama sekali tidak berpengaruh pada database Anda, kecuali ada bagian dari compose file yang mereferensikannya.

${NAME:-default} menyediakan nilai fallback saat variabel tidak disetel atau nilainya kosong. ${NAME:?message} membuat Compose menolak untuk memulai dan menampilkan 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.env
services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
docker compose run --rm demo

Perintah ini mencetak from_env_file. Format file ini terdiri atas baris KEY=value biasa, satu entri per baris, dengan # di awal baris untuk menandai komentar. Format ini bukan sintaks shell. Dalam sebagian besar kasus, tanda kutip tetap menjadi bagian dari nilai, dan awalan export tidak diperlukan. Jangan menempatkan spasi di sekitar tanda =, karena KEY = value menghasilkan variabel yang secara harfiah bernama KEY dengan spasi di awal nilainya.

Jika jalur env_file tidak ditemukan, hal ini merupakan kesalahan dan Compose berhenti. Tandai file tersebut sebagai opsional jika file itu memang boleh tidak ada:

    env_file:
      - path: ./app.env
        required: false

environment menetapkan variabel secara inline

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    environment:
      GREETING: from_environment

Dua sintaks diterima, yaitu bentuk pemetaan di atas dan bentuk daftar yang menggunakan - GREETING=from_environment. Keduanya berperilaku sama. Bentuk daftar memiliki satu trik tambahan: kunci tanpa nilai meneruskan variabel dari shell tempat Anda menjalankan docker compose.

    environment:
      - GREETING
GREETING=from_my_shell docker compose run --rm demo

Perintah tersebut mencetak from_my_shell. Jika Anda menjalankannya tanpa menetapkan GREETING di shell, Compose tidak menetapkan apa pun dan tidak menampilkan peringatan. Kegagalan penerusan secara diam-diam perlu diperhatikan. Layanan yang dimulai dengan variabel kata sandi kosong sering kali tetap berhasil dimulai dan tidak terlindungi sama sekali.

Yang mana yang menang

Docker mendokumentasikan urutan prioritas, dari yang tertinggi: docker compose run -e pada baris perintah, lalu environment atau env_file yang nilainya diinterpolasi dari shell atau dari file env, kemudian environment biasa dalam file compose, lalu env_file, dan terakhir direktif ENV yang sudah ditanamkan ke dalam image.

Ringkasan untuk pekerjaan sehari-hari: environment: mengalahkan env_file:, sedangkan -e pada baris perintah mengalahkan keduanya. Buktikan dalam satu file.

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
    environment:
      GREETING: from_environment
docker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING

Perintah pertama mencetak from_environment, sehingga environment: menimpa nilai dalam app.env. Perintah kedua mencetak from_cli. Tidak ada konfigurasi dalam file compose yang menimpa baris perintah.

Jika container berperilaku seolah-olah konfigurasi Anda tidak pernah 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 bocor

Tetapkan kata sandi dalam environment: dan kata sandi tersebut disimpan dalam konfigurasi container di disk. Nilainya 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 yang menyebabkan nilai tersebut akhirnya ditempelkan ke forum dukungan. Proses apa pun di dalam container dapat membaca /proc/1/environ, dan setiap proses anak mewarisi variabel tersebut. Selain itu, penangan crash aplikasi sering membuang seluruh lingkungan ke dalam log atau laporan kesalahan.

Keanggotaan dalam grup docker pada dasarnya memberikan hak root pada host. Karena itu, grup ini bukan batas hak istimewa yang dapat diandalkan. Panduan tentang akun pengguna dengan hak istimewa minimum di VPS menjelaskan alasan grup tersebut perlu dibatasi pada sistem bersama mana pun.

Secret Compose menyimpan nilai dalam file

Compose mendukung secret berbasis file. Nilainya dipasang ke dalam container sebagai file, bukan disuntikkan ke lingkungan.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt

Secret dipasang di /run/secrets/db_password dalam container. Nama setelah garis miring adalah nama secret dari blok tingkat teratas secrets:.

Sufiks _FILE adalah 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 menganggap SOMETHING_FILE akan dipatuhi. Aplikasi yang tidak mendukungnya biasanya dapat membaca file tersebut sendiri saat dimulai, atau Anda dapat meneruskan path dan membiarkan entrypoint Anda sendiri yang memprosesnya.

Verifikasi dari dalam container yang sedang berjalan:

docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORD

Perintah pertama menampilkan password. Perintah kedua tidak menampilkan apa pun karena nilainya tidak pernah masuk ke lingkungan. Itulah tujuannya: docker inspect pada container ini hanya menampilkan path yang tidak berbahaya.

Lindungi file sumber di host karena secret hanya bersifat privat selama file yang mendasarinya tetap terlindungi:

chmod 600 db_password.txt

Jalan tengah yang pragmatis pada VPS

Banyak image yang dihosting sendiri tidak mendukung variabel _FILE, sehingga variabel lingkungan adalah satu-satunya cara untuk memasukkan nilai. Pada satu VPS dengan satu administrator, tujuan yang realistis adalah mencegah nilai tersimpan dalam file yang dapat dibaca semua pengguna di direktori proyek, serta 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.env

install -m 600 membuat file dengan mode yang sudah ditetapkan, sehingga tidak ada jeda ketika file dapat dibaca semua pengguna. root menjadi pemiliknya, sehingga pengguna non-root pada server tidak dapat membacanya, meskipun siapa pun yang dapat menjalankan docker tetap dapat membaca nilainya dari dalam container. Tambahkan *.env dan .env ke .gitignore, lalu commit app.env.example yang berisi nama-nama kunci dengan nilai kosong sebagai gantinya. Password yang di-commit harus segera dirotasi.

Merotasi nilai berarti memulai ulang service. Variabel lingkungan dibaca satu kali saat proses container dimulai, sehingga 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, tempat kunci enkripsi disimpan di luar file compose.

Memisahkan konfigurasi berdasarkan lingkungan

Compose membaca .env dari direktori proyek secara default. Arahkan ke lokasi lain dengan --env-file.

docker compose --env-file .env.staging config

Beberapa file dibaca secara berurutan. File berikutnya akan menimpa nilai dari file sebelumnya. Simpan nilai default yang tidak rahasia dalam file yang di-commit, dan simpan rahasia dalam file yang tidak pernah keluar dari server. Hal yang sama berlaku untuk env_file:. Jika sebuah kunci muncul lebih dari sekali, nilai dari 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. Untuk memasukkan nilainya ke dalam container, referensikan variabel tersebut: 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 baris perintah. Jika suatu kunci ditetapkan di kedua tempat, nilai dalam env_file tidak digunakan dan tidak ada pesan yang ditampilkan.

Bagaimana cara melihat nilai akhir yang akan digunakan Compose?

Jalankan docker compose config untuk mencetak 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>, dan 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?

Gunakan KEY=value with spaces dan jangan gunakan tanda kutip. Compose menganggap seluruh bagian baris setelahnya sebagai nilai, sehingga tanda kutip biasanya menjadi karakter literal dalam nilai tersebut. Jangan pernah menempatkan spasi di sekitar =, karena kunci tersebut akan memiliki spasi di akhir dan tidak ada yang cocok dengannya.