SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor

วิธีติดตั้ง ERPNext บน VPS ด้วย Docker แบบครบวงจร

เรียนรู้วิธีติดตั้ง ERPNext บน VPS ด้วย Docker Compose อย่างถูกต้อง ครอบคลุมการจัดการ 11 คอนเทนเนอร์ การตั้งค่า TLS การส่งอีเมล และการสำรองข้อมูลที่กู้คืนได้จริง

สิ่งที่คุณกำลังจะติดตั้งและใช้งาน

การทำ self-host ระบบ ERPNext บน VPS เป็นงานด้านปฏิบัติการ (operations) ไม่ใช่การติดตั้งด้วยคำสั่งเดียว สแต็กมาตรฐานของ Docker Compose ประกอบด้วยคอนเทนเนอร์ 11 ตัว และเป็นที่เก็บข้อมูลบัญชีแยกประเภททั่วไปรวมถึงบันทึกข้อมูลลูกค้าของคุณ สิ่งนี้ยกระดับมาตรฐานสำหรับทุกสิ่งที่กล่าวถึงด้านล่าง: การสำรองข้อมูลจะไม่ถือว่าเป็นการสำรองข้อมูลจนกว่าคุณจะทดสอบกู้คืนได้จริง และการใช้ image tag ที่ไม่ได้ระบุเวอร์ชันไว้ (unpinned) อาจนำไปสู่ปัญหา schema migration ที่คาดไม่ถึงได้

มีชื่อเรียกบางอย่างที่ปรากฏอยู่ตลอดคู่มือนี้ ERPNext คือแอปพลิเคชันทางธุรกิจ Frappe คือเฟรมเวิร์กภาษา Python ที่อยู่เบื้องล่าง Bench คือเครื่องมือบรรทัดคำสั่งสำหรับจัดการไซต์ ซึ่งถูกติดตั้งไว้ภายในคอนเทนเนอร์เรียบร้อยแล้ว ไซต์ (site) คือหนึ่งผู้เช่า (tenant): ประกอบด้วยฐานข้อมูล MariaDB หนึ่งชุดและไดเรกทอรีสำหรับไฟล์ที่อัปโหลดหนึ่งชุด คำสั่งเกือบทั้งหมดในที่นี้จะทำงาน bench ภายในคอนเทนเนอร์ backend โดยอ้างอิงกับไซต์ที่ระบุชื่อไว้

คู่มือนี้ใช้ repository frappe_docker ซึ่งเป็นรูปแบบการติดตั้งที่ทางโครงการดูแลรักษา คำสั่งทั้งหมดด้านล่างได้รับการตรวจสอบกับ repository ดังกล่าวในเดือนสิงหาคม 2026 หากคุณยังใหม่กับ Docker Compose คุณสามารถศึกษา การใช้งาน Docker Compose บน VPS ซึ่งครอบคลุมพื้นฐานที่คู่มือนี้ถือว่าคุณทราบแล้ว

ERPNext ต้องการ VPS ขนาดเท่าใด

ChartCommon published ERPNext sizing tiers (guidance, not a measurement)
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 ได้ แต่จะล้มเหลวในการนำเข้าข้อมูลครั้งแรกหรือการเรียกรายงานฉบับยาวครั้งแรก เนื่องจาก container ที่ทำงานค้างอยู่ 9 รายการ รวมกับ buffer pool ของ MariaDB และ Python worker ที่กำลังสร้างรายงาน ไม่สามารถบรรจุลงในหน่วยความจำขนาดนั้นได้ ความล้มเหลวนี้ไม่ได้เกิดขึ้นอย่างนุ่มนวล kernel จะสั่ง out of memory killer ให้หยุด container และเมื่อตรวจสอบด้วย docker inspect จะพบ "OOMKilled": true พร้อม exit code 137 การที่ worker ถูกหยุดกลางคันจะทำให้เอกสารที่ส่งไปแล้วมีงานเบื้องหลังที่ทำค้างอยู่

สำหรับบริษัทที่ใช้งาน ERPNext ทุกวัน 8 GB และ 4 vCPU พร้อมด้วย SSD ขนาด 100 GB คือระดับขั้นต่ำที่ใช้งานได้จริง RAM จะหมดก่อนเสมอ พื้นที่ดิสก์จะเพิ่มขึ้นเร็วกว่าที่คาดไว้ เนื่องจากไฟล์แนบทุกไฟล์และไฟล์สำรองข้อมูลในเครื่องทั้งหมดจะถูกจัดเก็บไว้ใน volume เดียวกับฐานข้อมูล

คอนเทนเนอร์ทั้ง 11 รายการและหน้าที่ของแต่ละรายการ

ให้รัน docker compose ps หลังจากที่ stack ทำงานแล้วและมีคอนเทนเนอร์ทำงานอยู่ 9 รายการ ส่วนอีก 2 รายการคือ configurator และ create-site จะทำงานเพียงครั้งเดียวแล้วจบการทำงาน ซึ่งเป็นที่มาของจำนวนรวม 11 รายการ

  • backend รันแอปพลิเคชัน Frappe ภายใต้ gunicorn ซึ่งเป็นที่อยู่ของ bench
  • frontend คือ nginx ทำหน้าที่ให้บริการไฟล์ static และส่งคำขออื่น ๆ ทั้งหมดไปยัง backend
  • queue-short และ queue-long คือ RQ (Redis Queue) workers ทำหน้าที่รันงานเบื้องหลัง เช่น การส่งอีเมล การนำเข้าข้อมูล และการสร้างรายงาน
  • scheduler ทำหน้าที่เรียกใช้งานตามเวลาที่กำหนด รวมถึงรายงานตามตารางเวลาและเอกสารที่ทำซ้ำอัตโนมัติ
  • websocket คือกระบวนการ socket.io ที่อยู่เบื้องหลังการอัปเดตข้อมูลแบบสดในเบราว์เซอร์
  • db คือ MariaDB
  • redis-cache และ redis-queue คืออินสแตนซ์ของ Redis สองรายการแยกกัน รายการหนึ่งสำหรับแคชและอีกรายการสำหรับคิวงาน

การแยกส่วนนี้เป็นสิ่งที่ควรเรียนรู้ เพราะจะช่วยให้คุณทราบว่าต้องอ่าน 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 -d

config จะยังไม่เริ่มการทำงานใดๆ คำสั่งนี้จะรวมไฟล์ฐานเข้ากับไฟล์ override แล้วแสดงผลลัพธ์ที่แทนที่ค่าตัวแปรทั้งหมดเรียบร้อยแล้ว จากนั้นคุณจึงรันไฟล์ที่ render ออกมานั้น ขั้นตอนนี้มีความสำคัญเพราะ stack ที่รันอยู่จะเป็นไฟล์เดียวที่คุณสามารถอ่านและทำ version control ได้ จึงมั่นใจได้ว่าการตั้งค่าจะไม่เปลี่ยนแปลงไปเองเมื่อมีคนแก้ไขไฟล์ env หรือเมื่อคุณ pull repository ใหม่ วิธีการรวมไฟล์ Docker Compose หลายไฟล์ อธิบายกฎการ override ไว้อย่างละเอียด

รอให้ db เริ่มทำงานและ configurator ทำงานเสร็จสิ้น ซึ่งจะใช้เวลาเพียงไม่กี่วินาที จากนั้นจึงสร้าง site

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-apps

list-apps ควรแสดงผล frappe และ erpnext พร้อมเวอร์ชันที่ใช้งาน หากระบบทำงานปกติ ps จะแสดง service จำนวน 9 รายการในสถานะ running และไม่มีรายการใดอยู่ในสถานะ restarting

ปัญหาที่มักพบได้บ่อยมีสองประการ ประการแรก --mariadb-user-host-login-scope=% ไม่ใช่สิ่งที่เลือกได้ภายใต้ Docker เนื่องจาก container ของแอปพลิเคชันเชื่อมต่อไปยัง MariaDB ผ่านเครือข่าย Docker จึงถูกมองว่าเป็น remote host ทำให้ผู้ใช้ฐานข้อมูลที่จำกัดสิทธิ์ไว้ที่ localhost ไม่สามารถล็อกอินได้ การสร้าง site จึงล้มเหลวด้วยข้อผิดพลาด MariaDB access denied สำหรับผู้ใช้ root การกำหนดสิทธิ์เป็น % จะช่วยให้ผู้ใช้ของ site ใหม่สามารถเข้าถึงได้จากทุก host บนเครือข่ายส่วนตัวนั้น

ประการที่สองคือชื่อของ site โดยปกติ frontend จะเลือก site ที่จะให้บริการจาก HTTP Host header ดังนั้น site ที่สร้างด้วยชื่อ erpnext จะไม่สามารถเข้าถึงได้ผ่าน erp.example.com แม้ว่าทั้งสองจะมีอยู่จริงก็ตาม ให้ตั้งชื่อ site ตามชื่อโดเมนดังตัวอย่างข้างต้น หรือกำหนดค่า FRAPPE_SITE_NAME_HEADER ในไฟล์ env ให้ตรงกับชื่อ site แล้วทำการ render ไฟล์ compose ใหม่อีกครั้ง

HTTPS และเงื่อนไขที่จำเป็นก่อนการใช้งาน

การตั้งค่า compose.https.yaml จะรัน Traefik บนพอร์ต 443, ทำการ redirect พอร์ต 80 มาที่พอร์ตนี้ และร้องขอใบรับรองจาก Let's Encrypt โดย TLS (transport layer security) คือสิ่งที่ป้องกันไม่ให้ใบแจ้งหนี้และ session cookie ถูกส่งผ่านเครือข่ายในรูปแบบข้อความธรรมดา (plain text)

มีสองสิ่งที่ต้องเป็นจริง มิฉะนั้นใบรับรองจะไม่มีวันออกให้ได้: DNS A record สำหรับ erp.example.com ต้องชี้มาที่ VPS แล้ว และพอร์ต 80 กับ 443 ต้องสามารถเข้าถึงได้จากอินเทอร์เน็ต เนื่องจาก Let's Encrypt จะตรวจสอบว่าคุณเป็นเจ้าของชื่อโดเมนจริงหรือไม่ผ่านการทำ HTTP-01 challenge บนพอร์ต 80 ให้ตรวจสอบ firewall ของผู้ให้บริการเครือข่ายควบคู่ไปกับ firewall บนเซิร์ฟเวอร์ด้วย เนื่องจากเป็นระบบควบคุมที่แยกจากกัน และ firewall บนหน้าจัดการ (panel) มักเป็นสิ่งที่ผู้ใช้งานลืมตรวจสอบ

ใบรับรองจะถูกเก็บไว้ใน volume cert-data ที่ตำแหน่ง /letsencrypt/acme.json หากเบราว์เซอร์แสดงใบรับรองเริ่มต้นแทนที่จะเป็นของคุณ ให้ค้นหาชื่อบริการ proxy ใน docker compose --project-name erpnext ps และอ่าน log เพื่อดูข้อผิดพลาดของ ACME (automatic certificate management environment) หากคุณรันเว็บแอปอื่นบนเซิร์ฟเวอร์เดียวกัน การใช้ Traefik หนึ่งอินสแตนซ์หน้าแอป Docker Compose หลายตัว จะแสดงวิธีแชร์ proxy แทนการแย่งใช้พอร์ต 443 กันเอง

อีเมลขาออก หรือเมื่อใบแจ้งหนี้ไม่ถูกส่งออกจากเซิร์ฟเวอร์

นี่คือขั้นตอนที่คู่มือ ERPNext ส่วนใหญ่มักข้ามไป แต่เป็นขั้นตอนที่ตัดสินว่าระบบจะใช้งานได้จริงหรือไม่ หากอีเมลขาออกไม่ทำงาน ใบแจ้งหนี้จะไม่ถึงมือลูกค้า การรีเซ็ตรหัสผ่านจะทำไม่ได้ และรายงานที่ตั้งเวลาไว้จะไม่ถูกส่งออกไป ตัว stack นี้ไม่มี mail server มาให้ในตัว

อย่าพยายามส่งอีเมลโดยตรงจาก VPS ผ่านพอร์ต 25 ผู้ให้บริการส่วนใหญ่บล็อกพอร์ต 25 ขาออกสำหรับบัญชีใหม่ และอีเมลที่ส่งออกไปได้มักจะถูกปฏิเสธหรือถูกจัดเป็นสแปม เนื่องจาก IP ของ VPS ใหม่ไม่มีชื่อเสียงในการส่งอีเมล ให้ใช้ relay ที่มีการยืนยันตัวตนผ่านพอร์ต 587 แทน

ช่องทางที่รองรับคือหน้าจอ Email Account ในอินเทอร์เฟซของ ERPNext ซึ่งจะจัดเก็บรหัสผ่านแบบเข้ารหัสไว้ คุณสามารถเขียนค่าคีย์ลงในไฟล์ site 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 แทนการใช้ command line เพื่อให้รหัสผ่านถูกจัดเก็บแบบเข้ารหัสและไม่ปรากฏในประวัติการใช้งาน 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

การสำรองข้อมูลที่กู้คืนได้จริง

การทำ database dump เพียงอย่างเดียวไม่ใช่การสำรองข้อมูลของ 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 ภายใน volume sites:

  • ไฟล์ dump ของ -database.sql.gz
  • ไฟล์ archive ของไฟล์สาธารณะ -files.tar
  • ไฟล์ archive ของไฟล์ส่วนตัว -private-files.tar
  • ไฟล์สำเนาของ site config -site_config_backup.json

ไฟล์ที่สี่คือไฟล์ที่ผู้คนมักละเลย และเป็นไฟล์ที่สร้างปัญหามากที่สุด ไฟล์นี้เก็บ encryption_key ซึ่งเป็นคีย์ที่ Frappe ใช้สำหรับเข้ารหัสรหัสผ่านที่จัดเก็บไว้ ไม่ว่าจะเป็นข้อมูลรับรองบัญชีอีเมล, คีย์ของ payment gateway และความลับของการเชื่อมต่อ (integration secret) ทั้งหมด หากคุณกู้คืนฐานข้อมูลโดยไม่มีคีย์ที่ตรงกัน เว็บไซต์จะโหลดขึ้นมาได้ตามปกติ แต่การส่งอีเมลจะล้มเหลวพร้อมข้อความ:

frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.json

ต้องเก็บไฟล์ทั้งสี่ไว้ด้วยกันเสมอ

จากนั้นให้ย้ายไฟล์ออกจากเซิร์ฟเวอร์ การสำรองข้อมูลที่เก็บไว้ภายใน volume จะไม่รอดพ้นหากเซิร์ฟเวอร์เสียหาย และคำสั่ง 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 แล้วเปรียบเทียบยอดคงเหลือปิดบัญชีกับไซต์ที่ใช้งานจริง เปิดใบแจ้งหนี้การซื้อล่าสุดแล้วดาวน์โหลดไฟล์แนบออกมา ไซต์ที่แสดงเพียงหน้าล็อกอินได้นั้นไม่ได้พิสูจน์ว่าข้อมูลถูกต้องทั้งหมด

ลบไซต์ทดสอบออกเมื่อดำเนินการเสร็จสิ้น:

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 จะเขียนทับตารางในฐานข้อมูลและอาจเขียนทับข้อมูลในเอกสาร ซึ่งไม่มีคำสั่งย้อนกลับ (undo) การย้อนคืนระบบทำได้เพียงการกู้คืนจากไฟล์สำรองเท่านั้น ไม่ใช่การใช้ docker compose down

ดังนั้น ให้ระบุ tag ให้ชัดเจน ERPNEXT_VERSION=v16.32.1 คือเวอร์ชันที่ถูกระบุไว้ใน pwd.yml ของ repository เองเมื่อเดือนสิงหาคม 2026 ห้ามนำตัวเลขดังกล่าวไปใช้ต่อโดยไม่ตรวจสอบก่อน คุณสามารถดูเวอร์ชันปัจจุบันได้ที่ หน้า releases ของ frappe/erpnext และดู tag ของ image ที่มีอยู่ได้ที่ Docker Hub โปรดอ่านบันทึกประจำรุ่นของเวอร์ชันที่คุณกำลังจะย้ายไปก่อนดำเนินการ

กระบวนการอัปเกรดเริ่มต้นด้วยการสำรองข้อมูลและเปิดโหมดบำรุงรักษา (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 ในขณะที่ทำงาน หากผู้ใช้ส่งเอกสารเข้ามาในขณะที่ตารางกำลังถูกย้ายข้อมูล จะทำให้คุณต้องแก้ไขระเบียนข้อมูลด้วยตนเอง

ให้ทยอยอัปเกรดทีละ major version โดยทำการสำรองข้อมูลคั่นระหว่างแต่ละขั้นตอน โค้ดสำหรับการย้ายข้อมูลในแต่ละรุ่นถูกเขียนขึ้นเพื่อรองรับการอัปเกรดจากรุ่นก่อนหน้าเท่านั้น การข้ามรุ่น major จะทำให้เกิดการรัน migration ในรูปแบบที่ไม่มีใครเคยทดสอบมาก่อน

นอกจากนี้ repository ยังมาพร้อมกับ overrides/compose.migrator.yaml ซึ่งจะเพิ่ม container ที่รัน bench --site all migrate ในทุกครั้งที่เริ่มระบบ แม้จะสะดวกสบาย แต่ก็หมายความว่าการทำ docker compose up ด้วย tag ที่เปลี่ยนไปจะทำให้ฐานข้อมูลการผลิตของคุณถูกย้ายข้อมูลโดยไม่มีใครเฝ้าดู สำหรับระบบธุรกิจ การรัน 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 mapping ไปยังบริการ db เพื่อให้เชื่อมต่อกับ database client ได้ง่ายขึ้น เพราะนั่นจะเป็นการนำ 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 จะกลับมาทำงานได้ตามปกติ ให้ตรวจสอบไฟล์ที่ถูกสร้างขึ้นว่ามีการตั้งค่า restart policy ไว้ในแต่ละบริการหรือไม่ เพราะ stack ที่ไม่มีการตั้งค่านี้จะไม่เริ่มทำงานใหม่หลังจากรีบูต เนื้อหาใน การทำให้ Docker Compose stack เริ่มทำงานใหม่หลังรีบูต ได้อธิบายถึงการตั้งค่าในส่วนของ systemd ไว้แล้ว

เมื่อ ERPNext ไม่สามารถทำงานบน VPS เดียวได้อย่างราบรื่นอีกต่อไป

VPS หนึ่งเครื่องสามารถรองรับบริษัทขนาดเล็กได้เป็นเวลานาน สัญญาณที่บ่งบอกว่าทรัพยากรไม่เพียงพอมีดังนี้:

  • งานเบื้องหลัง (background jobs) ค้างสะสม ทำให้การส่งอีเมลและการนำเข้าข้อมูลล่าช้าไปหลายนาทีหรือหลายชั่วโมง
  • docker inspect รายงานสถานะคอนเทนเนอร์ด้วย "OOMKilled": true หรือ exit code 137
  • รายงานที่เคยใช้เวลาประมวลผล 2 วินาทีกลับใช้เวลาถึง 30 วินาที และ MariaDB เป็นกระบวนการที่ใช้ CPU สูงสุด
  • การสำรองข้อมูลใช้เวลานานจนทับซ้อนกับรอบการทำงานถัดไปที่ตั้งไว้

เริ่มต้นด้วยการจัดสรรทรัพยากรให้ MariaDB โดยเฉพาะ เพื่อไม่ให้ต้องใช้ทรัพยากรร่วมกับส่วนอื่น เนื่องจากฐานข้อมูลและ Python workers แย่งชิงหน่วยความจำกัน และ buffer pool คือส่วนที่ต้องการหน่วยความจำเพิ่มมากที่สุด การขยายขนาด application server อาจช่วยได้น้อยกว่าที่คาดไว้ การรันฐานข้อมูลใน Docker หรือบนโฮสต์ จะช่วยให้ตัดสินใจในเรื่องนี้ได้ และ การกำหนดขีดจำกัดหน่วยความจำใน Docker Compose จะช่วยป้องกันไม่ให้คอนเทนเนอร์ตัวใดตัวหนึ่งแย่งชิงทรัพยากรจากตัวอื่นในระหว่างที่คุณดำเนินการปรับแต่ง

หลังจากนั้น ให้เพิ่มจำนวน queue workers แทนการเพิ่มขีดความสามารถของเว็บเซิร์ฟเวอร์ งานที่ล่าช้าของ ERPNext ส่วนใหญ่เป็นงานเบื้องหลัง เช่น การสร้างรายงานและการนำเข้าข้อมูลจำนวนมาก การเพิ่มจำนวนคอนเทนเนอร์ worker มีค่าใช้จ่ายน้อยกว่าการอัปเกรดเซิร์ฟเวอร์ และช่วยแก้ไขปัญหาที่ผู้ใช้งานร้องเรียนได้โดยตรง

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 รายการที่ต้องเก็บไว้ด้วยกัน ได้แก่ ไฟล์ dump ของ -database.sql.gz, ไฟล์ archive ของ -files.tar และ -private-files.tar, และสำเนาไฟล์ config ของ -site_config_backup.json การรัน bench --site erp.example.com backup --with-files จะสร้างไฟล์ทั้ง 4 รายการนี้ สำเนาไฟล์ config จะเก็บ 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 และข้อมูลเอกสารโดยไม่สามารถย้อนกลับได้ การย้อนกลับทำได้โดยการกู้คืนข้อมูลสำรองที่คุณทำไว้ตั้งแต่ตอนเริ่มต้นเท่านั้น