

ตอนเริ่มมี dev, UAT และ prod เราอาจอยากบิ้วอิมเมจครั้งเดียวแล้วส่งชิ้นงานเดิมผ่านแต่ละชุด วิธีนี้ช่วยให้ตามของที่ทดสอบไปถึง production ได้ แต่ยังมีคำถามว่าค่าตั้งของแอปถูกอ่านตอนไหน
ถ้าค่าบางตัวถูกฝังลง JavaScript ระหว่าง build การเปลี่ยน environment ของ container ตอนรันอาจไม่เปลี่ยนสิ่งที่ browser ได้รับครับ จึงต้องแยกความเหมือนของอิมเมจออกจากความสามารถเปลี่ยน config ของโค้ดภายใน
ใช้ Next.js เป็นตัวอย่างได้ชัด เพราะมีทั้งค่าที่ตั้งใจส่งให้ client กับค่าที่อ่านบน server ซึ่งการเลือกทางนั้นมีผลต่อแผนบิ้วและการส่งต่อหลาย environment
ค่าอย่างที่อยู่ API สาธารณะที่ browser เรียกอาจต้องอยู่ในฝั่ง client ส่วนรหัสฐานข้อมูลควรถูกใช้ในฝั่ง server ที่รับผิดชอบงานนั้น ไม่ส่งไปพร้อมหน้าเพียงเพราะกำหนดเป็น environment variable เหมือนกัน
จากนั้นแยกจังหวะอ่านเป็นช่วง build เริ่ม process หรือประมวลผล request เพราะแม้อยู่บน server ก็ยังอาจมีโค้ดที่นำค่าไปสร้างหน้าแบบ static ตอน build แล้วเก็บผลนั้นไว้ การมีคำว่า server อย่างเดียวจึงยังไม่ตอบว่าเปลี่ยนตอนรันได้แน่นอน

ทำรายการแต่ละค่าให้รู้ผู้ใช้กับช่วงเวลา จะเริ่มเห็นว่าค่าใดต้องคงที่ในชิ้นงาน และค่าใดเป็นส่วนของการตั้งระบบปลายทาง แผน build once จึงควรเกิดหลังแยกกลุ่มนี้ ไม่เริ่มจากทำให้ทุกค่าถูกอ่าน runtime โดยยังไม่รู้หน้าที่
ข้อมูลที่ไม่ลับก็ยังมีผลต่อความถูกต้อง เช่น URL ของ API ผิดชุดอาจทำให้เว็บ UAT คุยกับบริการอีก environment จึงควรมีจุดตรวจ config ตามความหมาย ไม่ถือว่าผ่านเพียงเพราะไม่มีข้อมูลลับหลุด
ใน Next.js การอ้างค่าที่มี prefix NEXT_PUBLIC_ ตามรูปแบบที่ framework รองรับสามารถถูกแทนด้วยค่าคงที่ใน bundle ระหว่าง next build เมื่อสร้างเสร็จแล้วจึงไม่ใช่การให้ browser กลับมาอ่าน environment ของ container ทุกครั้ง
ตัวอย่างสมมติ หาก build ใช้ NEXT_PUBLIC_API_BASE_URL เป็นปลายทาง UAT ชิ้นงานที่ได้อาจมีค่านั้นอยู่แล้ว การย้ายอิมเมจเดิมไป prod แล้วตั้งชื่อตัวแปรใหม่ใน process ไม่ได้รับรองว่า bundle ฝั่ง client จะถูกเขียนใหม่ตาม

การเปลี่ยนไป lookup ชื่อตัวแปรแบบ dynamic ก็ไม่ใช่วิธีทำให้ browser มี process.env ของเครื่อง server มาใช้งานเอง เพียงเพราะรูปแบบนั้นไม่ถูก inline เหมือนการอ้างตรง ต้องออกแบบช่องทางส่งค่าที่ต้องการให้ client รู้จริงๆ
ค่าสาธารณะควรถูกเลือกตามสิ่งที่ browser จำเป็นต้องใช้เท่านั้น หากรหัสถูกส่งลง bundle ไปแล้ว การซ่อนไฟล์ .env ฝั่ง server ไม่ได้เรียกข้อมูลนั้นกลับจากคนที่ได้รับชิ้นงานไป ต้องจัดการข้อมูลที่รั่วกับสร้างชิ้นงานที่แก้แล้วตามเหตุจริง
ถ้าแอปพึ่งค่าที่ฝังใน browser อยู่มากและยังไม่ต้องการเปลี่ยนรูปแบบ การบิ้วแยกตาม environment ก็เป็นทางเลือกที่ตรงกับพฤติกรรมปัจจุบัน เพียงต้องบันทึกว่าชิ้นงานแต่ละชุดมาจากโค้ดและค่าตั้งรุ่นใด
หากต้องการส่งอิมเมจเดียวจริง สามารถออกแบบให้ server อ่านค่าที่เหมาะสมตอน runtime แล้วส่งเฉพาะ public config ที่อนุญาตผ่านทางที่กำหนด เช่น ข้อมูลเริ่มต้นหรือ endpoint สำหรับ config โดยต้องดูการ render กับ cache ของทางนั้นด้วย
ไม่ควรสร้าง endpoint ที่ส่ง environment ทั้งหมดออกไปเพื่อให้ client เลือกเอง ควรมีรายการฟิลด์ที่อนุญาตและตรวจความหมายของมัน เพราะการอ่าน runtime ช่วยยืดหยุ่นเรื่องจังหวะ แต่ไม่ได้แยกความลับให้เราโดยอัตโนมัติ
อีกแนวหนึ่งคือให้ browser เรียก API ใน origin เดียวกันแล้วให้ server รับผิดชอบปลายทางตาม environment ซึ่งอาจลดค่าที่ต้องส่งไป client แต่เพิ่มงานฝั่ง server ที่ต้องออกแบบเรื่องสิทธิ์ การส่งต่อ และข้อจำกัดตามบริการ ไม่ใช่ proxy ทุกปลายทางโดยไม่มีขอบเขต
โค้ดที่อ่านค่าระหว่าง dynamic rendering สามารถเห็น environment ของ process ตามวิธีที่ framework รองรับ แต่ถ้าไปอ่านแล้วสร้างผลตอน build หรือเก็บผลไว้ใน cache อีกชั้น พฤติกรรมที่คนอ่านได้รับก็ต้องตามชั้นเหล่านั้นด้วย
เมื่อเปลี่ยน config ควรรู้ว่าต้องเริ่ม process ใหม่ ล้าง cache หรือให้ request ถัดไปอ่านค่าใหม่อย่างไร ไม่ใช้ชื่อ runtime เป็นคำรับรองว่าทุกผู้ใช้เห็นค่าใหม่พร้อมกันทันทีตั้งแต่แก้หน้าจอตั้งค่า
ถ้ามี HTML เริ่มต้นกับ client hydration ควรรักษาข้อมูลที่ทั้งสองฝั่งคาดให้ตรงกันด้วย การได้ค่าจากคนละฉบับอาจทำให้ช่วงแรกแสดงอย่างหนึ่งแล้วเปลี่ยนอีกอย่างหลัง client เริ่ม ซึ่งต้องดูทั้งความถูกต้องกับประสบการณ์ใช้งาน
ในงานหลาย replica ต้องตรวจด้วยว่าค่าถูกส่งไปครบชุดตามการ rollout หรือยัง เพราะการทดสอบผ่าน instance หนึ่งอาจไม่ได้บอกว่าทุกคำขอใช้ config รุ่นเดียวกัน ควรมีรหัส config ที่ไม่ใช่ค่าลับช่วยตามสถานะ
บางงานต้องใช้ token เพื่อดาวน์โหลด dependency ส่วนตัวตอน build ซึ่งไม่ได้หมายความว่าแอปตอนใช้งานต้องเก็บ token นั้นไว้ในอิมเมจ ควรใช้วิธีส่ง secret สำหรับขั้น build ที่เหมาะสม และตรวจว่าไฟล์หรือ log ที่ผลิตไม่ได้เก็บค่าติดออกมา
การใส่ secret ใน Dockerfile ผ่านทางที่ฝังลงชิ้นงานหรือ history ไม่ควรถูกใช้แทนการจัดความลับตามหน้าที่ เช่นเดียวกับการอ่านจาก Vault มาแล้วเขียนรวมเป็นไฟล์ใน image ก็ยังต้องดูว่าข้อมูลสุดท้ายอยู่ที่ไหน ไม่จบที่แหล่งอ่านชื่อปลอดภัย
ก่อนใช้หลาย environment ให้ทดสอบด้วยชิ้นงานที่จะส่งต่อจริง ตรวจ Network ฝั่ง browser และค่าระบุรุ่นที่ปลอดภัยในแต่ละชุด โดยไม่พิมพ์ secret ลงผลตรวจ จะเห็นว่าความตั้งใจ build once ตรงกับพฤติกรรมที่แอปทำจริงหรือยัง
การใช้อิมเมจเดียวหลาย environment ต้องรู้ว่าค่าแต่ละตัวถูกอ่านโดยใครและในจังหวะใด โดยเฉพาะ public config ของ Next.js ที่อาจถูกฝังตอน build เลือกบิ้วแยกหรือออกแบบช่องทาง runtime ให้ตรงงาน แล้วตรวจการ render แคช และ replica ร่วมกัน เมื่อชิ้นงานกับ config มีขอบเขตและรุ่นที่ตามได้ เราจะส่งต่อระบบโดยไม่หวังให้การเปลี่ยน env ตอนรันแก้ทุกค่าที่เคยถูกฝังไว้แล้วครับ