Security Best Practices

Docker Secrets Management
อย่างมืออาชีพ

เรียนรู้การจัดการ secrets อย่างปลอดภัยด้วย Docker Secrets และเปรียบเทียบกับเครื่องมือภายนอก เช่น HashiCorp Vault, AWS Secrets Manager

1

Docker Secrets คืออะไร?

Docker Secrets เป็นระบบจัดการข้อมูลลับ (secrets) ที่มาคู่มากับ Docker Swarm สำหรับจัดการข้อมูลที่ไวต่อความปลอดภัย เช่น รหัสผ่าน, API keys, คีย์เชื่อมต่อฐานข้อมูล ฯลฯ

ความรู้พื้นฐาน

Docker Secrets ถูกออกแบบมาเพื่อใช้กับ Docker Swarm (orchestration platform) ของ Docker โดย secrets จะถูกเข้ารหัสในการส่งผ่าน ( TLS) และจัดเก็บใน Docker Swarm's Raft log ที่ถูกเข้ารหัส

สิ่งสำคัญคือ secrets จะถูก mount เป็นไฟล์ใน filesystem ของ container (ไม่ใช่ environment variables) ที่ใช้ tmpfs (memory-based filesystem) ซึ่งหมายความว่า secrets จะไม่ถูกจัดเก็บใน disk และจะถูกลบออกเมื่อ container หยุดทำงาน

โครงสร้างของ Docker Secrets
$ # สร้าง secret จากไฟล์
docker secret create db_password ./password.txt

$ # ดูรายการ secrets
docker secret ls

$ # สร้าง service พร้อม secrets
docker service create --name web --secret db_password nginx

$ # อ่าน secret ใน container
cat /run/secrets/db_password
2

ทำไมต้องใช้ Docker Secrets? (และทำไมenvironmentตัวแปรไม่ปลอดภัย)

ปัญหาของ Environment Variables

  • ไม่ปลอดภัย: Environment variables สามารถมองเห็นได้จาก docker inspect หรือ docker ps
  • ไม่สามารถ rotate ได้ง่าย: ต้อง rebuild/restart container ทั้งหมด
  • บันทึกใน image: ค่า environment variables อาจถูกบันทึกใน layer ของ image
  • ไม่มี encryption: ไม่มีการเข้ารหัสข้อมูล secrets
  • Log potential: ในบางระบบ secrets อาจถูกลlogไว้โดยไม่ตั้งใจ

ประโยชน์ของ Docker Secrets

  • ปลอดภัย: ไม่สามารถดูจาก docker inspect
  • เข้ารหัส: ข้อมูล secrets ถูกเข้ารหัสในการส่งผ่านและจัดเก็บ
  • tmpfs mount: secrets อยู่ใน memory ไม่เข้า disk
  • Controlled access: ใช้ RBAC ควบคุมการเข้าถึง secrets
  • Auto-mount: Docker จัดการ mount ให้เองใน /run/secrets/

ตัวอย่างเปรียบเทียบ

❌ ไม่ปลอดภัย (Environment Variables):
$ docker run -e DB_PASSWORD=secretpass123 myapp
# ดูได้จากคำสั่งนี้:
$ docker inspect container_id | grep DB_PASSWORD
✅ ปลอดภัย (Docker Secrets):
$ echo "secretpass123" | docker secret create db_password -
$ docker service create --secret db_password myapp
# ไม่สามารถดู secrets จากคำสั่งใดๆ ได้
3

Docker Secrets vs Environment Variables

คุณสมบัติ Environment Variables Docker Secrets
วิธีการส่งผ่าน -e KEY=value หรือ environment: ใน docker-compose.yml --secret หรือ secrets: ใน docker-compose.yml
ที่เก็บข้อมูล Container environment (visible in docker inspect) tmpfs (memory-based, /run/secrets/)
Encryption ไม่มี encryption เข้ารหัสใน transit และ at rest
Rotation ยาก (ต้อง rebuild/restart) ง่ายกว่า (update service)
Security กลางๆ / ไม่ปลอดภัย สูง (ตัวเลือกที่ปลอดภัยที่สุดสำหรับ Docker Swarm)
เหมาะสำหรับใช้ Configuration settings (ไม่ใช่ secrets), development Passwords, API keys, certificates, production

สรุป

สำหรับข้อมูลที่ไวต่อความปลอดภัย (passwords, API keys, certificates) ควรใช้ Docker Secrets เสมอ สำหรับ configuration settings ที่ไม่ใช่ secrets (เช่น DEBUG_MODE, APP_NAME) สามารถใช้ environment variables ได้

4

Docker Secrets vs External Secrets Management Tools

ตารางเปรียบเทียบเครื่องมือ

เครื่องมือ Integration กับ Docker Scalability Advanced Features เหมาะสำหรับ
Docker Secrets Built-in, ง่ายมาก Medium Basic (rotation, audit limited) Swarm clusters, simple apps
HashiCorp Vault ต้องตั้งค่าเอง สูง (cluster capable) Full (audit, PKI, dynamic creds) Enterprise, complex apps
AWS Secrets Manager ผ่าน IAM roles สูง (AWS-native) อัตโนมัติ rotation, cross-region AWS users, cloud-native
Azure Key Vault ผ่าน Managed Identity สูง (Azure-native) Hardware security (HSM) Azure users, enterprise
GCP Secret Manager ผ่าน SERVICE_ACCOUNT สูง (GCP-native) Automatic encryption, audit logs GCP users, cloud-native

เริ่มต้นเร็ว (Start Fast)

ถ้าใช้ Docker Swarm และต้องการเริ่มต้นเร็ว Docker Secrets คือตัวเลือกที่ดีที่สุด

Enterprise (SCALE)

ถ้าต้องการ advanced features, audit, rotation HashiCorp Vault เป็นตัวเลือกที่ดีที่สุด

Cloud Native

ถ้าใช้ AWS/Azure/GCP ควรเลือก Secrets Manager ของ cloud provider นั้นๆ

5

วิธีใช้งาน Docker Secrets

5.1 การสร้าง Secrets

Docker Secrets จะถูกสร้างจากไฟล์หรือ string แล้วจึงนำไปใช้กับ services

วิธีที่ 1: จากไฟล์

# สร้างไฟล์ secrets ก่อน
$ echo "mysecretpassword" > db_password.txt
$ echo "myapikey123" > api_key.txt

# สร้าง secrets จากไฟล์
$ docker secret create db_password ./db_password.txt
$ docker secret create api_key ./api_key.txt

# ดูรายการ secrets
$ docker secret ls

วิธีที่ 2: จาก STDIN (pipe)

$ echo "mysecretpassword" | docker secret create db_password -
$ cat secrets.txt | docker secret create db_password -

วิธีที่ 3: จาก Environment Variable

$ echo "$DB_PASSWORD" | docker secret create db_password -

วิธีที่ 4: ลบ secrets

$ docker secret rm db_password
# เฉพาะ secrets ที่ไม่ได้ถูกใช้กับ service ใดๆ เท่านั้นที่จะลบได้

5.2 ใช้ Secrets กับ Docker Swarm Service

หลังจากสร้าง secrets แล้ว สามารถนำไปใช้กับ Docker Swarm service ได้

สร้าง service พร้อม secrets

# สร้าง service พร้อม secrets หนึ่งตัว
$ docker service create \
--name myapp \
--secret db_password \
myimage:latest

สร้าง service พร้อม secrets หลายตัว

$ docker service create \
--name myapp \
--secret db_password \
--secret api_key \
--secret tls_cert \
myimage:latest

ใช้ secrets ที่มีอยู่กับ service ที่มีอยู่

$ docker service update \
--secret-add db_password \
myapp

5.3 ใช้ Secrets กับ Docker Compose

Short Syntax (ง่าย)

Secrets จะถูก mount ที่ /run/secrets/[secret_name]

# docker-compose.yml
services:
app:
image: myapp:latest
secrets:
- db_password
- api_key

secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt

Long Syntax (ควบคุมได้มากกว่า)

สามารถกำหนด uid, gid, mode ได้

services:
app:
image: myapp:latest
secrets:
- source: db_password
target: db_password
uid: '1000'
gid: '1000'
mode: 0440

secrets:
db_password:
file: ./secrets/db_password.txt

5.4 อ่าน Secrets ในแอปพลิเคชัน

Secrets จะถูก mount ที่ /run/secrets/[secret_name] ใน container

Shell Script

#!/bin/bash

# อ่าน secret ไปใช้
DB_PASSWORD=$(cat /run/secrets/db_password)
echo "Connecting to database..."
mysql -u root -p"$DB_PASSWORD" mydb

Node.js

const fs = require('fs');
const mysql = require('mysql');

// Read secret from file
const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8');

// Use in connection
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: dbPassword,
database: 'mydb'
});

Python

import os
import mysql.connector

# Read secret from file
with open('/run/secrets/db_password', 'r') as f:
db_password = f.read().strip()

# Use in connection
connection = mysql.connector.connect(
host='localhost',
user='root',
password=db_password,
database='mydb'
)

5.5 Build Secrets (Docker BuildKit)

Docker BuildKit ช่วยให้สามารถใช้ secrets ระหว่างการ build image โดย secrets จะไม่ถูกบันทึกใน image layers

Using --mount=type=secret (BuildKit)

# Dockerfile (ใช้ BuildKit)
FROM python:3.9-slim

# Build stage
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) \
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data -o data.txt

# Normal build...
COPY app.py .
CMD ["python", "app.py"]

Build ด้วย --secret

$ DOCKER_BUILDKIT=1 docker build \
--secret id=api_key,src=./secrets/api_key.txt \
-t myapp:latest .

# หรือใช้ environment variable
$ DOCKER_BUILDKIT=1 docker build \
--secret id=api_key env=API_KEY \
-t myapp:latest .

SSH Agent Forwarding

# สำหรับ Git authentication หรือ SSH access
$ DOCKER_BUILDKIT=1 docker build \
--ssh default=$SSH_AUTH_SOCK \
-t myapp:latest .

# Dockerfile
RUN --mount=type=ssh \
git clone git@github.com:company/private-repo.git
6

ตัวอย่างการใช้งานจริง

1

Database Credentials สำหรับ Docker Compose

ตัวอย่างการจัดการ database credentials แบบปลอดภัยด้วย Docker Compose

files/secrets/db_password.txt
supersecretpassword123
docker-compose.yml
version: '3.8'

services:
app:
image: myapp:latest
environment:
- APP_ENV=production
secrets:
- source: db_password

db:
image: postgres:15
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
secrets:
- source: db_password

secrets:
db_password:
file: ./secrets/db_password.txt
หมายเหตุ

PostgreSQL รองรับ POSTGRES_PASSWORD_FILE ซึ่งจะอ่านค่าจากไฟล์แทน environment variable ทำให้สามารถใช้ secrets ได้ตามปกติ

2

API Keys (Stripe, Twilio)

version: '3.8'

services:
app:
image: myapp:latest
environment:
- STRIPE_API_KEY_FILE=/run/secrets/stripe_key
- TWILIO_AUTH_TOKEN_FILE=/run/secrets/twilio_token
secrets:
- source: stripe_key
target: stripe_key
- source: twilio_token
target: twilio_token

secrets:
stripe_key:
file: ./secrets/stripe_api_key.txt
twilio_token:
file: ./secrets/twilio_auth_token.txt
3

TLS Certificates

ใช้ secrets ในการจัดการ certificates สำหรับ HTTPS

docker-compose.yml
version: '3.8'

services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
secrets:
- source: tls_cert
target: /etc/nginx/ssl/server.crt
- source: tls_key
target: /etc/nginx/ssl/server.key

secrets:
tls_cert:
file: ./ssl/server.crt
tls_key:
file: ./ssl/server.key
4

Docker Swarm Stack (Multi-service)

docker-stack.yml
version: '3.8'

secrets:
app_db_password:
file: ./secrets/db_password.txt
app_api_key:
file: ./secrets/api_key.txt
tls_cert:
file: ./ssl/server.crt
tls_key:
file: ./ssl/server.key

services:
app:
image: myapp:latest
deploy:
replicas: 3
secrets:
- source: app_db_password
- source: app_api_key

reverse-proxy:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
secrets:
- source: tls_cert
- source: tls_key
$ # Deploy stack
docker stack deploy -c docker-stack.yml myapp

$ # Upsert (update existing stack)
docker stack deploy -c docker-stack.yml myapp --with-registry-auth
7

Security Best Practices

1. ใช้ Least Privilege Principle

ไม่ควรให้ container เข้าถึง secrets ที่ไม่จำเป็น ตัวอย่างเช่น ถ้า service A ต้องการใช้ database password แต่ไม่ต้องการใช้ API key ก็ไม่ควรให้ service A เข้าถึง API key

# ❌ ไม่ดี - ทุก service เข้าถึง secrets ทั้งหมด
version: '3'
services:
app1:
secrets:
- all_secrets
app2:
secrets:
- all_secrets
# ✅ ดี - แต่ละ service มี secrets ที่จำเป็นเท่านั้น
services:
app1:
secrets:
- app1_db_password
app2:
secrets:
- app2_api_key

2. ตั้งค่า file permissions (uid, gid, mode)

ใช้ long syntax เพื่อกำหนด file permissions ของ secrets ที่ถูก mount ให้ strictly

services:
app:
secrets:
- source: db_password
target: /run/secrets/db_password
uid: '1000'
gid: '1000'
mode: 0400

หมายเหตุ: ค่า mode: 0400 หมายถึงอ่านได้อย่างเดียวสำหรับ owner เท่านั้น ไม่ควรใช้ 0777 เด็ดขาด!

3. ไม่ควร log secrets

หลีกเลี่ยงการ log secrets โดยตรง ควรใช้ placeholder หรือ mask แทน

❌ ไม่ดี
console.log('DB Password:', process.env.DB_PASSWORD)
// หรือ
app.logger.info(`Connecting with password: ${dbPassword}`)
✅ ดี
console.log('Database connection initialized')
console.log('DB Host:', process.env.DB_HOST)

4. Secret Rotation (สรุป workaround)

Docker Secrets ไม่มี feature สำหรับ rotation โดยตรง แต่สามารถทำได้ด้วยการ update service ดังนี้:

1. # สร้าง secret ใหม่
echo "newpassword" | docker secret create db_password_v2 -

2. # Update service ให้ใช้ secret ใหม่
docker service update \
--secret-rm db_password \
--secret-add db_password_v2 \
myapp

3. # รอ service redeploy
# ตรวจสอบ service status
docker service ps myapp

4. # ลบ secret เก่า
docker secret rm db_password

5. Network Isolation

ใช้ Docker networks เพื่อแยก service ที่ใช้ secrets ออกจาก service อื่นๆ

version: '3.8'

services:
app:
image: myapp:latest
networks:
- backend
secrets:
- db_password

db:
image: postgres:15
networks:
- backend
secrets:
- db_password

networks:
backend:
driver: overlay

6. Use Minimal Base Images

ใช้ base images ที่มีขนาดเล็กและปลอดภัย เช่น Alpine Linux จะมี attack surface น้อยกว่า full images

❌ ไม่ดี
$ docker run -it ubuntu:latest
✅ ดี
$ docker run -it alpine:latest
8

การแก้ไขปัญหาที่พบบ่อย

1. Permission denied when reading secret

ปัญหา: แอปพลิเคชันอ่าน secrets ไม่ได้ด้วยข้อผิดพลาด Permission denied

สาเหตุ:
  • File permissions ของ secrets ถูกตั้งไว้แคบเกินไป
  • User ที่รันแอปพลิเคชันไม่มีสิทธิ์อ่าน file
วิธีแก้:

แก้ไขด้วยการตั้งค่า mode ให้กว้างขึ้น:

secrets:
db_password:
file: ./secrets/db_password.txt

services:
app:
secrets:
- source: db_password
mode: 0444

2. Secret not found / Invalid secret

ปัญหา: Service ไม่สามารถสร้างได้ด้วยข้อผิดพลาด secret not found

สาเหตุ:
  • Secret ยังไม่ถูกสร้างก่อนใช้งาน
  • ชื่อ secret ใน docker-compose.yml ไม่ตรงกับ secret ที่สร้างไว้
  • ใช้ secrets ที่สร้างใน Swarm กับ Compose หรือกลับกัน
วิธีแก้:

ตรวจสอบว่า secret ถูกสร้างแล้ว:

# ตรวจสอบ secrets ที่มีอยู่
docker secret ls

# สร้าง secret ถ้ายังไม่มี
echo "password" | docker secret create db_password -

# ตรวจสอบอีกครั้ง
docker secret ls

3. Cannot update service - secret already in use

ปัญหา: ไม่สามารถลบ secret ที่กำลังใช้อยู่

สาเหตุ:
  • Secret ยังถูกใช้อยู่กับ service ใด service หนึ่ง
วิธีแก้:

ต้องลบ secret ออกจากทุก service ก่อน:

# ดูรายละเอียด secret
docker secret inspect db_password

# ลบ secret ออกจาก service
docker service update \
--secret-rm db_password \
myapp

# ถ้าใช้ Docker Compose ต้องใช้คำสั่งนี้
docker stack deploy -c docker-stack.yml myapp

# ตอนนี้ลบ secret ได้
docker secret rm db_password

4. Secret empty or missing content

ปัญหา: อ่าน secret ได้แต่ content เป็นค่าว่าง

สาเหตุ:
  • ไฟล์ source มี newline หรือ carriage return ที่ไม่ต้องการ
  • ใช้คำสั่ง echo โดยไม่มีข้อมูลจริง
  • File path ไม่ถูกต้อง
วิธีแก้:

ตรวจสอบเนื้อหาไฟล์ก่อนสร้าง secret:

# ตรวจสอบเนื้อหาไฟล์ (including hidden characters)
cat -A ./secrets/db_password.txt

# หรือดูขนาดไฟล์
wc -c ./secrets/db_password.txt

# ใช้ printf เพื่อหลีกเลี่ยง newline
printf "secretpass123" | docker secret create db_password -

Debugging Commands

ดู secrets ที่ใช้งานอยู่:
docker secret ls
ดูรายละเอียด secret:
docker secret inspect [secret_name]
ตรวจสอบ container ที่ใช้ secrets:
docker inspect [container_id] | grep -i secret
ตรวจสอบ secrets ที่ container เข้าถึงได้:
docker exec [container_id] ls -la /run/secrets/
อ่าน content ของ secret (ใน container):
docker exec [container_id] cat /run/secrets/[secret_name]

สรุป

คำนึงถึงความปลอดภัย

secrets เป็นข้อมูลที่สำคัญและไวต่อความปลอดภัย - ใช้ Docker Secrets หรือ external tools อย่างเหมาะสม

เลือกเครื่องมือที่ถูกต้อง

Docker Secrets สำหรับ Docker Swarm, HashiCorp Vault สำหรับ Enterprise, Cloud services สำหรับ cloud-native

Best Practices

Least privilege, file permissions, no logging, network isolation, และ usage ที่เหมาะสม

สิ่งที่ควรเรียนรู้เพิ่มเติม

Next Steps - ขั้นตอนถัดไป

คำถามที่พบบ่อย (FAQ)

Q1 Docker Secrets ใช้ได้เฉพาะกับ Docker Swarm เท่านั้นหรือไม่?

A: Docker Secrets ถูกออกแบบมาให้ works กับ Docker Swarm เป็นหลัก โดย secrets จะถูกเก็บไว้ใน Swarm's raft log ที่เข้ารหัสแล้ว อย่างไรก็ตาม สำหรับ Docker Compose (standalone) ก็สามารถใช้ secrets ได้แต่จะมีข้อจำกัดกว่า

Q2 Secrets ถูกลบเมื่อ container หยุดทำงานหรือไม่?

A: ใช่! Docker secrets จะถูก mount ใช้ tmpfs (memory-based filesystem) ดังนั้นเมื่อ container หยุดหรือถูกลบ secrets จะถูกลบออกจาก memory ตามด้วย

Q3 สามารถใช้ secrets กับ Docker Compose บน Docker Desktop ได้หรือไม่?

A: ได้! Docker Compose รองรับ secrets syntax ตั้งแต่ version 3 ขึ้นไป แต่ secrets จะถูก mount บน host machine (ไม่ใช่ swarm mode) ซึ่งอาจมีข้อจำกัดด้านความปลอดภัยบางอย่าง

Q4 ถ้าต้องการ rotate secret ต้องทำอย่างไร?

A: Docker Secrets ไม่มี auto-rotation แต่สามารถทำได้โดย: (1) สร้าง secret ใหม่, (2) update service ให้ใช้ secret ใหม่, (3) wait for rolling update, (4) ลบ secret เก่าออก

Q5 แล้ว environment variables ปลอดภัยแค่ไหน?

A: environment variables ไม่ปลอดภัย สำหรับ secrets เพราะ: (1) ดูได้จาก docker inspect, (2) อาจถูกบันทึกใน image layers, (3) ไม่มี encryption, (4) ไม่มี audit trail. ควรใช้ secrets เท่านั้นสำหรับข้อมูลไวต่อความปลอดภัย