Docker & DevOps

    Docker'da Gizli Bilgi (Secret) Yönetimi

    Parola ve API anahtarlarını konteynerlere sızdırmadan taşımanın dosya ve Swarm tabanlı yolları.

    11 dk okuma Güncellendi: 25 Ağustos 2026

    Docker'da parola saklamanın en yaygın yolu ortam değişkenidir ve tam da bu yüzden en yaygın sızıntı kaynağıdır. POSTGRES_PASSWORD değerini compose.yaml içine yazarsınız, dosya depoya girer; ya da .env dosyasına koyarsınız, birisi onu yanlışlıkla commit eder. Sunucuya bağlanan herhangi biri docker inspect ile o değeri düz metin okur, hata ayıklarken aldığınız docker compose config çıktısı ekranda parolayı gösterir, uygulamanız çöktüğünde ürettiği yığın izinde ortam değişkenleri bir kez daha görünür. Hiçbiri bir saldırı değildir; hepsi normal iş akışının doğal sonucudur.

    Docker'ın gizli bilgi yönetimi için üç ayrı mekanizması vardır ve hangisini ne zaman kullanacağınız çoğu zaman karıştırılır: dosya tabanlı Compose secret'ları, Swarm'ın şifreli secret deposu ve derleme zamanı için BuildKit secret'ları. Bu rehberde önce ortam değişkeninin tam olarak nerede sızdığını göstereceğim, sonra bu üç mekanizmayı gerçek örneklerle kuracağız; ayrıca resmi imajların _FILE kalıbını, gizli bilgi rotasyonunu, harici bir kasaya geçiş yolunu ve en sık düşülen tuzakları ele alacağım.

    Ortam Değişkeni Neden Gizli Bilgi İçin Kötü Bir Yer#

    Ortam değişkeninin sorunu şifrelenmemiş olması değil; çok fazla yerden okunabilir olmasıdır. Somut listeye bakalım:

    # 1) Konteyner meta verisinden duz metin olarak okunur
    docker inspect firmaniz-db --format='{{range .Config.Env}}{{println .}}{{end}}'
    # POSTGRES_PASSWORD=cok-gizli-parola
    
    # 2) Konteyner icindeki HER sureç ortami miras alir
    docker exec firmaniz-db env | grep PASSWORD
    
    # 3) Ayni kullanicinin acabildigi surec dosyasindan okunur
    docker exec firmaniz-db cat /proc/1/environ | tr '\0' '\n'
    
    # 4) Compose'un cozulmus yapilandirmasi ekrana basar
    docker compose config | grep -i password
    

    Bu dört yol da Docker'ın hatası değil, tasarımıdır. Ortam değişkeni süreçlere aktarılmak için vardır ve aktarıldığı her yerden okunabilir. Buna bir de üç dolaylı yol eklenir: uygulamanız bir istisna yakaladığında hata izleme aracına ortam değişkenlerini gönderebilir, konteyner çöktüğünde alınan bellek dökümü değeri içerir ve docker inspect çıktısını destek talebine ekleyen bir ekip arkadaşınız parolayı üçüncü tarafa iletmiş olur.

    Dosya tabanlı yaklaşımın kazandırdığı şey tam olarak budur: değer bir dosyada durur, yalnızca o dosyayı okuyabilen süreç görür, docker inspect çıktısında yalnızca dosyanın yolu görünür ve hata izleme araçları ortam değişkeni toplarken hiçbir şey bulamaz. Depoya sızmış bir gizli bilgiyi nasıl temizleyeceğinizi merak ediyorsanız .env dosyası ifşası yazısında bu kazanın ardından atılması gereken adımları anlattım.

    Dosya Tabanlı Gizli Bilgi ve _FILE Kalıbı#

    Resmi Docker imajlarının büyük kısmı bir sözleşmeyi destekler: bir ortam değişkeninin sonuna _FILE ekleyip değer yerine dosya yolu verirsiniz, imajın giriş betiği o dosyayı okuyup değeri kendisi kullanır. Postgres, MySQL, MariaDB, Redis ve daha birçok imaj bu kalıbı destekler ve gizli bilgi yönetimine geçmenin en düşük maliyetli yoludur.

    Değişken_FILE karşılığıİmaj
    POSTGRES_PASSWORDPOSTGRES_PASSWORD_FILEpostgres
    MYSQL_ROOT_PASSWORDMYSQL_ROOT_PASSWORD_FILEmysql, mariadb
    MYSQL_PASSWORDMYSQL_PASSWORD_FILEmysql, mariadb
    POSTGRES_USERPOSTGRES_USER_FILEpostgres

    Kendi uygulamanızda da aynı kalıbı uygulamanızı öneririm; on satırlık bir yardımcı fonksiyon yeterlidir:

    # Uygulamanizin giris betiginde: once _FILE, yoksa duz degisken
    if [ -n "$DB_PASSWORD_FILE" ] && [ -r "$DB_PASSWORD_FILE" ]; then
      DB_PASSWORD="$(cat "$DB_PASSWORD_FILE")"
      export DB_PASSWORD
      unset DB_PASSWORD_FILE
    fi
    exec "$@"
    

    Bu yaklaşımın küçük bir uyarısı var: değeri ortam değişkenine geri yazdığınız anda yukarıdaki okunabilirlik sorunları kısmen geri döner. En temizi, uygulamanızın parolayı doğrudan dosyadan okumasıdır. Yine de _FILE kalıbı, parolanın depoda ve Compose meta verisinde görünmemesini sağladığı için tek başına ciddi bir iyileşmedir.

    Compose'da secrets Bloğu#

    Compose, Swarm dışında da gizli bilgi desteği sunar; bu modda secret basitçe konteynerin içine salt okunur olarak bağlanan bir dosyadır. Hedef yol varsayılan olarak /run/secrets/ altındaki secret adıdır.

    services:
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_USER: firmaniz
          POSTGRES_PASSWORD_FILE: /run/secrets/db_parola
        secrets:
          - db_parola
        volumes:
          - dbdata:/var/lib/postgresql/data
    
      app:
        image: firmaniz/web:1.4.0
        environment:
          DB_PASSWORD_FILE: /run/secrets/db_parola
          SMTP_TOKEN_FILE: /run/secrets/smtp_token
        secrets:
          - db_parola
          - source: smtp_token
            target: smtp_token       # /run/secrets/smtp_token
            mode: 0400               # sadece sahibi okusun
    
    secrets:
      db_parola:
        file: ./secrets/db_parola.txt
      smtp_token:
        file: ./secrets/smtp_token.txt
    
    volumes:
      dbdata:
    

    Dosyaları oluştururken izinlere dikkat edin; sunucudaki gizli bilgi dosyası dünyaya okunur olmamalıdır:

    mkdir -p /srv/firmaniz/secrets && chmod 700 /srv/firmaniz/secrets
    # Guclu bir parola uret ve dosyaya yaz - satir sonu EKLEME
    printf '%s' "$(openssl rand -base64 32)" > /srv/firmaniz/secrets/db_parola.txt
    chmod 600 /srv/firmaniz/secrets/db_parola.txt
    
    # Dogrula: konteyner icinde dosya var mi
    docker compose exec app ls -l /run/secrets/
    docker compose exec app wc -c /run/secrets/db_parola
    

    Sondaki satır sonu meselesi göründüğünden önemlidir. echo komutu dosyanın sonuna bir \n ekler ve birçok uygulama bu karakteri parolanın parçası sanar; sonuç, hiçbir yerde görünmeyen bir kimlik doğrulama hatasıdır. printf '%s' kullanmak ya da tr -d '\n' ile temizlemek bu tuzağı kapatır. Secret dosyalarının bulunduğu klasörü .gitignore ve .dockerignore içine eklemeyi de unutmayın; nasıl yapılacağını Compose profilleri ve ortam değişkenleri yazısındaki dosya düzeninde bulabilirsiniz.

    Swarm Secret: Şifreli Dağıtım ve tmpfs#

    Docker Swarm modunda secret'lar bambaşka bir mekanizmaya kavuşur ve bu, tek makineyi aşan kurulumlarda en sağlam yerleşik çözümdür. Secret, yönetici düğümlerdeki dağıtık depoda şifreli olarak saklanır, yalnızca o secret'a ihtiyaç duyan görevlerin çalıştığı düğümlere TLS üzerinden gönderilir ve hedef düğümde belleğe bağlanır (tmpfs), yani diske hiç yazılmaz. Görev sonlandığında bellekteki kopya da kaybolur.

    # Swarm'i baslat (henuz baslatilmadiysa)
    docker swarm init --advertise-addr 185.12.34.56
    
    # Secret olustur: dosyadan ya da standart girdiden
    docker secret create db_parola /srv/firmaniz/secrets/db_parola.txt
    printf '%s' "$(openssl rand -hex 24)" | docker secret create api_anahtari -
    
    # Listele - deger ASLA gosterilmez, sadece meta veri
    docker secret ls
    docker secret inspect db_parola --format='{{.Spec.Name}} {{.CreatedAt}}'
    
    # Servise bagla
    docker service create --name firmaniz-db \
      --secret db_parola \
      --env POSTGRES_PASSWORD_FILE=/run/secrets/db_parola \
      postgres:16-alpine
    

    Stack dosyasıyla dağıtıyorsanız external: true ile önceden oluşturulmuş secret'ı gösterirsiniz. Bu, gizli bilgiyi dağıtım dosyasından tamamen ayırdığı için üretimde tercih edilen biçimdir:

    services:
      db:
        image: postgres:16-alpine
        environment:
          POSTGRES_PASSWORD_FILE: /run/secrets/db_parola
        secrets:
          - db_parola
        deploy:
          replicas: 1
    
    secrets:
      db_parola:
        external: true      # docker secret create ile onceden olusturuldu
    

    Swarm kümesi kurmayı ve stack dağıtmayı hiç denemediyseniz Docker Swarm ile küme kurulumu yazısı adım adım bir başlangıç sunar.

    Derleme Zamanı Gizli Bilgi: BuildKit#

    Bazen gizli bilgiye çalışma zamanında değil, imajı derlerken ihtiyaç duyarsınız: özel bir paket deposundan çekmek, lisanslı bir bağımlılık indirmek, özel bir Git deposunu klonlamak. Burada yapılan klasik hata ARG kullanmaktır ve bu, gizli bilgiyi imaj geçmişine kalıcı olarak yazar:

    # Bu komut, ARG ile verilmis her degeri okunur bicimde gosterir
    docker history --no-trunc firmaniz/web:1.4.0 | grep -i token
    

    Doğru yöntem BuildKit'in secret bağlama mekanizmasıdır. Gizli değer yalnızca ilgili RUN komutu çalışırken geçici olarak bağlanır, hiçbir katmana yazılmaz ve docker history çıktısında iz bırakmaz:

    # syntax=docker/dockerfile:1
    FROM node:22-alpine AS deps
    WORKDIR /app
    COPY package.json package-lock.json ./
    # npm token'i sadece bu komut boyunca var olur
    RUN --mount=type=secret,id=npmtoken \
        sh -c 'npm config set //registry.firmaniz.com/:_authToken=$(cat /run/secrets/npmtoken) && npm ci && npm config delete //registry.firmaniz.com/:_authToken'
    
    # Gizli bilgiyi dosyadan besleyerek derle
    docker build --secret id=npmtoken,src=./.npmtoken -t firmaniz/web:1.4.0 .
    
    # Ortam degiskeninden de beslenebilir
    NPM_TOKEN=xxx docker build --secret id=npmtoken,env=NPM_TOKEN -t firmaniz/web:1.4.0 .
    

    Bu tekniği çok aşamalı derlemeyle birleştirdiğinizde, derleme aşamasında kullanılan hiçbir şey nihai imaja geçmez; ayrıntılar için multi-stage build yazısına bakabilirsiniz.

    Gizli Bilgi Rotasyonunu Planlamak#

    Bir parolanın ne kadar iyi saklandığından bağımsız olarak, onu değiştirebiliyor olmanız gerekir. Ekipten biri ayrıldığında, bir laptop kaybolduğunda ya da yıllık rutin olarak parolaları döndürmek zorundasınız. Buradaki en büyük engel teknik değil, süreçseldir: parolanın kaç yerde kullanıldığını bilmiyorsanız değiştiremezsiniz.

    Swarm secret'ları değiştirilemez (immutable). Bu kasıtlıdır ve rotasyonu şu adımlara indirger:

    1. Yeni secret'ı sürüm etiketli bir adla oluşturun: docker secret create db_parola_v2 dosya.
    2. Veritabanında yeni parolayı tanımlayın; eski parola bir süre daha geçerli kalsın.
    3. Servisi güncelleyin: docker service update --secret-rm db_parola --secret-add source=db_parola_v2,target=db_parola firmaniz-db.
    4. Tüm görevlerin yeni secret ile ayağa kalktığını doğrulayın.
    5. Eski parolayı veritabanından iptal edin ve docker secret rm db_parola ile eski secret'ı silin.

    target=db_parola demenin püf noktası şudur: konteyner içindeki dosya yolu değişmez, dolayısıyla imajı ya da yapılandırmayı hiç değiştirmeniz gerekmez. Yalnızca dosyanın arkasındaki değer yenilenir.

    Dosya tabanlı Compose kurulumunda rotasyon daha kabadır: dosyayı güncellersiniz ve konteyneri yeniden oluşturursunuz. docker compose up -d yeni secret dosyasını fark eder ve etkilenen servisleri yeniden yaratır; sadece restart yeterli değildir çünkü bağlama noktası konteyner oluşturulurken belirlenir.

    Harici Kasa Kullanmak#

    Ölçek büyüdüğünde secret'ları sunucudaki dosyalarda tutmak yönetilemez hâle gelir: kim erişti, ne zaman değişti, hangi ortamda hangi değer geçerli? Bu sorulara cevap veren araçlar mevcut ve Docker ile birlikte iki temel desende kullanılırlar.

    Birinci desen dağıtım anında enjeksiyondur: dağıtım hattınız kasadan değeri çeker, sunucuda geçici bir dosya oluşturur, Compose ya da Swarm secret'ı bundan üretir ve geçici dosyayı siler. Basit, ek çalışma zamanı bağımlılığı yoktur ve mevcut yapınızı değiştirmez.

    İkinci desen çalışma zamanında çekmedir: uygulama açılışta kasaya kendi kimliğiyle bağlanır ve gizli bilgileri oradan alır. Daha güçlüdür çünkü rotasyon uygulamayı yeniden başlatmadan yapılabilir; ama uygulamada kod değişikliği ve kasaya erişim için bir ilk kimlik (bootstrap secret) gerektirir, yani sorunu tümüyle ortadan kaldırmaz, daraltır.

    Depoya şifreli olarak commit edilen dosyalar (yaşa göre şifreleyen araçlar) üçüncü bir orta yoldur: gizli bilgi sürüm kontrolünde durur ama yalnızca anahtarı olan çözebilir. Küçük ekipler için pratik bir başlangıçtır. Hangisini seçerseniz seçin, iki kuralı bozmayın: gizli bilgi düz metin olarak depoya girmesin ve her gizli bilginin kimin erişebildiği belgelenmiş olsun.

    Sık Yapılan Hatalar#

    Secret dosyasını volumes ile bağlamak. volumes: ["./secrets:/run/secrets"] yazmak işe yarar gibi görünür ama tüm klasörü konteynere açar, izinleri korumaz ve Swarm'a geçtiğinizde bambaşka davranır. secrets bloğunu kullanın.

    Parola dosyasının sonundaki satır sonu. echo parola > dosya komutu sona \n ekler ve birçok uygulama bunu parolanın parçası sayar. printf '%s' kullanın ya da uygulamanızda okuduğunuz değeri kırpın.

    Secret'ı imaja gömmek. COPY secrets/db_parola.txt /app/ satırı gizli bilgiyi kalıcı olarak imaja yazar; imajı bir registry'ye gönderdiğinizde o registry'ye erişebilen herkese vermiş olursunuz. .dockerignore içine secret klasörünü eklemek bu hatanın kazara yapılmasını da engeller.

    Gizli bilgiyi loglamak. Hata ayıklarken eklenen bir console.log(config) satırı, tüm yapılandırmayı log dosyasına düz metin yazar. Log toplama sisteminiz varsa parola artık orada da vardır. Yapılandırma nesnesini bastırmadan önce gizli alanları maskeleyin.

    Aynı parolayı her ortamda kullanmak. Geliştirme, hazırlık ve üretim aynı veritabanı parolasını paylaşıyorsa, geliştirme makinesinden sızan bir değer doğrudan üretimi açar. Ortam başına ayrı değerler üretin.

    Rotasyonu hiç denememek. Rotasyon prosedürünü ilk kez gerçek bir sızıntı anında denemek en kötü senaryodur. Yılda bir kez, bilerek ve planlı biçimde bir parolayı döndürün; eksik adımları o zaman görürsünüz.

    Sıkça Sorulan Sorular#

    Docker secret Swarm olmadan kullanılabilir mi#

    Evet, Docker Compose dosya tabanlı secret'ları Swarm olmadan da destekler; secrets bölümünde file: kaynağı tanımlarsınız ve dosya konteynerin içine /run/secrets/ altına salt okunur bağlanır. Bu modda şifreli dağıtım ve tmpfs gibi Swarm'a özgü koruma katmanları yoktur; gizli bilgi sunucuda düz bir dosya olarak durur. Yine de ortam değişkenine göre belirgin bir iyileşmedir çünkü değer Compose meta verisinde ve docker inspect çıktısında görünmez.

    Ortam değişkeni gerçekten bu kadar riskli mi#

    Riskin büyüklüğü ortamınıza bağlıdır ama okunabilirlik yüzeyi tartışmasız geniştir: docker inspect, konteyner içindeki her süreç, /proc/1/environ, docker compose config çıktısı ve hata izleme araçları hepsi aynı değeri görebilir. Küçük ve tek kişilik bir kurulumda bu kabul edilebilir olabilir; birden fazla kişinin sunucuya eriştiği ya da hata izleme aracı kullandığınız her yerde dosya tabanlı yönteme geçmeye değer.

    /run/secrets altındaki dosyayı kim okuyabilir#

    Varsayılan olarak dosya salt okunur bağlanır ve sahibi root'tur. Konteyneriniz root olmayan bir kullanıcıyla çalışıyorsa dosyayı okuyamayabilir; bu durumda uid, gid ve mode alanlarını secret tanımında açıkça verirsiniz. Mümkün olan en dar izni seçin: yalnızca uygulamanın çalıştığı kullanıcı okuyabilsin, mode: 0400 bunun için uygundur.

    Parolayı imajın içine koymanın zararı ne#

    İmaj taşınabilir bir arşivdir; bir registry'ye gönderdiğinizde o registry'ye erişebilen herkes katmanları indirip içeriğini okuyabilir. Ayrıca katmanlar salt okunurdur, yani sonraki bir RUN rm komutu dosyayı gerçekten silmez, yalnızca üstünü örter ve önceki katmanda değer durmaya devam eder. Gizli bilgi imaja bir kez girdiyse tek güvenli çözüm o parolayı iptal edip yenisini üretmektir.

    Gizli bilgi rotasyonu ne sıklıkla yapılmalı#

    Zorunlu tetikleyiciler nettir: ekipten ayrılan biri, kaybolan bir cihaz, şüpheli erişim ya da bir sızıntı ihbarı. Rutin rotasyon için yılda bir kez makul bir başlangıçtır; kritik sistemlerde altı ay tercih edilir. Asıl önemli olan sıklık değil, prosedürün gerçekten çalışıyor olmasıdır; hiç denenmemiş bir rotasyon planı acil durumda genellikle yarıda kalır.

    CI hattında gizli bilgiyi nasıl saklamalıyım#

    CI sağlayıcınızın kendi gizli değişken deposunu kullanın ve bu değerleri log'a basmayın; çoğu sistem bilinen gizli değerleri çıktıda otomatik maskeler ama yalnızca tam eşleşmede. İmaj derlerken ARG yerine BuildKit'in --mount=type=secret mekanizmasını kullanın. Dağıtım adımında değeri sunucuya kopyalarken geçici dosya kullanın ve işlem bitince silin; kalıcı bırakmak istiyorsanız 600 izniyle ve yalnızca dağıtım kullanıcısının erişebildiği bir klasörde tutun.

    Secret değerini değiştirdim ama uygulama eskisini görüyor#

    Bağlama noktası konteyner oluşturulurken belirlenir; dosyayı güncellemek çalışan konteynerdeki içeriği her durumda yenilemez ve uygulamanız değeri açılışta bir kez okuduysa zaten hafızasındakini kullanır. Compose kurulumunda docker compose up -d ile etkilenen servisleri yeniden oluşturmanız gerekir, sadece restart yeterli olmayabilir. Swarm'da ise yeni bir secret oluşturup docker service update --secret-rm eski --secret-add yeni ile görevleri yenilersiniz.

    Kapanış#

    Gizli bilgi yönetiminde amaç mükemmel bir sistem kurmak değil, en kolay sızıntı yollarını kapatmaktır. Aklınızda tutmanız gereken dört alışkanlık şunlar: parolaları ortam değişkeni yerine dosyadan verin ve resmi imajların _FILE kalıbından yararlanın, secret dosyalarını 600 izniyle tutup depoya asla göndermeyin, derleme sırasında gizli bilgi gerekiyorsa ARG değil BuildKit secret kullanın ve rotasyon prosedürünüzü acil durum beklemeden bir kez deneyin. Bunlar tek başına bir kasa çözümünün yerini tutmaz ama en sık yaşanan kazaların büyük çoğunluğunu ortadan kaldırır.

    Altyapı tarafında da yalıtım kadar erişim disiplini önemlidir. Kendi Docker yığınınızı ayrı bir makinede çalıştırmak için tam root erişimli VDS ya da bulut sunucu paketlerimize bakabilir, sunucu sertleştirme ve erişim yönetimini devretmek isterseniz sunucu yönetimi hizmetimizden yararlanabilirsiniz. Güçlü parolalar üretmek için şifre üretici aracımız, dosya bütünlüğü kontrolü için de hash üretici aracımız işinizi kolaylaştırır.

    DockerGüvenlikSecret

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.