Variabel lingkungan di container Docker terlihat dalam teks biasa melalui docker inspect, sehingga siapa pun yang memiliki akses ke Docker daemon dapat membaca nilai seperti kata sandi database dan kunci API. Nilainya juga terlihat di /proc/1/environ di dalam container dan bisa muncul di log pipeline CI/CD saat perintah echo atau debug dijalankan. Dokumentasi Docker sendiri menyatakan bahwa variabel lingkungan tidak sesuai untuk data sensitif.
Post ini merekomendasikan Mozilla SOPS dengan enkripsi age sebagai pendekatan paling pragmatis untuk setup Docker Compose tanpa Swarm. Anda menyimpan file secret terenkripsi di repositori git, lalu mendekripsinya ke file .env pada waktu deployment menggunakan kunci deploy yang disimpan di GitHub Actions secrets atau variabel GitLab CI. File .env teks biasa hanya ada di memori selama deployment dan tidak pernah ditulis ke disk.
Docker Swarm menyimpan secret yang dienkripsi saat istirahat di log Swarm Raft dan memasangnya sebagai file di /run/secrets/ di dalam container menggunakan filesystem berbasis memori (tmpfs). Karena tmpfs menghilang saat container berhenti, secret tidak pernah tersimpan di filesystem container dan tidak pernah terlihat dalam output docker inspect.
Secret waktu build tidak boleh pernah diteruskan melalui instruksi ARG atau ENV di Dockerfile, karena nilai-nilai tersebut dipanggang ke lapisan image dan tetap terlihat di docker history. Sebagai gantinya, gunakan mount secret BuildKit Docker dengan sintaks RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm install, yang membuat secret hanya tersedia selama satu langkah RUN tersebut dan tidak meninggalkan jejak di lapisan image mana pun.
Post ini mencantumkan HashiCorp Vault (self-hosted, open source, kaya fitur), AWS Secrets Manager (terkelola, terintegrasi dengan ECS dan EKS), GCP Secret Manager (terkelola, terintegrasi dengan Cloud Run dan GKE), dan Infisical (open source, disebut sebagai alternatif Vault dengan UX yang lebih baik). Alat-alat ini menyediakan kontrol akses, audit logging, rotasi otomatis, dan versioning yang tidak bisa ditawarkan oleh pendekatan berbasis file.
Variabel lingkungan di container Docker terlihat dalam teks biasa melalui docker inspect — siapa pun yang memiliki akses ke Docker daemon atau runtime container dapat membaca setiap variabel lingkungan, termasuk kata sandi database, kunci API, dan JWT secret. Studi GitGuardian 2023 menemukan bahwa lebih dari 10 juta secret bocor di GitHub dalam tahun itu saja. Di Commsult Indonesia, setelah mengaudit konfigurasi Docker awal kami dan menemukan kredensial teks biasa dalam output docker inspect dan log CI/CD, kami merombak total manajemen secret kami.
Meneruskan secret sebagai variabel lingkungan memiliki beberapa vektor eksposur: docker inspect menampilkan semua variabel lingkungan dalam teks biasa, variabel lingkungan terlihat di /proc/1/environ di dalam container, perintah docker run dengan secret muncul di riwayat shell, dan log pipeline CI/CD dapat menangkap nilai variabel lingkungan pada perintah echo atau debug.
Docker Swarm memiliki mekanisme secret bawaan: docker secret create menyimpan secret yang dienkripsi saat istirahat di log Swarm Raft, hanya dapat diakses oleh layanan yang secara eksplisit memintanya. Secret dipasang sebagai file di /run/secrets/ di dalam container menggunakan filesystem berbasis memori (tmpfs) yang menghilang saat container berhenti.
Docker Compose juga mendukung secret melalui kunci secrets di docker-compose.yml. Anda mendefinisikan sumber secret (file atau secret Swarm eksternal) dan memasangnya ke layanan tertentu. Untuk pengembangan, gunakan secret berbasis file (file lokal yang tidak ada di git). Untuk produksi, gunakan kata kunci external untuk mereferensikan secret Swarm.
┌─────────────────────────────────────────────────────┐
│ SECRETS EXPOSURE COMPARISON │
└─────────────────────────────────────────────────────┘
ENV VAR approach (INSECURE):
docker run -e DB_PASS=secret123 ...
docker inspect container → plaintext visible
/proc/1/environ → plaintext visible
CI/CD debug logs → plaintext leaks
Docker Secrets approach (SECURE):
docker secret create db_pass /run/secrets/db_pass
Encrypted in Swarm Raft log
Mounted as tmpfs: /run/secrets/db_pass
docker inspect → NOT visible
Container reads: fs.readFileSync('/run/secrets/db_pass')
SOPS + age (GitOps-friendly):
secrets.enc.yaml ──(age decrypt)──► .env (CI only)
Encrypted in git Never written to diskDari pengalaman mengamankan deployment NestJS produksi di Commsult Indonesia, pendekatan paling pragmatis untuk setup Docker Compose tanpa Swarm adalah Mozilla SOPS dengan enkripsi age. Simpan file secret terenkripsi di repositori git Anda (aman karena terenkripsi), dekripsi ke file .env pada waktu deployment di pipeline CI/CD Anda menggunakan kunci deploy yang disimpan di GitHub Actions secrets atau variabel GitLab CI.
Pola secret yang paling berlaku secara universal adalah berbasis file: simpan secret sebagai file dengan izin restriktif (600, dimiliki oleh root atau pengguna service), dan minta aplikasi membaca file secret alih-alih variabel lingkungan. Sebagian besar pustaka klien mendukung ini: Node.js dapat membaca file dengan fs.readFileSync. Simpan file secret di /run/secrets/ (tmpfs) atau /etc/myapp/secrets/.
Secret waktu build (token registry npm, kredensial paket privat) memerlukan penanganan khusus. Jangan pernah gunakan ARG atau ENV di Dockerfile untuk secret — mereka dipanggang ke lapisan image dan terlihat di docker history. Gunakan mount secret BuildKit Docker: RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm install.
# docker-compose.yml with secrets
services:
app:
image: myapp:latest
secrets:
- db_password
- jwt_secret
environment:
NODE_ENV: production
DB_HOST: db
secrets:
db_password:
file: ./secrets/db_password.txt # dev
# external: true # prod (Swarm)
jwt_secret:
file: ./secrets/jwt_secret.txt
# In Node.js, read secret from file
const dbPass = require('fs').readFileSync('/run/secrets/db_password', 'utf8').trim()
# Dockerfile BuildKit secret (build-time)
# RUN --mount=type=secret,id=npmrc,target=/root/.npmrc # npm install
# Audit current exposure
docker inspect $(docker ps -q) | grep -i -E "password|secret|key|token"
# Scan git history for leaked secrets
trufflehog git file://. --only-verifiedUntuk tim yang lebih besar atau lingkungan yang sensitif terhadap kepatuhan, gunakan manajer secret khusus: HashiCorp Vault (self-hosted, open source, kaya fitur), AWS Secrets Manager (terkelola, terintegrasi dengan ECS dan EKS), GCP Secret Manager (terkelola, terintegrasi dengan Cloud Run dan GKE), atau Infisical (open source, alternatif Vault dengan UX yang lebih baik).
Saya telah melihat tim menggunakan docker-compose.override.yml untuk secret lokal, git-ignore-nya, lalu secara tidak sengaja melakukan commit ketika entri gitignore dihapus atau file diganti nama. Bahkan satu file secret yang di-commit bisa menjadi kebocoran permanen — riwayat git menyimpannya bahkan setelah penghapusan. Gunakan file secret terpisah yang di-gitignore secara eksplisit, dokumentasikan variabel yang diperlukan dalam file .env.example dengan nilai placeholder.
Di Commsult Indonesia, stack secret produksi kami: SOPS dengan enkripsi age untuk mengenkripsi secret di git (kunci disimpan di 1Password dan secret platform CI/CD), Docker Compose secrets untuk injeksi secret runtime (file secret dipasang di /run/secrets/), dan file .env terpisah per lingkungan (dihasilkan oleh dekripsi SOPS di CI/CD, tidak pernah di-commit).
Sebelum membangun solusi secret yang lebih baik, audit apa yang sudah terekspos. Jalankan docker inspect terhadap container yang berjalan dan grep untuk pola password, secret, key, dan token untuk memeriksa secret di variabel lingkungan. Tinjau file docker-compose.yml Anda untuk secret yang di-hardcode. Periksa riwayat git Anda untuk file .env yang tidak sengaja di-commit.