Як розгорнути ERPNext на VPS через Docker
Практичний посібник із запуску ERPNext на власному VPS: 11 контейнерів, вибір ресурсів, TLS, пошта, фіксація версій і перевірене відновлення.
Що саме ви погоджуєтеся запускати
Самостійне розгортання ERPNext на VPS — це операційне завдання, а не встановлення однією командою. Офіційний стек Docker Compose складається з одинадцяти контейнерів і містить вашу головну книгу та дані клієнтів. Тому до всіх наведених нижче дій висуваються підвищені вимоги: резервна копія не є резервною копією, доки ви не відновили з неї дані, а тег образу без фіксованої версії може призвести до міграції схеми.
У тексті часто використовуються кілька назв. ERPNext — це бізнес-застосунок. Frappe — Python-фреймворк, на якому він працює. Bench — інструмент командного рядка для керування сайтами, уже встановлений усередині контейнерів. Сайт — це один tenant: одна база даних MariaDB і один каталог завантажених файлів. Майже кожна команда в цьому матеріалі виконується bench у контейнері backend для одного сайту із заданою назвою.
У цьому посібнику використовується репозиторій frappe_docker — розгортання, яке супроводжує проєкт. Усі наведені нижче команди перевірено для цього репозиторію в серпні 2026 року. Якщо Docker Compose для вас новий, у матеріалі запуск Docker Compose на VPS розглянуто базові відомості, на які спирається цей посібник.
Скільки ресурсів VPS потрібно ERPNext?
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 RAM запустить стек, але завершить роботу під час першого імпорту або тривалого звіту. Дев’ять довготривалих контейнерів, buffer pool MariaDB і Python worker, який формує звіт, не вміщуються в такий обсяг пам’яті. Завершення роботи не буде коректним. Kernel out-of-memory killer зупиняє контейнер, а docker inspect у ньому після цього показує "OOMKilled": true з кодом завершення 137. Worker, зупинений під час виконання завдання, залишає поданий документ із незавершеною фоновою обробкою.
Для компанії, яка щодня використовує ERPNext, 8 GB RAM, 4 vCPU і 100 GB SSD — це реалістичний мінімум. Пам’ять закінчується першою. Диск заповнюється швидше, ніж очікують користувачі, оскільки кожне вкладення та кожна локальна резервна копія зберігаються на тому самому томі, що й база даних.
Одинадцять контейнерів і призначення кожного
Виконайте docker compose ps після запуску стека, коли працюють дев’ять контейнерів. Ще два, configurator і create-site, виконують свої завдання один раз і завершують роботу. Саме тому загалом контейнерів одинадцять.
backendзапускає застосунок Frappe під керуванням gunicorn. Саме тут працюєbench.frontend— це nginx. Він обслуговує статичні ресурси, а всі інші запити передає бекенду.queue-shortіqueue-long— це робочі процеси RQ (Redis Queue). Вони виконують фонові завдання, зокрема надсилання електронної пошти, імпорт і формування звітів.schedulerзапускає завдання, прив’язані до часу, зокрема заплановані звіти й документи з автоматичним повторенням.websocket— це процес socket.io, який забезпечує live-оновлення в браузері.db— це MariaDB.redis-cacheіredis-queue— два окремі екземпляри Redis: один для кешу, інший для черги завдань.
Цей поділ важливо розуміти, оскільки він підказує, який журнал потрібно читати. Якщо електронний лист застряг у черзі, проблема пов’язана з робочим процесом черги, тому потрібна команда docker compose logs -f queue-short. Якщо сторінка завантажується, але індикатор сповіщень не оновлюється, проблема пов’язана з websocket. Читання журналів backend у будь-якому з цих випадків лише марнує час.
Встановлюйте production-файли compose, а не demo
У репозиторії є 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 і змініть чотири значення. ERPNEXT_VERSION фіксує тег image. DB_PASSWORD у прикладі має значення 123. SITES_RULE — це правило маршрутизації 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Тепер згенеруйте один файл 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 нічого не запускає. Команда об’єднує базовий файл із файлами перевизначень і виводить результат, у якому всі змінні вже підставлено. Потім запустіть згенерований файл. Додатковий крок виправданий: запущений стек описано в одному файлі, який можна прочитати та додати до репозиторію, тому його вміст не зміниться непомітно після редагування env-файлу або оновлення репозиторію. У статті як об’єднуються кілька файлів Docker Compose докладно пояснено правила перевизначення.
Дочекайтеся запуску 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 показує дев’ять сервісів у стані running і жодного — у стані restarting.
Тут часто виникають дві проблеми. --mariadb-user-host-login-scope=% є обов’язковим у Docker. Контейнер застосунку підключається до MariaDB через Docker network, тому для бази даних він є віддаленим хостом. Користувач бази даних із дозволом лише для localhost не може підключитися з такого хоста. Через це створення сайту завершується помилкою доступу MariaDB, у якій зазначено користувача root. Обмеження % надає користувачу нового сайту доступ із будь-якого хоста в цій приватній мережі.
Друга проблема стосується імені сайту. За замовчуванням frontend вибирає сайт для обслуговування за значенням HTTP-заголовка Host. Тому сайт, створений як erpnext, недоступний за адресою erp.example.com, навіть якщо обидва значення існують. Назвіть сайт відповідно до домену, як показано вище, або задайте FRAPPE_SITE_NAME_HEADER в env-файлі як ім’я сайту та знову згенеруйте файл compose.
HTTPS і умови для його роботи
Конфігурація compose.https.yaml запускає Traefik на порту 443, перенаправляє порт 80 на нього та запитує сертифікати в Let's Encrypt. TLS (безпека транспортного рівня) не дає передавати рахунок і cookie сеансу мережею у відкритому тексті.
Мають виконуватися дві умови, інакше сертифікат не буде видано. Запис DNS A для erp.example.com уже має вказувати на VPS. Порти 80 і 443 мають бути доступні з інтернету, оскільки Let's Encrypt перевіряє, що ви контролюєте це ім’я, за допомогою перевірки HTTP-01 через порт 80. Перевірте мережевий firewall у свого провайдера, а також firewall на сервері. Це окремі засоби керування, і про firewall у панелі керування часто забувають.
Сертифікати зберігаються у volume cert-data за шляхом /letsencrypt/acme.json. Якщо браузер показує сертифікат за замовчуванням замість вашого, знайдіть назву сервісу проксі в docker compose --project-name erpnext ps і перегляньте його журнали, щоб знайти помилку ACME (середовище автоматичного керування сертифікатами). Запускаєте інші вебзастосунки на цьому самому сервері? У матеріалі один екземпляр Traefik перед кількома застосунками Docker Compose показано, як спільно використовувати проксі, а не створювати конфлікт за порт 443.
Вихідна пошта, або рахунки ніколи не залишають сервер
Цей крок пропускають у більшості посібників з ERPNext. Саме він визначає, чи буде система корисною. Без робочої вихідної пошти жоден рахунок не надійде клієнту, лист для скидання пароля не буде доставлений, а запланований звіт не буде надісланий. До складу стеку не входить поштовий сервер.
Не намагайтеся надсилати пошту безпосередньо з VPS через порт 25. Більшість провайдерів блокують вихідний порт 25 для нових облікових записів. Повідомлення, яким усе ж вдається вийти, часто відхиляються або потрапляють у спам, оскільки нова адреса VPS не має репутації відправника. Використовуйте автентифікований relay через порт 587.
Підтримуваний спосіб — екран Email Account в інтерфейсі ERPNext. Пароль зберігається в зашифрованому вигляді. Також можна записати ці параметри до конфігурації сайту:
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Вихідна пошта обробляється фоновим завданням. Тому повідомлення, яке не надійшло, зазвичай відображається в цьому журналі як невдале завдання, а не як помилка в браузері. Також опублікуйте для домену відправника записи SPF (sender policy framework) і DKIM (domainkeys identified mail), а потім додайте політику DMARC. Без них навіть технічно правильний рахунок може потрапити до папки «Спам» клієнта. Якщо ви хочете повністю контролювати цей шлях, self-hosted поштовий сервер Mailcow надасть вам relay під власним контролем на окремому від ERP сервері.
Резервні копії, які справді відновлюються
Дамп бази даних сам по собі не є резервною копією ERPNext. Вкладені файли та приватні файли зберігаються в каталозі sites, а не в MariaDB. Якщо відновити лише базу даних, кожне завантажене замовлення на закупівлю відобразиться як непрацююче посилання.
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-filesЦя команда записує чотири файли в 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 підтверджує, що репозиторій і надалі доступний для читання. Резервна копія 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>'Скопіюйте ключ шифрування з резервної конфігурації до відновленого сайту. Інакше його інтеграції не працюватимуть:
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Чому фіксація версій важливіша для ERPNext
На статичному сайті невказаний тег образу означає неочікуваний перезапуск. У ERPNext це означає міграцію схеми. bench migrate переписує таблиці бази даних і може змінювати дані документів без можливості скасування. Відкат виконується шляхом відновлення з резервної копії, а не через docker compose down.
Тому зафіксуйте тег. ERPNEXT_VERSION=v16.32.1 — це версія, зафіксована у власному pwd.yml репозиторію в August 2026. Не використовуйте це значення надалі без перевірки. Поточні версії наведено на сторінці релізів frappe/erpnext, а наявні теги образів — на Docker Hub. Перед оновленням ознайомтеся з примітками до версії, на яку переходите.
Саме оновлення починається зі створення резервної копії та переходу в режим обслуговування.
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, потім виконайте рендеринг, завантаження образу та міграцію.
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 змінює схему під час виконання. Якщо користувач надішле документ до частково мігрованої таблиці, записи доведеться виправляти вручну.
Переходьте між основними версіями по черзі та створюйте резервну копію перед кожним кроком. Код міграції у версії розрахований на оновлення з попередньої версії, тому пропуск основних версій запускає комбінацію міграцій, яку ніхто не тестував.
Репозиторій також містить overrides/compose.migrator.yaml, який додає контейнер і запускає bench --site all migrate під час кожного старту. Це зручно. Але якщо змінений тег спричинить docker compose up, контейнер мігрує робочу базу даних без контролю з боку адміністратора. У бізнес-системі запускайте migrate як свідомо прийняте того ранку рішення.
Захист сервера, на якому зберігаються записи клієнтів
Змініть пароль Administrator під час першого входу. У файлі compose для оцінювання admin указано як цей пароль, і користувачі переносять таку практику в production.
Змініть DB_PASSWORD, щоб він відрізнявся від 123 у example.env. Це значення потрапляє у згенерований ~/gitops/erpnext.yaml у відкритому тексті, тому chmod 600 файл і не зберігайте його в жодному git-репозиторії. Надійніший варіант: overrides/compose.mariadb-secrets.yaml читає пароль із файлу Docker secret, а не зі змінної середовища. У матеріалі робота з env-файлами та секретами в Docker Compose описано компроміси між цими підходами.
Публікуйте лише потрібні порти. З override-файлом для HTTPS назовні доступні лише порти 80 і 443. Не додавайте зіставлення ports до сервісу db, щоб спростити підключення клієнта до бази даних: так MariaDB стане доступною з публічного інтернету. Натомість використовуйте docker compose --project-name erpnext exec backend bench mariadb. На хості дозвольте порти 22, 80 і 443, решту забороніть. Також перевірте окремий мережевий firewall у провайдера.
Увімкніть двофакторну автентифікацію в System Settings для кожного облікового запису з роллю System Manager. Ця роль дає змогу читати всі документи й експортувати всі таблиці, тому ставтеся до неї як до облікового запису адміністратора, а не як до засобу спрощення роботи. Якщо ви запускаєте кілька self-hosted застосунків, Authentik як self-hosted провайдер єдиного входу кращий за додавання ще одного пароля для кожного застосунку.
Встановлюйте оновлення на хост і перезавантажуйте його після оновлень kernel. Перш ніж покладатися на автоматичне відновлення стека, перевірте згенерований файл на наявність політики restart для кожного сервісу. Без такої політики стек не запуститься після цього перезавантаження. У матеріалі як налаштувати повторний запуск стека Docker Compose після перезавантаження описано налаштування systemd.
Коли ERPNext перестає комфортно працювати на одному VPS
Один VPS може тривалий час забезпечувати роботу невеликої компанії. Ознаки того, що його ресурсів уже недостатньо:
- Фонові завдання накопичуються, тому електронні листи та імпорти надходять із затримкою у хвилини або години.
docker inspectповідомляє про контейнери зі станом"OOMKilled": trueабо кодом завершення 137.- Звіти, які раніше формувалися за дві секунди, тепер виконуються тридцять секунд, а MariaDB споживає найбільше CPU.
- Резервне копіювання триває так довго, що один запуск накладається на наступний запланований запуск.
Спочатку виділіть MariaDB ресурси, які не спільно використовуються з іншими компонентами. База даних і Python workers конкурують за ту саму пам’ять, а buffer pool потребує її найбільше. Збільшення ресурсів application server допомагає менше, ніж зазвичай очікують. У матеріалі запуск бази даних у Docker або на хості розглянуто цей вибір, а налаштування обмежень пам’яті в Docker Compose допоможе запобігти виснаженню ресурсів одного контейнера іншими, поки ви виконуєте це налаштування.
Після цього додайте queue workers, а не збільшуйте потужність web-компонента. Повільні операції ERPNext виконуються у фоновому режимі: генерація звітів і масовий імпорт. Додаткові worker-контейнери коштують дешевше за потужніший сервер і усувають саме проблему, на яку скаржаться користувачі.
FAQ
Скільки RAM потрібно ERPNext на VPS?
Опубліковані рекомендації починаються з 4 GB і 2 vCPU, але цей рівень призначений лише для оцінювання. Для компанії, яка щодня використовує систему, плануйте 8 GB, 4 vCPU і 100 GB SSD. За менших ресурсів kernel out of memory killer під навантаженням зупиняє контейнери. docker inspect реєструє це як "OOMKilled": true з кодом завершення 137. Це початкові орієнтири, а не результати вимірювань, тому протягом першого місяця контролюйте фактичне використання пам’яті.
Чи можна запускати pwd.yml у production?
Ні. У README проєкту зазначено, що цей файл призначений лише для короткочасного оцінювання. Також зазначено, що до нього не можна встановлювати custom apps. Використовуйте compose.yaml із перевизначеннями для MariaDB, Redis і HTTPS, згенеруйте з них один файл за допомогою docker compose config і запустіть цей файл.
Чому мій сайт ERPNext недоступний одразу після створення?
За замовчуванням frontend визначає, який сайт обслуговувати, за HTTP-заголовком Host. Тому назва сайту має відповідати домену у браузері. Сайт, створений як erpnext, не обслуговується за адресою erp.example.com. Створіть сайт, використавши домен як його назву, або задайте FRAPPE_SITE_NAME_HEADER у env-файлі як назву сайту, повторно згенеруйте compose-файл і перезапустіть stack.
Що має входити до резервної копії ERPNext?
Чотири файли, які потрібно зберігати разом: дамп -database.sql.gz, архіви -files.tar і -private-files.tar та копія конфігурації -site_config_backup.json. Команда bench --site erp.example.com backup --with-files створює всі чотири файли. Копія конфігурації містить encryption_key, тому під час відновлення без неї збережені паролі інтеграцій неможливо розшифрувати. Це проявляється як Encryption key is invalid! Please check site_config.json.
Як оновити ERPNext, не пошкодивши дані?
Створіть резервну копію за допомогою --with-files, увімкніть maintenance mode, змініть ERPNEXT_VERSION у env-файлі, повторно згенеруйте compose-файл, виконайте pull, запустіть stack, потім виконайте bench --site erp.example.com migrate і вимкніть maintenance mode. Переходьте лише на одну major version за раз і спочатку прочитайте release notes, оскільки migrate перезаписує схему та дані документів без можливості скасування. Для rollback відновіть резервну копію, створену на початку.