ปกติเราจัดการ .env บนเซิร์ฟเวอร์กันยังไง
ทางที่ใช้กันมาตลอดคือ ssh เข้าเซิร์ฟเวอร์แล้วแก้ .env ด้วย nano หรือลากไฟล์ขึ้นไปทาง FileZilla ระบบเล็ก ๆ ก็พอไปได้ แต่พอระบบโตขึ้นจะเริ่มเจอเรื่องเดิมซ้ำ ๆ
- ไม่มีประวัติ ใครเปลี่ยน
MAIL_PASSWORDเมื่อไหร่ ค่าเก่าคืออะไร ไม่มีใครตอบได้ - เพิ่ม key ใหม่ในโค้ดแล้วลืมไปเพิ่มบนเซิร์ฟเวอร์ deploy เสร็จแอปพังทันที
- เซิร์ฟเวอร์ตายหรือย้ายเครื่อง ค่าทั้งหมดอยู่ในเครื่องนั้นเครื่องเดียว
- จะ commit
.env.productionลง git ตรง ๆ ก็ไม่ได้ เพราะรหัสผ่านทุกตัวจะติดอยู่ในประวัติของ repo ไปตลอด
Laravel มี env:encrypt ให้อยู่แล้ว
ตั้งแต่ Laravel 9 มีคำสั่ง env:encrypt กับ env:decrypt มาให้ เราเข้ารหัส .env.production เป็น .env.production.encrypted แล้ว commit ไฟล์นั้นลง git ได้เลย เพราะในไฟล์มีแต่ข้อความที่เข้ารหัสแล้ว ตอน deploy ค่อยถอดรหัสกลับเป็น .env บนเซิร์ฟเวอร์
ค่าทั้งหมดจึงมีประวัติใน git ตามโค้ด สิ่งเดียวที่ต้องเก็บแยกคือ key ตัวเดียวต่อ environment
แต่ key นี่แหละที่จัดการยาก
- เรียก
env:encryptเฉย ๆ Laravel จะสร้าง key ใหม่แล้วพิมพ์ออกหน้าจอ key จึงไปค้างอยู่ใน scrollback ของ terminal - ส่ง key ด้วย
--key=…ก็ไปโผล่ใน shell history และในpsของเครื่อง - ต้องเอา key ขึ้นไปวางบนเซิร์ฟเวอร์เองอีกทอด
- พอจะแก้ค่าแล้วเรียก
env:encryptซ้ำโดยไม่ได้ส่ง key เดิม ไฟล์ที่ได้จะเข้ารหัสด้วย key ใหม่ที่ไม่มีใครเก็บไว้ deploy รอบถัดไปจะเจอThe MAC is invalid
โปรเจกต์ของผมเกือบทุกตัวเก็บ .env แบบนี้ ผมจึงรวบขั้นตอนเรื่อง key ไว้ในแพ็กเกจ laravel-env-secrets
แพ็กเกจนี้ทำอะไรให้บ้าง
แพ็กเกจเพิ่มคำสั่ง artisan 6 ตัว
| คำสั่ง | ใช้ทำอะไร |
|---|---|
secrets:provision <env> |
ครั้งแรก: สร้าง key เข้ารหัส .env.<env> แล้วส่ง key ขึ้นเซิร์ฟเวอร์ |
secrets:edit <env> |
ดึง key จากเซิร์ฟเวอร์มาถอดรหัส .env.<env> ไว้แก้ |
secrets:reencrypt <env> |
เข้ารหัสไฟล์ที่แก้แล้วด้วย key เดิม แล้วตรวจว่าถอดกลับได้ตรง |
secrets:status <env> |
เช็กว่า key บนเซิร์ฟเวอร์ยังถอดไฟล์ใน repo ได้ไหม |
secrets:show <env> [name] |
ดูชื่อตัวแปรพร้อมค่าที่ปิดบังไว้ หรือดูค่าทีละตัว |
secrets:merge <env> |
เติมค่าที่ .env ในเครื่องยังไม่มี โดยไม่เขียนทับค่าเดิม |
ทุกคำสั่งยึดหลักเดียวกัน key ไม่เคยออกหน้าจอ ไม่เป็น argument ของคำสั่งไหน และไม่มีคำสั่งไหนเขียน key ลงไฟล์ในเครื่องเรา key วิ่งผ่าน stdin ของ ssh ตอนส่งขึ้นเซิร์ฟเวอร์ และผ่าน stdin ของ pbcopy ตอนคัดลอกลง clipboard คำสั่งที่ต้องใช้ key ภายหลังจะดึงกลับมาทาง ssh เก็บไว้ในหน่วยความจำของ process นั้นอย่างเดียว
ติดตั้งและตั้งค่า
แพ็กเกจนี้รันจากเครื่องที่เราพัฒนา จึงติดตั้งเป็น dev dependency
composer require --dev phattarachai/laravel-env-secrets
php artisan vendor:publish --tag=env-secrets-config
ค่าที่ต้องตั้งมีสองตัว คือเซิร์ฟเวอร์ที่จะเก็บ key (ชื่อ host ใน ~/.ssh/config) และโฟลเดอร์บนเซิร์ฟเวอร์นั้น
return [
'host' => env('ENV_SECRETS_HOST', 'prod-box'),
'dir' => env('ENV_SECRETS_DIR', '/etc/myapp'),
'slug' => env('ENV_SECRETS_SLUG', null), // null ใช้ชื่อแอป
// ...
];
host ไม่มีค่าเริ่มต้น ถ้ายังไม่ได้ตั้ง ทุกคำสั่งจะหยุดตั้งแต่บรรทัดแรกพร้อมบอกว่าต้องตั้งที่ไหน key จะไปอยู่ที่ <dir>/<slug>.<env>.key เช่น /etc/myapp/myapp.production.key
แพ็กเกจรองรับ PHP 8.3 ขึ้นไป และ Laravel 12 หรือ 13
ตั้งต้น environment แรกด้วย secrets:provision
เตรียม .env.production ไว้ในเครื่อง แล้วสั่ง
php artisan secrets:provision production
Box prod-box:/etc/myapp/myapp.production.key
.env.production encrypted → .env.production.encrypted
Key installed at prod-box:/etc/myapp/myapp.production.key (600, owner only)
→ Key copied to clipboard. Paste it into your password manager (item: myapp · production).
→ Commit .env.production.encrypted.
บรรทัดแรกบอกก่อนเลยว่ากำลังจะไปเซิร์ฟเวอร์ไหน ไฟล์อะไร ถ้าเลือกเครื่องผิดจะเห็นตั้งแต่บรรทัดนี้ จากนั้นคำสั่งทำสามอย่าง
flowchart LR
A[".env.production ในเครื่อง"] --> B["secrets:provision"]
B --> C[".env.production.encrypted ลง git"]
B -->|ssh stdin| D["key บนเซิร์ฟเวอร์ 600"]
B -->|pbcopy| E["clipboard → password manager"]key ออกจากเครื่องเราแค่สองทาง คือ ssh ไปเซิร์ฟเวอร์ และ clipboard ไปที่ password manager
key สร้างด้วย random_bytes ซึ่งสุ่มแบบปลอดภัยสำหรับงานเข้ารหัส บนเซิร์ฟเวอร์ไฟล์ key มีสิทธิ์ 600 เป็นของ user ที่เรา ssh เข้าไปเท่านั้น วาง key จาก clipboard ลง password manager ไว้เป็นสำเนาสำรอง แล้ว commit .env.production.encrypted ได้เลย
ฝั่ง deploy ต้องเพิ่มอะไร
ส่วนที่เหลืออยู่ใน repo กับขั้นตอน deploy ของเราเอง เพิ่มครั้งเดียว
ไฟล์ .env แบบยังไม่เข้ารหัสต้องไม่หลุดเข้า git
.env.production
.env.uat
ใน script deploy อ่าน key จากไฟล์บนเซิร์ฟเวอร์ แล้วถอดรหัสก่อนแอปเริ่มทำงาน
export LARAVEL_ENV_ENCRYPTION_KEY="$(cat /etc/myapp/myapp.production.key)"
php artisan env:decrypt --env=production --force
cp .env.production .env
แก้ค่าทีหลังทำยังไง
key อยู่บนเซิร์ฟเวอร์แล้ว รอบนี้จึงไม่ต้องสร้าง key ใหม่ แก้เป็นสองจังหวะ
php artisan secrets:edit production
# แก้ .env.production ตามต้องการ
php artisan secrets:reencrypt production --prune
secrets:edit ดึง key จากเซิร์ฟเวอร์มาถอดรหัสเป็น .env.production ให้แก้ secrets:reencrypt เข้ารหัสกลับด้วย key ตัวเดิม แล้วลองถอดในหน่วยความจำเทียบกับไฟล์ที่เราแก้ ถ้าไม่ตรงจะไม่ปล่อยให้ไปถึง commit ส่วน --prune ลบไฟล์ที่ยังไม่เข้ารหัสทิ้งหลังตรวจผ่าน
อยากรู้ว่าบน production ตั้งค่าอะไรไว้
php artisan secrets:status production
php artisan secrets:show production
php artisan secrets:show production DB_HOST
secrets:status เช็กว่า key บนเซิร์ฟเวอร์ยังถอดไฟล์ใน repo ได้อยู่ secrets:show แสดงชื่อตัวแปรทุกตัวพร้อมค่าที่ปิดบังไว้ ถ้าใส่ชื่อตัวแปรด้วยจะแสดงค่าจริงของตัวนั้นตัวเดียว ทั้งสองคำสั่งไม่เขียนไฟล์ที่ยังไม่เข้ารหัสลงเครื่อง
ใส่ --remote เพื่ออ่าน .env ที่แอปบนเซิร์ฟเวอร์ใช้อยู่จริงแทนไฟล์ใน repo ใช้ตอนสงสัยว่า deploy ล่าสุดเอาค่าไปครบหรือเปล่า ตั้ง app_path ใน config ไว้ก่อน
เพื่อนร่วมทีม pull แล้ว .env ในเครื่องขาด key ใหม่
เวลามีคนเพิ่ม key ใหม่ เช่น OPENROUTER_API_KEY ลงใน .env.local.encrypted แล้ว push ขึ้นมา .env ในเครื่องของคนอื่นที่สร้างไว้นานแล้วจะไม่มี key นั้น และไม่มีอะไรบอกว่าขาดตัวไหน secrets:merge เติมให้
php artisan secrets:merge local --dry-run
php artisan secrets:merge local
add OPENROUTER_API_KEY sk********
fill RESEND_KEY (was empty) → re********
skip APP_KEY (protected — never merged)
same MAIL_MAILER (already set)
differs SMTP_PASSWORD (already set, differs — left alone)
คำสั่งนี้ออกแบบให้รันอัตโนมัติหลัง git pull ได้อย่างปลอดภัย
- key ที่
.envมีอยู่แล้วจะไม่แตะ ถึงค่าจะต่างกันก็แค่รายงานว่าต่าง ยกเว้น key ที่ค่าว่าง เช่นFOO=ที่ได้มาจากcp .env.example .envจะเติมให้ - key ที่ป้องกันไว้ เช่น
APP_KEY,APP_ENV,DB_*และREDIS_*ไม่เขียนเด็ดขาด รหัสผ่านฐานข้อมูลของ production จึงไม่หลุดมาอยู่ในเครื่องใคร - ก่อนเขียนจะสำรองไฟล์เดิมไว้ที่
.env.backupและเขียนผ่านไฟล์ชั่วคราวก่อน.envจึงไม่ค้างครึ่ง ๆ กลาง ๆ - ค่าที่แสดงบนหน้าจอปิดบังไว้ทุกตัว
ทีมมีหลายคน หรือมีหลายเซิร์ฟเวอร์
key ที่ติดตั้งแบบปกติเป็น 600 อ่านได้แค่ user ที่ provision คนเดียว ถ้าทีมต้องใช้ key ร่วมกัน ให้ตั้ง unix group ไว้ใน config แล้ว provision ใหม่ โฟลเดอร์จะเป็น 750 และ key เป็น 640 ตั้ง group ไว้ใน config ดีกว่าไปแก้สิทธิ์เองบนเซิร์ฟเวอร์ เพราะทุกครั้งที่ provision ใหม่ คำสั่งจะตั้งสิทธิ์ตาม config อีกรอบ
ถ้าแต่ละ environment อยู่คนละเครื่อง ประกาศไว้ใน environments ทีเดียว ไม่ต้องจำว่าต้องใส่ --host ตอนไหน
'host' => 'prod-box',
'dir' => '/etc/myapp',
'environments' => [
'staging' => ['host' => 'staging-box'],
'mini' => ['host' => 'mac-mini', 'dir' => '/Users/deploy/.config/env-secrets'],
],
เครื่องที่ไม่มี sudo แบบไม่ต้องใส่รหัสผ่าน เช่น Mac mini ที่ตั้งไว้ในออฟฟิศ วาง key ไว้ในโฟลเดอร์ใต้ home ของ user ได้เลย คำสั่งจะใช้ sudo เฉพาะตอนจำเป็นจริง ๆ คือสร้างโฟลเดอร์ใต้ /etc หรือตั้ง group ที่ user ไม่ได้อยู่ และใช้แบบ sudo -n ที่ไม่รอรหัสผ่าน ถ้าติดตั้ง key ไม่สำเร็จ key ใหม่จะยังอยู่ใน clipboard พร้อมคำสั่งให้ติดตั้งเอง บนเซิร์ฟเวอร์ key ใหม่เขียนลงไฟล์ชั่วคราวแล้วค่อยเปลี่ยนชื่อทับ key เดิมจึงไม่เสียหาย
ข้อจำกัดที่ควรรู้
- ทุกคำสั่งยกเว้น
secrets:provision --localต้อง ssh เข้าเซิร์ฟเวอร์ที่เก็บ key ได้ คนที่ไม่มีสิทธิ์เข้าเครื่องนั้นจึงเปิดค่าใน repo ไม่ได้ - การคัดลอก key ลง clipboard ใช้
pbcopyของ macOS บนเครื่องอื่นคำสั่งจะบอกให้ไปเก็บ key จากเซิร์ฟเวอร์แทน - ถ้าองค์กรต้องการ log ว่าใครเปิดค่าไหน หมุน key อัตโนมัติ หรือแยกสิทธิ์ละเอียดระดับตัวแปร เครื่องมืออย่าง HashiCorp Vault หรือ AWS Secrets Manager เหมาะกว่า แลกกับระบบที่ต้องดูแลเพิ่มอีกตัว แพ็กเกจนี้เหมาะกับทีมเล็กที่อยากได้ประวัติใน git และ key ที่ไม่หลุด โดยไม่ต้องเพิ่มระบบ
GitHub: phattarachai/laravel-env-secrets





