Parolalar, API anahtarları ve token’lar… Container dünyasında bunları yanlış yere koymanın en kolay yolu ENV ya da ARG kullanmaktır, ve en tehlikeli yolu da budur. Bu yazıda önce neden tehlikeli olduğunu gerçek bir deneyle görüyor, sonra gizli bilgiyi build sırasında (BuildKit secret mount) ve çalışma sırasında (Compose secrets) güvenle nasıl kullanacağınızı öğreniyoruz.
Neden ARG ve ENV güvenli değil?
Docker dokümantasyonu bunu açıkça belirtir: build argümanları ve ortam değişkenleri, nihai imajda kalıcı olarak saklandıkları için gizli bilgi aktarmaya uygun değildir. Bunu doğrulamak için şu Dockerfile’ı build ettik:
FROM temel-imaj
ARG API_TOKEN
ENV TOKEN_ENV=$API_TOKEN
RUN echo "kuruldu"
$ docker build --build-arg API_TOKEN=s3cr3t-token-123 -t sec:bad .
İmajı inceleyen herkes — imajı çekebilen herkes — token’ı görebilir:
$ docker history --no-trunc sec:bad --format '{{.CreatedBy}}' | grep TOKEN
RUN |1 API_TOKEN=s3cr3t-token-123 /bin/sh -c echo "kuruldu" # buildkit
ENV TOKEN_ENV=s3cr3t-token-123
ARG API_TOKEN=s3cr3t-token-123
$ docker inspect sec:bad --format '{{.Config.Env}}'
[PATH=... TOKEN_ENV=s3cr3t-token-123]
Token hem katman geçmişinde hem de imajın yapılandırmasında duruyor. İmajı ENV olmadan, yalnızca ARG ile build etseniz bile değer katman geçmişinde kalır. Modern Docker bunu build sırasında da uyarır:
WARN: SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions
for sensitive data (ARG "API_TOKEN") (line 2)
Build sırasında: BuildKit secret mount
Çözüm, sırrı imaja hiç yazmadan, yalnızca ihtiyaç duyan RUN komutuna geçici olarak bağlamaktır:
FROM temel-imaj
RUN --mount=type=secret,id=api_token \
TOKEN=$(cat /run/secrets/api_token) && ozel-paketi-indir "$TOKEN"
# Dosyadan
docker build --secret id=api_token,src=token.txt .
# Ortam değişkeninden
docker build --secret id=api_token,env=API_TOKEN .
Sır varsayılan olarak /run/secrets/<id> yoluna yalnızca o RUN adımı süresince bağlanır. Başka bir hedef yol (target=) ya da ortam değişkeni olarak (env=) bağlamak da mümkündür:
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials aws s3 cp ...
RUN --mount=type=secret,id=aws-key-id,env=AWS_ACCESS_KEY_ID aws s3 cp ...
Doğrulama denemesinde 16 karakterlik bir token’ı bu yöntemle okuttuk ve adımın çıktısında yalnızca uzunluğu yazdırdık. Ardından imajın içinde ve katman geçmişinde token’ı aradık:
$ docker run --rm sec:ok ls /run/secrets
ls: cannot access '/run/secrets': No such file or directory
$ docker history --no-trunc sec:ok | grep -c s3cr3t
0
$ docker save sec:ok | tar -xO | grep -c s3cr3t
0
Sır nihai imajda yok: /run/secrets dizini mevcut değil, geçmişte ve kaydedilmiş imaj arşivinde hiç eşleşme bulunmadı. Özel bir Git deposuna erişim gibi durumlar için benzer şekilde RUN --mount=type=ssh ve docker buildx build --ssh default . kullanılabilir.
Sırrı güvenle bağlasanız bile, komutunuzun kendisi sırrı bir dosyaya yazarsa, çıktıya basarsa ya da yan etki olarak imaja kaydederse yine sızar. Secret mount, sırrı katmana yazılmaktan korur, komutun kendi davranışından değil.
Çalışma sırasında: Compose secrets
Çalışan container için gizli bilgiler, ortam değişkeni yerine bir dosya olarak bağlanmalıdır. Compose’ta secrets bölümü bunu yapar; container içinde /run/secrets/<ad> altında salt okunur bir dosya olarak görünür:
services:
db:
image: postgres:18
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_parola
secrets:
- db_parola
secrets:
db_parola:
file: ./secrets/db_parola.txt
Birçok resmi imaj (PostgreSQL, MySQL, MariaDB gibi) bu yaklaşım için *_FILE ortam değişkenlerini destekler: parola değişkenin kendisinde değil, gösterdiği dosyada durur. Böylece docker inspect çıktısında ve süreç ortamında düz metin parola görünmez.
| Yöntem | İmaja sızar mı? | docker inspect‘te görünür mü? |
|---|---|---|
ARG / ENV (build) |
Evet | Evet |
docker run -e PAROLA=... |
Hayır | Evet (Config.Env) |
| BuildKit secret mount | Hayır | Yok (yalnızca build’de) |
| Compose secrets (dosya) | Hayır | Hayır — dosya olarak bağlanır |
Üretim için notlar
- Sır dosyalarını Git’e koymayın.
secrets/klasörünü.gitignoreve .dockerignore içine ekleyin. - Sızan sırrı yenileyin. Bir token imaja ya da repoya girdiyse silmek yetmez; iptal edip yenisini üretin — geçmişte kalmış olabilir.
- Daha büyük sistemlerde Docker Swarm secrets, Kubernetes Secrets ya da bir gizli bilgi yöneticisi (HashiCorp Vault, bulut sağlayıcı KMS/Secrets Manager) kullanın. Swarm tarafı için Docker Swarm yazısına bakın.
- CI’da sırları pipeline’ın gizli depolama özelliğinden verin ve
--secret id=...,env=...ile build’e aktarın (bkz. CI/CD ile Docker).
Hızlı kontrol listesi
- Dockerfile’da
ARG/ENVile parola, token ya da anahtar var mı? (Varsa kaldırın.) - Build’de ihtiyaç duyulan sırlar
RUN --mount=type=secretile mi veriliyor? - Çalışan servisler parolayı dosya (
secrets,*_FILE) olarak mı alıyor? docker history --no-truncçıktısında hassas bir şey görünüyor mu?
İmaj güvenliğinin geniş çerçevesi için Container Image Güvenliği yazısına ve sitemizdeki Güvenlik Merkezi aracına bakın.