วิธีติดตั้ง ERPNext บน VPS ด้วย Docker แบบละเอียด
เรียนรู้วิธีติดตั้ง ERPNext บน VPS ด้วย Docker Compose อย่างถูกต้อง ครอบคลุมการจัดการ 11 คอนเทนเนอร์ การตั้งค่า TLS การส่งอีเมล และการสำรองข้อมูลที่กู้คืนได้จริง
สิ่งที่คุณกำลังจะติดตั้งและใช้งาน
การ self-host ERPNext บน VPS เป็นงานด้านปฏิบัติการ (operations) ไม่ใช่การติดตั้งด้วยคำสั่งเดียว Docker Compose stack อย่างเป็นทางการประกอบด้วยคอนเทนเนอร์ 11 รายการ และเป็นที่เก็บข้อมูลบัญชีแยกประเภททั่วไปรวมถึงบันทึกข้อมูลลูกค้าของคุณ สิ่งนี้ยกระดับมาตรฐานสำหรับทุกสิ่งที่จะกล่าวถึงต่อไปนี้: การสำรองข้อมูลจะไม่ถือว่าเป็นการสำรองข้อมูลจนกว่าคุณจะทดสอบกู้คืนได้สำเร็จ และการใช้ image tag ที่ไม่ได้ระบุเวอร์ชัน (unpinned) คือความเสี่ยงที่อาจทำให้เกิด schema migration โดยไม่คาดคิด
มีชื่อเรียกบางอย่างที่ปรากฏอยู่ตลอดคู่มือนี้ ERPNext คือแอปพลิเคชันทางธุรกิจ Frappe คือเฟรมเวิร์กภาษา Python ที่อยู่เบื้องล่าง Bench คือเครื่องมือบรรทัดคำสั่งที่ใช้จัดการไซต์ ซึ่งติดตั้งไว้ภายในคอนเทนเนอร์เรียบร้อยแล้ว ไซต์ (site) คือผู้เช่าหนึ่งราย: ประกอบด้วยฐานข้อมูล MariaDB หนึ่งชุด และไดเรกทอรีสำหรับไฟล์ที่อัปโหลดหนึ่งชุด คำสั่งเกือบทั้งหมดในที่นี้จะรัน bench ภายในคอนเทนเนอร์ backend โดยอ้างอิงกับไซต์ที่ระบุ
คู่มือนี้ใช้ repository frappe_docker ซึ่งเป็นรูปแบบการปรับใช้ที่ทางโครงการดูแลรักษา คำสั่งทั้งหมดด้านล่างได้รับการตรวจสอบกับ repository ดังกล่าวในเดือนสิงหาคม 2026 หากคุณยังใหม่กับ Docker Compose คุณสามารถศึกษา การรัน Docker Compose บน VPS ซึ่งครอบคลุมพื้นฐานที่คู่มือนี้สมมติว่าคุณทราบแล้ว
ERPNext ต้องการ VPS ขนาดเท่าใด
The data behind this chart
[
{
"label": "Evaluation",
"vcpu": 2,
"ram_gb": 4,
"disk_gb": 40
},
{
"label": "Small production",
"vcpu": 4,
"ram_gb": 8,
"disk_gb": 100
},
{
"label": "Room to grow",
"vcpu": 4,
"ram_gb": 16,
"disk_gb": 160
}
]คำแนะนำที่เผยแพร่อยู่เริ่มต้นที่ 2 vCPU และ 4 GB ของ RAM ก่อนที่จะมีผู้ใช้ล็อกอินเข้ามา นี่คือระดับสำหรับการประเมินผล ตัวเลขเหล่านี้เป็นเพียงจุดเริ่มต้น ไม่ใช่การวัดผลจากคู่มือนี้ และปริมาณเอกสารของคุณจะเป็นตัวกำหนดจำนวนที่แท้จริง แถวสุดท้ายไม่ใช่ค่าขั้นต่ำที่เผยแพร่อย่างเป็นทางการ แต่เป็นระดับที่หน่วยความจำจะไม่ใช่สิ่งที่คุณต้องกังวลอีกต่อไป
จงซื่อสัตย์กับตัวเองเกี่ยวกับแผนบริการขนาดเล็ก VPS ขนาด 1 GB หรือ 2 GB จะเริ่มการทำงานของ stack ได้ แต่จะล้มเหลวในการนำเข้าข้อมูลครั้งแรกหรือการเรียกรายงานฉบับยาวครั้งแรก เพราะคอนเทนเนอร์ที่ทำงานค้างอยู่ 9 รายการ บวกกับ buffer pool ของ MariaDB และ Python worker ที่กำลังสร้างรายงาน ไม่สามารถบรรจุลงในหน่วยความจำขนาดนั้นได้ ความล้มเหลวนี้ไม่ได้เกิดขึ้นอย่างนุ่มนวล kernel out of memory killer จะหยุดคอนเทนเนอร์ และ docker inspect บนคอนเทนเนอร์นั้นจะแสดง "OOMKilled": true พร้อม exit code 137 worker ที่ถูกหยุดทำงานกลางคันจะทิ้งเอกสารที่ส่งไว้ให้มีสถานะการทำงานเบื้องหลังที่ไม่สมบูรณ์
สำหรับบริษัทที่ใช้งาน ERPNext ทุกวัน 8 GB และ 4 vCPU พร้อมด้วย 100 GB ของ SSD คือระดับขั้นต่ำที่ใช้งานได้จริง RAM จะหมดก่อนเสมอ พื้นที่ดิสก์จะเพิ่มขึ้นเร็วกว่าที่คาดไว้ เพราะไฟล์แนบทุกไฟล์และไฟล์สำรองข้อมูลในเครื่องทั้งหมดจะถูกจัดเก็บไว้ใน volume เดียวกับฐานข้อมูล
คอนเทนเนอร์ทั้ง 11 รายการและหน้าที่ของแต่ละตัว
ให้รัน docker compose ps หลังจากที่ stack ทำงานขึ้นมาแล้วและมีคอนเทนเนอร์ทำงานอยู่ 9 รายการ ส่วนอีก 2 รายการคือ configurator และ create-site จะทำงานเพียงครั้งเดียวแล้วจบการทำงาน ซึ่งเป็นที่มาของจำนวนรวม 11 รายการ
backendรันแอปพลิเคชัน Frappe ภายใต้ gunicorn ซึ่งเป็นที่อยู่ของbenchfrontendคือ nginx ทำหน้าที่ให้บริการไฟล์ static และส่งคำขออื่นทั้งหมดไปยัง backendqueue-shortและqueue-longคือ RQ (Redis Queue) workers ทำหน้าที่รันงานเบื้องหลัง เช่น การส่งอีเมล, การนำเข้าข้อมูล และการสร้างรายงานschedulerทำหน้าที่เรียกใช้งานตามเวลาที่กำหนด รวมถึงรายงานตามตารางเวลาและเอกสารที่ทำซ้ำอัตโนมัติwebsocketคือกระบวนการ socket.io ที่อยู่เบื้องหลังการอัปเดตข้อมูลแบบสดในเบราว์เซอร์dbคือ MariaDBredis-cacheและredis-queueคืออินสแตนซ์ Redis สองตัวแยกกัน ตัวหนึ่งสำหรับ cache และอีกตัวสำหรับคิวงาน
การแยกส่วนนี้เป็นสิ่งที่ควรเรียนรู้ เพราะจะช่วยให้คุณทราบว่าต้องอ่าน log จากที่ใด หากอีเมลค้างแสดงว่าเป็นปัญหาที่ queue worker ดังนั้น docker compose logs -f queue-short จึงเป็นคำสั่งที่ถูกต้อง หากหน้าเว็บโหลดได้แต่ป้ายแจ้งเตือนไม่ยอมอัปเดต แสดงว่าเป็นปัญหาที่ websocket การอ่าน log ของ backend สำหรับปัญหาเหล่านี้จะทำให้เสียเวลาโดยเปล่าประโยชน์
ติดตั้งด้วยไฟล์ compose สำหรับใช้งานจริง ไม่ใช่ตัวเดโม
repository นี้มาพร้อมกับ pwd.yml และไฟล์ README ได้ระบุไว้อย่างชัดเจนว่า "การตั้งค่านี้มีไว้สำหรับการประเมินผลระยะสั้นเท่านั้น คุณจะไม่สามารถติดตั้งแอปพลิเคชันที่กำหนดเองลงในการตั้งค่านี้ได้" ให้ใช้เพื่อทดลองใช้งาน ERPNext ในช่วงเวลาสั้นๆ เท่านั้น ห้ามนำไปใช้รันระบบงานจริงของบริษัท
sudo apt update && sudo apt install -y git
curl -fsSL https://get.docker.com | bash
git clone https://github.com/frappe/frappe_docker
cd frappe_docker
mkdir -p ~/gitops
cp example.env ~/gitops/erpnext.envเปิดไฟล์ ~/gitops/erpnext.env และแก้ไขค่า 4 รายการ ERPNEXT_VERSION ใช้สำหรับระบุ image tag DB_PASSWORD มาพร้อมกับค่า 123 ในไฟล์ตัวอย่าง SITES_RULE คือกฎการ routing ของ Traefik และ LETSENCRYPT_EMAIL ใช้สำหรับรับการแจ้งเตือนเกี่ยวกับใบรับรอง
ERPNEXT_VERSION=v16.32.1
DB_PASSWORD=<a long random password>
SITES_RULE=Host(`erp.example.com`)
LETSENCRYPT_EMAIL=ops@example.comจากนั้นให้ render ไฟล์ compose ออกมาหนึ่งไฟล์ แล้วจึงเริ่มการทำงาน
docker compose --project-name erpnext \
--env-file ~/gitops/erpnext.env \
-f compose.yaml \
-f overrides/compose.mariadb.yaml \
-f overrides/compose.redis.yaml \
-f overrides/compose.https.yaml \
config > ~/gitops/erpnext.yaml
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml up -dconfig จะยังไม่เริ่มการทำงานใดๆ มันจะรวมไฟล์ฐานเข้ากับไฟล์ override และแสดงผลลัพธ์ที่แทนที่ตัวแปรทั้งหมดเรียบร้อยแล้ว จากนั้นคุณจึงรันไฟล์ที่ render ออกมานั้น ขั้นตอนเพิ่มเติมนี้มีประโยชน์มาก เพราะ stack ที่รันอยู่จะเป็นไฟล์เดียวที่คุณสามารถอ่านและ commit ได้ จึงไม่มีการเปลี่ยนแปลงโดยไม่ตั้งใจเมื่อมีคนแก้ไขไฟล์ env หรือเมื่อคุณ pull repository ใหม่ วิธีการรวมไฟล์ Docker Compose หลายไฟล์ อธิบายกฎการ override ไว้อย่างละเอียด
รอให้ db เริ่มทำงานและ configurator ทำงานเสร็จสิ้น ซึ่งจะใช้เวลาเพียงไม่กี่วินาที จากนั้นจึงสร้างไซต์
docker compose --project-name erpnext exec backend \
bench new-site --mariadb-user-host-login-scope=% \
--db-root-password '<your DB_PASSWORD>' \
--install-app erpnext \
--admin-password '<a strong admin password>' \
erp.example.comตรวจสอบผลลัพธ์:
docker compose --project-name erpnext ps
docker compose --project-name erpnext exec backend bench --site erp.example.com list-appslist-apps ควรแสดงผล frappe และ erpnext พร้อมเวอร์ชันของแต่ละรายการ ระบบที่ทำงานปกติของ ps จะแสดงบริการ 9 รายการในสถานะ running และไม่มีรายการใดอยู่ในสถานะ restarting
มีสองสิ่งที่มักจะผิดพลาดในขั้นตอนนี้ --mariadb-user-host-login-scope=% ไม่ใช่ตัวเลือกเสริมภายใต้ Docker เนื่องจาก container ของแอปพลิเคชันจะเข้าถึง MariaDB ผ่านเครือข่าย Docker จึงถือเป็น remote host ดังนั้นผู้ใช้ฐานข้อมูลที่จำกัดสิทธิ์ไว้ที่ localhost จึงไม่สามารถล็อกอินจากจุดนั้นได้ การสร้างไซต์จึงล้มเหลวด้วยข้อผิดพลาด MariaDB access denied โดยระบุชื่อผู้ใช้เป็น root การกำหนดสิทธิ์แบบ % จะช่วยให้ผู้ใช้ของไซต์ใหม่สามารถเข้าถึงได้จากทุก host บนเครือข่ายส่วนตัวนั้น
ประการที่สองคือชื่อไซต์ โดยปกติแล้ว frontend จะเลือกไซต์ที่จะให้บริการจาก HTTP Host header ดังนั้นไซต์ที่สร้างด้วยชื่อ erpnext จะไม่สามารถเข้าถึงได้ผ่าน erp.example.com แม้ว่าทั้งสองจะมีอยู่จริงก็ตาม ให้ตั้งชื่อไซต์ตามชื่อโดเมนดังที่แสดงด้านบน หรือกำหนดค่า FRAPPE_SITE_NAME_HEADER ในไฟล์ env ให้เป็นชื่อไซต์แล้วทำการ render ไฟล์ compose ใหม่อีกครั้ง
HTTPS และสิ่งที่ต้องเตรียมพร้อมก่อนใช้งาน
การตั้งค่า compose.https.yaml จะรัน Traefik บนพอร์ต 443 พร้อมเปลี่ยนเส้นทางพอร์ต 80 มาที่พอร์ตนี้ และร้องขอใบรับรองจาก Let's Encrypt โดย TLS (transport layer security) คือสิ่งที่ป้องกันไม่ให้ใบแจ้งหนี้และ session cookie ถูกส่งผ่านเครือข่ายในรูปแบบข้อความธรรมดา
เงื่อนไขสองประการต้องเป็นจริง มิฉะนั้นจะไม่มีการออกใบรับรองให้: DNS A record สำหรับ erp.example.com ต้องชี้มาที่ VPS แล้ว และพอร์ต 80 กับ 443 ต้องสามารถเข้าถึงได้จากอินเทอร์เน็ต เนื่องจาก Let's Encrypt จะตรวจสอบว่าคุณเป็นเจ้าของชื่อโดเมนจริงผ่านการทำ HTTP-01 challenge บนพอร์ต 80 ให้ตรวจสอบทั้ง network firewall ของผู้ให้บริการและ firewall ภายในเซิร์ฟเวอร์ด้วย เนื่องจากเป็นระบบควบคุมที่แยกจากกัน และ firewall บนหน้าเว็บจัดการมักเป็นสิ่งที่ผู้ใช้งานลืมตรวจสอบ
ใบรับรองจะถูกเก็บไว้ใน volume cert-data ที่ตำแหน่ง /letsencrypt/acme.json หากเบราว์เซอร์แสดงใบรับรองเริ่มต้นแทนที่จะเป็นของคุณ ให้ค้นหาชื่อบริการ proxy ใน docker compose --project-name erpnext ps แล้วอ่าน log เพื่อดูข้อผิดพลาดของ ACME (automatic certificate management environment) หากคุณรันเว็บแอปอื่นบนเซิร์ฟเวอร์เดียวกัน การใช้ Traefik หนึ่ง instance หน้าแอป Docker Compose หลายตัว จะแสดงวิธีแชร์ proxy แทนการแย่งใช้พอร์ต 443 แอปที่สองบนเซิร์ฟเวอร์ลักษณะนี้มักเป็นแอปที่ให้บริการลูกค้า และ การติดตั้ง Chatwoot support desk ด้วยตนเอง ก็สามารถวางไว้หลัง proxy ตัวเดียวกันนี้ได้ เพื่อให้ทีมงานที่ดูแลใบแจ้งหนี้สามารถตอบอีเมลและแชทของลูกค้าได้ในที่เดียว
อีเมลขาออก หรือเมื่อใบแจ้งหนี้ไม่ถูกส่งออกจากเซิร์ฟเวอร์
นี่คือขั้นตอนที่คู่มือ ERPNext ส่วนใหญ่มักข้ามไป แต่เป็นขั้นตอนที่ตัดสินว่าระบบจะใช้งานได้จริงหรือไม่ หากอีเมลขาออกไม่ทำงาน ใบแจ้งหนี้จะไม่ถึงมือลูกค้า การรีเซ็ตรหัสผ่านจะทำไม่ได้ และรายงานที่ตั้งเวลาไว้จะไม่ถูกส่งออกไป เนื่องจาก stack นี้ไม่มี mail server ในตัว
อย่าพยายามส่งอีเมลโดยตรงจาก VPS ผ่านพอร์ต 25 ผู้ให้บริการส่วนใหญ่บล็อกพอร์ต 25 สำหรับบัญชีใหม่ และอีเมลที่ส่งออกไปได้มักจะถูกปฏิเสธหรือถูกจัดเป็นสแปม เนื่องจาก IP ของ VPS ใหม่ไม่มีชื่อเสียงในการส่งอีเมล ให้ใช้ relay ที่มีการยืนยันตัวตนผ่านพอร์ต 587 แทน
วิธีการที่รองรับคือการตั้งค่าผ่านหน้าจอ Email Account ในอินเทอร์เฟซของ ERPNext ซึ่งจะจัดเก็บรหัสผ่านแบบเข้ารหัสไว้ คุณยังสามารถเขียนค่าคีย์ลงในไฟล์ config ของไซต์ได้โดยตรง:
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_server smtp.example.com
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_port 587 --parse
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config use_tls 1 --parse
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config mail_login 'erp@example.com'
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-config auto_email_id 'erp@example.com'--parse จะจัดเก็บ 587 เป็นตัวเลขแทนที่จะเป็นสตริง "587" ให้เปิดไฟล์อ่านเพื่อยืนยันว่าค่าทั้งสองไม่มีเครื่องหมายคำพูดล้อมรอบ:
docker compose --project-name erpnext exec backend \
cat sites/erp.example.com/site_config.jsonตั้งค่า mail_password ผ่านหน้าจอ Email Account แทนการใช้บรรทัดคำสั่ง เพื่อให้ข้อมูลถูกจัดเก็บแบบเข้ารหัสและไม่ปรากฏในประวัติการใช้งาน shell ของคุณ
จากนั้นให้ทดสอบส่งข้อความจริง สร้าง Sales Invoice แล้วส่งอีเมลไปยังที่อยู่ที่คุณควบคุมได้ พร้อมกับเฝ้าดูคิวการส่งในขณะที่ดำเนินการ:
docker compose --project-name erpnext logs -f queue-shortอีเมลขาออกเป็นงานเบื้องหลัง (background job) ดังนั้นข้อความที่ไม่ถูกส่งออกมักจะปรากฏเป็นงานที่ล้มเหลวใน log ดังกล่าว แทนที่จะแสดงข้อผิดพลาดในเบราว์เซอร์ นอกจากนี้ คุณควรประกาศระเบียน SPF (sender policy framework) และ DKIM (domainkeys identified mail) สำหรับโดเมนที่ใช้ส่งอีเมล รวมถึงเพิ่มนโยบาย DMARC หากไม่มีสิ่งเหล่านี้ ใบแจ้งหนี้ที่ถูกต้องตามเทคนิคก็ยังคงถูกส่งไปยังโฟลเดอร์สแปมของลูกค้าอยู่ดี หากคุณต้องการควบคุมเส้นทางการส่งอีเมลทั้งหมดด้วยตนเอง การติดตั้ง Mailcow mail server ด้วยตนเอง จะช่วยให้คุณมี relay ที่คุณควบคุมได้เองบนเซิร์ฟเวอร์แยกต่างหากจาก ERP
การสำรองข้อมูลที่กู้คืนได้จริง
การดัมพ์ฐานข้อมูลเพียงอย่างเดียวไม่ใช่การสำรองข้อมูลของ ERPNext ไฟล์แนบและไฟล์ส่วนตัวจะอยู่ในไดเรกทอรี sites ไม่ใช่ใน MariaDB หากกู้คืนเฉพาะฐานข้อมูล ใบสั่งซื้อที่อัปโหลดไว้ทั้งหมดจะกลายเป็นลิงก์ที่ใช้งานไม่ได้
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-filesคำสั่งดังกล่าวจะเขียนไฟล์ 4 ไฟล์ลงใน sites/erp.example.com/private/backups ภายในโวลุ่ม sites:
- ไฟล์ดัมพ์
-database.sql.gz - ไฟล์เก็บถาวร
-files.tarของไฟล์สาธารณะ - ไฟล์เก็บถาวร
-private-files.tarของไฟล์ส่วนตัว - ไฟล์สำเนา
-site_config_backup.jsonของการตั้งค่าไซต์
ไฟล์ที่สี่คือไฟล์ที่ผู้คนมักละเลย และเป็นไฟล์ที่สร้างปัญหามากที่สุด ไฟล์นี้เก็บ encryption_key ซึ่งเป็นคีย์ที่ Frappe ใช้สำหรับเข้ารหัสรหัสผ่านที่จัดเก็บไว้ เช่น ข้อมูลรับรองบัญชีอีเมล, คีย์ของเกตเวย์การชำระเงิน และความลับของการเชื่อมต่อระบบทั้งหมด หากกู้คืนฐานข้อมูลโดยไม่มีคีย์ที่ตรงกัน ไซต์จะโหลดได้ตามปกติ แต่การส่งอีเมลจะล้มเหลวพร้อมข้อความ:
frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.jsonต้องเก็บไฟล์ทั้งสี่ไว้ด้วยกันเสมอ
จากนั้นให้นำไฟล์ออกจากเซิร์ฟเวอร์ การสำรองข้อมูลที่เก็บไว้ภายในโวลุ่มจะไม่รอดหากเซิร์ฟเวอร์เสียหาย และ bench จะลบข้อมูลเหล่านั้นทิ้งอยู่ดี โดยค่าเริ่มต้นระบบจะลบไฟล์สำรองที่มีอายุเกิน 24 ชั่วโมงออกจากไดเรกทอรีดังกล่าว
docker compose --project-name erpnext cp \
backend:/home/frappe/frappe-bench/sites/erp.example.com/private/backups \
~/erpnext-backupsให้รันคำสั่งนั้นผ่าน cron จากนั้นส่งไดเรกทอรีไปยังที่อื่นที่คุณไม่ได้เป็นผู้ดูแลระบบ การสำรองข้อมูลด้วย restic ที่เข้ารหัสไปยังที่จัดเก็บภายนอก เป็นเครื่องมือที่เหมาะสมที่สุด เพราะจะทำการเข้ารหัสก่อนอัปโหลด และ restic check จะช่วยยืนยันว่า repository ยังคงอ่านได้ปกติ การสำรองข้อมูล ERP คือสำเนาของบัญชีแยกประเภททั้งหมดของคุณ ดังนั้นจึงควรจัดเก็บในรูปแบบที่เข้ารหัสไว้บนฮาร์ดแวร์อื่นที่ไม่ใช่เครื่องนี้
ทดสอบการกู้คืนข้อมูลก่อนถึงเวลาที่ต้องใช้งานจริง
การสำรองข้อมูลที่ไม่ได้ผ่านการทดสอบถือเป็นเพียงการคาดเดาเท่านั้น ให้ทดสอบการกู้คืนข้อมูลไปยังไซต์สำรองบนเครื่องเดียวกัน ห้ามกู้คืนทับไซต์ที่ใช้งานจริงโดยเด็ดขาด
docker compose --project-name erpnext exec backend \
bench new-site --mariadb-user-host-login-scope=% \
--db-root-password '<your DB_PASSWORD>' \
--admin-password '<a strong admin password>' \
restore-test.example.com
docker compose --project-name erpnext exec backend \
bench --site restore-test.example.com --force restore \
sites/erp.example.com/private/backups/<stamp>-erp.example.com-database.sql.gz \
--with-public-files sites/erp.example.com/private/backups/<stamp>-erp.example.com-files.tar \
--with-private-files sites/erp.example.com/private/backups/<stamp>-erp.example.com-private-files.tar \
--db-root-password '<your DB_PASSWORD>'คัดลอก encryption key จากไฟล์ config ที่สำรองไว้ไปยังไซต์ที่กู้คืน มิฉะนั้นการเชื่อมต่อกับระบบอื่นจะใช้งานไม่ได้:
docker compose --project-name erpnext exec backend \
bench --site restore-test.example.com set-config encryption_key '<value from site_config_backup.json>'จากนั้นให้ตรวจสอบการกู้คืนข้อมูลด้วยวิธีเดียวกับที่นักบัญชีทำ ให้เปิดรายงาน Accounts Receivable แล้วเปรียบเทียบยอดคงเหลือปิดบัญชีกับไซต์ที่ใช้งานจริง เปิดใบแจ้งหนี้การซื้อล่าสุดแล้วดาวน์โหลดไฟล์แนบออกมา ไซต์ที่แสดงหน้า login ได้นั้นไม่ได้เป็นเครื่องพิสูจน์ว่าข้อมูลถูกต้องทั้งหมด
ลบไซต์ทดสอบออกเมื่อดำเนินการเสร็จสิ้น:
docker compose --project-name erpnext exec backend \
bench drop-site restore-test.example.comเหตุผลที่การระบุเวอร์ชัน (version pinning) มีความสำคัญยิ่งสำหรับ ERPNext
สำหรับเว็บไซต์แบบ static การไม่ระบุ tag ของ image อาจทำให้เกิดการ restart โดยไม่คาดคิด แต่สำหรับ ERPNext สิ่งนี้หมายถึงการทำ schema migration โดย bench migrate จะเขียนทับตารางในฐานข้อมูลและอาจแก้ไขข้อมูลในเอกสาร ซึ่งไม่สามารถย้อนกลับได้ การย้อนคืนระบบทำได้เพียงการกู้คืนจากไฟล์สำรอง (backup) เท่านั้น ไม่ใช่การใช้ docker compose down
ดังนั้น ควรระบุ tag ให้ชัดเจน โดย ERPNEXT_VERSION=v16.32.1 คือเวอร์ชันที่ถูกระบุไว้ใน pwd.yml ของ repository เองเมื่อเดือนสิงหาคม 2026 ห้ามนำตัวเลขดังกล่าวไปใช้ต่อโดยไม่ได้ตรวจสอบก่อน คุณสามารถดูเวอร์ชันปัจจุบันได้ที่ หน้า releases ของ frappe/erpnext และดู tag ของ image ที่มีอยู่ได้ที่ Docker Hub โปรดอ่านบันทึกประจำรุ่น (release notes) ของเวอร์ชันที่คุณกำลังจะย้ายไปก่อนดำเนินการเสมอ
กระบวนการอัปเกรดเริ่มต้นด้วยการสำรองข้อมูลและเข้าสู่โหมดบำรุงรักษา (maintenance mode)
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-files
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-maintenance-mode onแก้ไข ERPNEXT_VERSION ใน ~/gitops/erpnext.env จากนั้นทำการ render, pull และ migrate
docker compose --project-name erpnext \
--env-file ~/gitops/erpnext.env \
-f compose.yaml \
-f overrides/compose.mariadb.yaml \
-f overrides/compose.redis.yaml \
-f overrides/compose.https.yaml \
config > ~/gitops/erpnext.yaml
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml pull
docker compose --project-name erpnext -f ~/gitops/erpnext.yaml up -d
docker compose --project-name erpnext exec backend \
bench --site erp.example.com migrate
docker compose --project-name erpnext exec backend \
bench --site erp.example.com set-maintenance-mode offโหมดบำรุงรักษามีความสำคัญเนื่องจาก migrate จะทำการปรับเปลี่ยน schema ในขณะที่ทำงาน หากผู้ใช้ส่งเอกสารเข้ามาในขณะที่ตารางกำลังถูกย้ายข้อมูล จะทำให้คุณต้องลงมือแก้ไขระเบียน (record) ด้วยตนเอง
ควรย้ายเวอร์ชันหลัก (major version) ทีละขั้น และทำ backup ระหว่างแต่ละขั้นตอนเสมอ โค้ดสำหรับการย้ายข้อมูลในแต่ละรุ่นถูกเขียนขึ้นเพื่อรองรับการอัปเกรดจากรุ่นก่อนหน้าเท่านั้น การข้ามเวอร์ชันหลักจะทำให้เกิดการรัน migration ในรูปแบบที่ไม่มีใครเคยทดสอบมาก่อน
นอกจากนี้ repository ยังมาพร้อมกับ overrides/compose.migrator.yaml ซึ่งจะเพิ่ม container ที่รัน bench --site all migrate ในทุกครั้งที่เริ่มระบบ แม้จะดูสะดวก แต่ก็หมายความว่าการทำ docker compose up ด้วย tag ที่เปลี่ยนไปจะทำให้ฐานข้อมูลในระบบผลิต (production) ของคุณถูกย้ายข้อมูลโดยไม่มีผู้ดูแล สำหรับระบบธุรกิจ การรัน migrate ควรเป็นสิ่งที่คุณตัดสินใจทำด้วยตนเองในเวลานั้นๆ
การเสริมความปลอดภัยให้กับเซิร์ฟเวอร์ที่เก็บข้อมูลลูกค้า
เปลี่ยนรหัสผ่าน Administrator ในการเข้าสู่ระบบครั้งแรก ไฟล์ compose ที่ใช้สำหรับการประเมินผลกำหนดให้ admin เป็นรหัสผ่านเริ่มต้น และความเคยชินนี้มักติดตัวผู้ใช้งานไปจนถึงสภาพแวดล้อมการผลิต (production)
เปลี่ยนค่า DB_PASSWORD ออกจาก 123 ในไฟล์ example.env ค่าดังกล่าวจะถูกแสดงเป็นข้อความธรรมดาในไฟล์ ~/gitops/erpnext.yaml ที่ถูกเรนเดอร์ออกมา ดังนั้นให้ chmod 600 ไฟล์ดังกล่าวและเก็บไว้ให้ห่างจาก repository ของ git สำหรับวิธีที่ปลอดภัยยิ่งขึ้น overrides/compose.mariadb-secrets.yaml จะอ่านรหัสผ่านจากไฟล์ Docker secret แทนการใช้ environment variable หัวข้อ การจัดการไฟล์ env และ secret ใน Docker Compose ได้อธิบายถึงข้อดีและข้อเสียของแต่ละวิธีไว้แล้ว
เปิดเผยเฉพาะพอร์ตที่จำเป็นเท่านั้น เมื่อมีการตั้งค่า HTTPS override พอร์ต 80 และ 443 จะเป็นพอร์ตเดียวที่เปิดใช้งาน อย่าเพิ่มการแมป ports ให้กับบริการ db เพียงเพื่อให้เชื่อมต่อกับฐานข้อมูลได้ง่ายขึ้น เพราะนั่นจะเป็นการนำ MariaDB ไปวางไว้บนอินเทอร์เน็ตสาธารณะ ให้ใช้ docker compose --project-name erpnext exec backend bench mariadb แทน สำหรับบนโฮสต์ ให้เปิดใช้งานพอร์ต 22, 80 และ 443 เท่านั้น และปฏิเสธการเชื่อมต่อพอร์ตอื่นทั้งหมด รวมถึงตรวจสอบ firewall ของเครือข่ายที่ผู้ให้บริการจัดเตรียมไว้ให้ด้วย
เปิดใช้งานการยืนยันตัวตนแบบสองชั้น (two-factor authentication) ใน System Settings สำหรับทุกบัญชีที่มีบทบาทเป็น System Manager เนื่องจากบทบาทนี้สามารถอ่านเอกสารและส่งออกตารางข้อมูลได้ทั้งหมด จึงควรปฏิบัติกับบัญชีนี้เสมือนเป็นบัญชีผู้ดูแลระบบ ไม่ใช่เพียงบัญชีเพื่อความสะดวก หากคุณใช้งานแอปพลิเคชันแบบ self-hosted หลายตัว การใช้ Authentik ในฐานะผู้ให้บริการ single sign-on แบบ self-hosted จะเป็นทางเลือกที่ดีกว่าการเพิ่มรหัสผ่านแยกสำหรับแต่ละแอป
แพตช์โฮสต์และรีบูตเครื่องเมื่อมีการอัปเดต kernel ก่อนที่จะเชื่อมั่นว่า stack จะกลับมาทำงานได้เอง ให้ตรวจสอบไฟล์ที่ถูกเรนเดอร์ว่ามีการตั้งค่า policy เป็น restart ในแต่ละบริการหรือไม่ เพราะหากไม่มีการตั้งค่านี้ stack จะไม่กลับมาทำงานหลังจากรีบูต การทำให้ Docker Compose stack เริ่มทำงานใหม่หลังรีบูต ได้อธิบายรายละเอียดในส่วนของ systemd ไว้แล้ว
เมื่อ ERPNext ไม่สามารถทำงานบน VPS เดียวได้อย่างราบรื่นอีกต่อไป
VPS หนึ่งเครื่องสามารถรองรับบริษัทขนาดเล็กได้เป็นเวลานาน สัญญาณที่บ่งบอกว่าทรัพยากรไม่เพียงพอมีดังนี้:
- งานเบื้องหลัง (Background jobs) ค้างสะสม ส่งผลให้อีเมลและการนำเข้าข้อมูลล่าช้าไปหลายนาทีหรือหลายชั่วโมง
docker inspectรายงานสถานะ container ด้วย"OOMKilled": trueหรือ exit code 137- รายงานที่เคยใช้เวลาประมวลผล 2 วินาทีกลับใช้เวลาถึง 30 วินาที และ MariaDB เป็นกระบวนการที่ใช้ CPU สูงสุด
- การสำรองข้อมูลใช้เวลานานจนทับซ้อนกับรอบการทำงานถัดไป
เริ่มต้นด้วยการจัดสรรทรัพยากรให้ MariaDB โดยเฉพาะ เพื่อไม่ให้ต้องใช้ทรัพยากรร่วมกับส่วนอื่น เนื่องจากฐานข้อมูลและ Python workers แย่งชิงหน่วยความจำกัน และ buffer pool คือส่วนที่ต้องการหน่วยความจำเพิ่มมากที่สุด การขยายขนาด application server มักช่วยได้น้อยกว่าที่คาดไว้ การรันฐานข้อมูลใน Docker หรือบนโฮสต์ จะช่วยให้คุณตัดสินใจในเรื่องนี้ได้ และ การตั้งค่าขีดจำกัดหน่วยความจำใน Docker Compose จะช่วยป้องกันไม่ให้ container หนึ่งแย่งชิงทรัพยากรจาก container อื่นในระหว่างที่คุณดำเนินการปรับปรุงระบบ
หลังจากนั้น ให้เพิ่มจำนวน queue workers แทนการเพิ่มขีดความสามารถของเว็บเซิร์ฟเวอร์ งานที่ล่าช้าของ ERPNext ส่วนใหญ่เป็นงานเบื้องหลัง เช่น การสร้างรายงานและการนำเข้าข้อมูลจำนวนมาก การเพิ่มจำนวน worker containers มีค่าใช้จ่ายน้อยกว่าการอัปเกรดเซิร์ฟเวอร์ และยังช่วยแก้ไขปัญหาที่ผู้ใช้งานพบเจอได้โดยตรง
FAQ
ERPNext ต้องการ RAM เท่าไรบน VPS?
คำแนะนำที่เผยแพร่เริ่มต้นที่ 4 GB พร้อม 2 vCPU ซึ่งระดับนั้นมีไว้สำหรับการประเมินผลเท่านั้น สำหรับบริษัทที่ใช้งานจริงทุกวัน ให้วางแผนที่ 8 GB และ 4 vCPU พร้อมพื้นที่ SSD 100 GB หากต่ำกว่านี้ kernel out of memory killer จะหยุดการทำงานของคอนเทนเนอร์เมื่อมีโหลดสูง ซึ่ง docker inspect จะรายงานเป็น "OOMKilled": true พร้อม exit code 137 ค่าเหล่านี้เป็นเพียงจุดเริ่มต้นไม่ใช่ค่าที่วัดผลได้จริง ดังนั้นควรติดตามการใช้หน่วยความจำของคุณเองตลอดเดือนแรก
ฉันสามารถรัน pwd.yml ในสภาพแวดล้อม production ได้หรือไม่?
ไม่ได้ ไฟล์ README ของโปรเจกต์ระบุว่ามีไว้สำหรับการประเมินผลระยะสั้นเท่านั้น และระบุว่าคุณไม่สามารถติดตั้งแอปพลิเคชันเพิ่มเติมเข้าไปได้ ให้ใช้ compose.yaml ร่วมกับ MariaDB, Redis และ HTTPS overrides จากนั้นรวมไฟล์เข้าด้วยกันเป็นไฟล์เดียวด้วย docker compose config แล้วจึงรันไฟล์นั้น
ทำไมไซต์ ERPNext ของฉันถึงเข้าใช้งานไม่ได้ทันทีหลังจากสร้างเสร็จ?
โดยค่าเริ่มต้น frontend จะเลือกไซต์ที่จะให้บริการจาก HTTP Host header ดังนั้นชื่อไซต์ต้องตรงกับโดเมนในเบราว์เซอร์ ไซต์ที่สร้างด้วยชื่อ erpnext จะไม่ถูกให้บริการที่ erp.example.com ให้สร้างไซต์โดยใช้ชื่อโดเมนเป็นชื่อไซต์ หรือตั้งค่า FRAPPE_SITE_NAME_HEADER ในไฟล์ env ให้เป็นชื่อไซต์ จากนั้น render ไฟล์ compose ใหม่อีกครั้งและรีสตาร์ท stack
สิ่งที่ต้องมีในการสำรองข้อมูล ERPNext คืออะไร?
ไฟล์ 4 รายการที่ต้องเก็บไว้ด้วยกัน ได้แก่ ไฟล์ดัมพ์ -database.sql.gz, ไฟล์เก็บถาวร -files.tar และ -private-files.tar, และสำเนาไฟล์คอนฟิก -site_config_backup.json การรัน bench --site erp.example.com backup --with-files จะสร้างไฟล์ทั้ง 4 รายการนี้ สำเนาไฟล์คอนฟิกจะเก็บ encryption_key ไว้ ดังนั้นหากกู้คืนโดยไม่มีไฟล์นี้ รหัสผ่านของการเชื่อมต่อที่บันทึกไว้จะไม่สามารถถอดรหัสได้ ซึ่งจะแสดงผลเป็น Encryption key is invalid! Please check site_config.json
ฉันจะอัปเกรด ERPNext โดยไม่ทำให้ข้อมูลเสียหายได้อย่างไร?
สำรองข้อมูลด้วย --with-files, เปิดโหมด maintenance, เปลี่ยนค่า ERPNEXT_VERSION ในไฟล์ env ของคุณ, render ไฟล์ compose ใหม่อีกครั้ง, ทำการ pull, รัน stack ขึ้นมา, จากนั้นรัน bench --site erp.example.com migrate แล้วปิดโหมด maintenance ให้ย้ายเวอร์ชันหลักทีละขั้นและอ่านบันทึกการเปลี่ยนแปลง (release notes) ก่อนเสมอ เพราะ migrate จะเขียนทับ schema และข้อมูลเอกสารโดยไม่สามารถย้อนกลับได้ การย้อนกลับ (rollback) หมายถึงการกู้คืนข้อมูลจากไฟล์สำรองที่คุณทำไว้ตั้งแต่เริ่มต้นเท่านั้น