Docker Compose secrets t?ch credential kh?i environment

Docker Compose secrets production: bảo mật API key, .env và CI/CD

Đưa API key vào .env giúp chạy thử nhanh nhưng chưa phải chiến lược bảo mật production. Secret có thể lọt vào Git, Docker image layer, log CI, lệnh docker inspect hoặc bản backup. Với Docker Compose, cơ chế secrets: tách dữ liệu nhạy cảm khỏi YAML và mount vào container dạng file chỉ đọc tại /run/secrets/.

Phạm vi: bài này áp dụng cho Docker Compose standalone dùng nguồn file ở local/dev/staging; Swarm, Kubernetes và secret manager trên cloud có semantics khác. Docker Docs quy định secret chỉ được cấp cho service khai báo và xuất hiện dưới dạng file trong container.

Docker Compose secrets với secret mount trong production
Ảnh đại diện: secret được mount đúng service thay vì truyền qua environment.

Secret thường lộ ở đâu?

  • Git: commit nhầm .env hoặc file cấu hình.
  • Image layer: dùng ARG/ENV trong Dockerfile, secret có thể nằm trong lịch sử layer.
  • Log và CI: lệnh debug, echo biến môi trường hoặc artifact build.
  • Runtime: biến môi trường xuất hiện trong docker inspect và đôi khi trong crash dump.
  • Backup: snapshot volume/config không mã hóa hoặc cấp quyền quá rộng.

Mục tiêu là giảm số nơi secret xuất hiện, cấp đúng service cần dùng và có thể thu hồi nhanh. Đây là nguyên tắc least privilege trong OWASP Secrets Management Cheat Sheet, cùng vòng đời tạo, xoay vòng, thu hồi và hết hạn.

Luồng secret từ nguồn bí mật vào container Docker Compose
Kiến trúc source → Compose secrets → /run/secrets → process.

Compose secrets: file mẫu an toàn hơn .env

Ví dụ dùng file local cho dev/staging. File secret nằm ngoài Git, được cấp quyền đọc hạn chế, và chỉ service api được mount.

services:
  api:
    image: ghcr.io/example/api:2026.08.1
    secrets: [db_password, stripe_secret]
    environment:
      DB_HOST: db
      DB_PASSWORD_FILE: /run/secrets/db_password
      STRIPE_SECRET_FILE: /run/secrets/stripe_secret
  worker:
    image: ghcr.io/example/worker:2026.08.1
    secrets: [db_password]
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
  db:
    image: postgres:16
    secrets: [db_password]
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
  db_password: {file: ./secrets/db_password.txt}
  stripe_secret: {file: ./secrets/stripe_secret.txt}

Tạo file và khóa quyền trên Linux:

mkdir -p secrets
umask 077
printf '%s' 'replace-with-a-long-random-value' > secrets/db_password.txt
printf '%s' 'replace-with-stripe-secret' > secrets/stripe_secret.txt
printf 'secrets/\n.env\n' > .gitignore
docker compose config
docker compose up -d

Trong ứng dụng, đọc đường dẫn DB_PASSWORD_FILE thay vì đọc secret trực tiếp từ DB_PASSWORD:

from pathlib import Path
import os

def read_secret(env_name: str) -> str:
    value = Path(os.environ[env_name]).read_text(encoding="utf-8").strip()
    if not value:
        raise RuntimeError(f"empty secret: {env_name}")
    return value

db_password = read_secret("DB_PASSWORD_FILE")

Không in giá trị đọc được ra log. Nếu thư viện chỉ nhận chuỗi cấu hình, đọc file khi khởi tạo và giữ biến trong phạm vi nhỏ nhất.

Anti-pattern và bản sửa

So sánh .env/ARG dễ lộ với secrets an toàn
Anti-pattern ENV/command/log đối chiếu với runtime secret mount.
Không nên Vấn đề Nên dùng
ENV API_KEY=... trong Dockerfile Lộ trong image history/inspect Runtime secret mount
ARG TOKEN rồi curl Token có thể nằm trong layer/log BuildKit --mount=type=secret
Commit .env.production Git lưu vĩnh viễn trong history Secret manager + CI injection
Echo secret để debug CI log/artifact bị lộ Log tên biến, hash hoặc độ dài

BuildKit secret: chỉ tồn tại trong bước build

Khi cần token riêng để tải package, dùng BuildKit; không truyền qua ARG/ENV:

# syntax=docker/dockerfile:1.7
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=secret,id=pypi_token \
    PIP_INDEX_URL="$(cat /run/secrets/pypi_token)" \
    pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
DOCKER_BUILDKIT=1 docker build \
  --secret id=pypi_token,src=./secrets/pypi_token.txt \
  -t example/api:dev .

Đọc thêm Docker Build secrets. Docker cảnh báo ARG/ENV không phù hợp để giữ bí mật vì dữ liệu có thể tồn tại trong image; sau build vẫn phải kiểm tra history và log.

Kiểm tra nhanh secret có lọt vào image hay không

docker history --no-trunc example/api:dev
docker image inspect example/api:dev --format '{{json .Config.Env}}'
docker compose exec api sh -c 'ls -l /run/secrets && test -s /run/secrets/db_password'
git grep -n -I -E '(AKIA|BEGIN PRIVATE KEY|api[_-]?key|password=)' -- ':!secrets/*'

Output kỳ vọng: docker history không chứa credential hoặc URL có token; image inspect chỉ hiện biến môi trường không nhạy cảm; lệnh exec trả mã thoát 0, hiện file secret không rỗng và quyền đọc phù hợp; git grep không trả dòng chứa mẫu bí mật. Nếu lệnh trả mã khác 0, dừng deploy để truy nguyên thay vì bỏ qua cảnh báo. Các lệnh không được in giá trị bí mật; nếu cần đối chiếu, chỉ so sánh hash cục bộ.

CI/CD: không biến pipeline thành secret store

  1. Ưu tiên OIDC/workload identity để CI nhận credential ngắn hạn thay vì access key dài hạn.
  2. Nếu phải dùng secret CI, giới hạn theo environment/branch, mask log và không đưa vào artifact.
  3. Runner ephemeral nên được xóa sau job; không cache thư mục chứa secret.
  4. Chỉ inject secret vào bước cần dùng; bước test/lint không cần thì không được thấy.
  5. Bật secret scanning/push protection; phát hiện lộ thì revoke trước rồi mới xóa khỏi Git.

GitHub mô tả secret scanning và push protection tại tài liệu chính thức. Scanning không thay cho rotation: coi mọi credential đã commit là bị lộ.

Rotation có kiểm soát và xử lý sự cố

Quy trình xoay vòng secret và thu hồi API key
Đổi secret mới, redeploy, kiểm tra health rồi thu hồi secret cũ.
  1. Tạo secret mới với quyền tương đương hoặc thấp hơn.
  2. Deploy secret mới, xác nhận metric lỗi xác thực không tăng.
  3. Thu hồi secret cũ sau cửa sổ chuyển tiếp.
  4. Kiểm tra Git, CI log, image registry và backup để tìm bản sao.
  5. Ghi thời điểm, phạm vi ảnh hưởng và người phê duyệt; không ghi secret vào ticket/chat.

Với database password, tạo user/password mới, cập nhật service rồi revoke credential cũ. Với API key không hỗ trợ overlap, chuẩn bị cửa sổ bảo trì và rollback rõ ràng; không mặc định rằng mọi ứng dụng đều đổi secret mà không gián đoạn.

Rotation có cần rebuild image không?

Không cần rebuild image nếu secret chỉ được mount ở runtime: cập nhật nguồn secret, recreate/redeploy service và kiểm tra health là đủ. Việc có gián đoạn hay không phụ thuộc vào số replica, cách reload và khả năng overlap của credential; hãy ghi rõ điều kiện vận hành trong runbook thay vì hứa hẹn kết quả tuyệt đối. Build lại chỉ cần khi dependency build-time thay đổi.

Compose secrets có giới hạn gì?

  • File file: local vẫn nằm trên host; Compose chỉ mount nó vào container.
  • Compose không tự xoay secret, không có policy engine và audit tập trung như Vault/Cloud Secret Manager.
  • Swarm/Kubernetes có mô hình secret khác; không bê nguyên giả định giữa các orchestrator.
  • Ứng dụng phải hỗ trợ đọc file và reload/khởi động lại khi secret đổi.

Với nhiều host hoặc yêu cầu audit nghiêm ngặt, dùng Vault, AWS Secrets Manager, GCP Secret Manager hoặc Azure Key Vault; Compose chỉ nhận secret ở bước deploy.

Checklist trước production

  • rg -n "(AKIA|BEGIN PRIVATE KEY|api[_-]?key|password\s*=)" . và chạy secret scanner trong CI.
  • Không có secret trong Dockerfile ARG/ENV, image history, log hoặc artifact.
  • File secrets/ nằm trong .gitignore, quyền host là 600 và backup được mã hóa.
  • Mỗi service chỉ mount đúng secret cần thiết; không mount cả thư mục.
  • Secret có owner, ngày rotation, TTL và quy trình revoke trong runbook.
  • Compose file qua docker compose config; image được pin tag/digest.

Để tránh deploy sai thứ tự sau khi đổi secret, tham khảo startup order + healthcheckrestart policy production. Xem thêm nền tảng Docker cho người mớiDOCKER-USER/Fail2Ban; tuyệt đối không gửi secret vào log.

Kiểm thử staging trước khi đổi production

Đừng chờ đến lúc rotation mới phát hiện ứng dụng không đọc được file. Trong staging, chạy cùng image và Compose file (chỉ thay nguồn secret), sau đó kiểm tra bốn lớp:

  1. Mount: docker compose exec api stat /run/secrets/db_password trả đúng owner/quyền và file không rỗng.
  2. Ứng dụng: gọi endpoint health/readiness; không trả secret trong response hoặc log.
  3. Phụ thuộc: đăng nhập một thao tác DB/API thật bằng credential mới, rồi kiểm tra retry/timeout.
  4. Rollback: khôi phục secret cũ trong cửa sổ test và xác nhận service vẫn khởi động được.

Ghi lại phiên bản Docker Compose, hệ điều hành host, image digest và kết quả test vào runbook. Việc này biến “đã đổi secret” thành một thay đổi có thể audit, thay vì một lệnh thủ công khó lặp lại.

Mẫu pipeline tối thiểu không làm lộ secret

steps:
  - run: docker compose config
  - run: docker buildx build --secret id=pypi_token,env=PYPI_TOKEN -t "$IMAGE" .
    env:
      PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
  - run: docker compose up -d api
  - run: curl --fail https://staging.example.com/health

Trong pipeline thật, bật masking cho biến secret, không dùng set -x, và xóa workspace sau job. Nếu runner có cache, loại trừ secrets/, Docker build context và file tạm chứa credential. Chỉ cho phép job deploy production chạy từ branch/tag đã bảo vệ.

Câu hỏi thường gặp

Docker Compose secrets có mã hóa secret trên host không?

Không. Với nguồn file, secret vẫn là file trên host; hãy khóa quyền, mã hóa disk/backup và cân nhắc secret manager.

Đọc secret từ /run/secrets có an toàn tuyệt đối?

Không tuyệt đối; process có quyền trong container hoặc host vẫn có thể đọc. Least privilege và hardening host vẫn bắt buộc.

Có thể dùng .env cho staging không?

Có thể cho thử nghiệm không nhạy cảm, nhưng nên chuyển sớm sang secrets. Không commit file thật; dùng .env.example chỉ chứa tên biến.

Secret bị commit nhầm thì chỉ cần xóa commit?

Không. Hãy revoke/rotate ngay, quét lịch sử Git, registry, CI log và backup; sau đó mới rewrite history nếu chính sách cho phép.

Khi nào cần Vault thay Compose secrets?

Khi có nhiều host, yêu cầu audit, TTL, rotation tự động hoặc secret động. Compose secrets phù hợp lớp runtime đơn giản.

Tài liệu chính thức: Docker Compose secrets; Docker Build secrets; OWASP Secrets Management Cheat Sheet; GitHub secret scanning.

Leave a Comment

Your email address will not be published. Required fields are marked *