Как развернуть ERPNext на VPS через Docker
Пошаговое руководство по установке ERPNext на собственном сервере. Разбираем настройку стека из 11 контейнеров, конфигурацию TLS, отправку почты и правила миграции базы данных.
Что вы берете на себя при запуске
Self-hosting ERPNext на VPS — это задача по администрированию, а не установка одной командой. Официальный стек Docker Compose состоит из одиннадцати контейнеров, в которых хранятся ваша главная бухгалтерская книга и записи о клиентах. Это повышает требования ко всему, что описано ниже: резервная копия не считается таковой, пока вы её не восстановили, а не зафиксированный тег образа — это потенциальная проблема при миграции схемы базы данных.
В тексте часто встречаются несколько названий. ERPNext — это бизнес-приложение. Frappe — это Python-фреймворк, на котором оно работает. Bench — это инструмент командной строки для управления сайтами, который уже установлен внутри контейнеров. Сайт — это один арендатор: одна база данных 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 ГБ оперативной памяти до того, как в систему войдет первый пользователь. Это уровень для ознакомительных целей. Данные значения являются отправной точкой, а не результатами измерений из этого руководства; реальные потребности зависят от объема ваших документов. Последняя строка не является официальным минимумом. Это примерный уровень, при котором объем памяти перестает быть критическим фактором.
Будьте реалистичны в отношении бюджетных тарифов. VPS с 1 ГБ или 2 ГБ оперативной памяти запустит стек, но выйдет из строя при первом же импорте данных или формировании объемного отчета. Девять работающих контейнеров, буферный пул MariaDB и Python-воркер, формирующий отчет, не поместятся в такой объем памяти. Сбой произойдет внезапно. Механизм OOM Killer (Out of Memory) ядра принудительно остановит контейнер, и команда docker inspect покажет статус "OOMKilled": true с кодом завершения 137. Воркер, остановленный в процессе выполнения задачи, оставит документ в состоянии незавершенной фоновой обработки.
Для компании, использующей ERPNext ежедневно, честным минимумом являются 8 ГБ оперативной памяти и 4 vCPU с 100 ГБ SSD-накопителя. Оперативная память заканчивается в первую очередь. Дисковое пространство расходуется быстрее, чем ожидается, так как все вложения и локальные резервные копии сохраняются на том же разделе, что и база данных.
Одиннадцать контейнеров и назначение каждого из них
Запустите docker compose ps после того, как стек поднят и девять контейнеров работают. Еще два, configurator и create-site, выполняют свою задачу один раз и завершаются, поэтому в сумме их одиннадцать.
backendзапускает приложение Frappe под управлением gunicorn. Именно здесь находитсяbench.frontend— это nginx. Он отдает статические файлы, а все остальное передает на backend.queue-shortиqueue-long— это воркеры RQ (Redis Queue). Они выполняют фоновые задачи, такие как отправка электронной почты, импорт данных и формирование отчетов.schedulerзапускает задачи по расписанию, включая автоматические отчеты и документы с автоповтором.websocket— это процесс socket.io, отвечающий за обновления в реальном времени в браузере.db— это MariaDB.redis-cacheиredis-queue— это два отдельных экземпляра Redis: один для кэша, другой для очереди задач.
Это разделение важно понимать, так как оно указывает, какой лог нужно читать. Если зависло письмо, проблема в воркере очереди, поэтому правильная команда — docker compose logs -f queue-short. Если страница загружается, но значок уведомлений не обновляется, проблема в веб-сокетах. Чтение логов backend в обоих случаях — пустая трата времени.
Установка с использованием производственных compose-файлов, а не демонстрационных
Репозиторий поставляется с 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 фиксирует тег образа. 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, поэтому он определяется как удаленный хост, и пользователь базы данных, ограниченный localhost, не сможет войти в систему. Создание сайта завершается ошибкой доступа MariaDB с указанием пользователя root. Область действия % предоставляет пользователю нового сайта доступ с любого хоста в этой частной сети.
Вторая проблема — имя сайта. Фронтенд по умолчанию выбирает сайт для обслуживания на основе HTTP-заголовка Host, поэтому сайт, созданный как erpnext, будет недоступен по адресу erp.example.com, даже если оба существуют. Называйте сайт в соответствии с доменом, как показано выше, или установите FRAPPE_SITE_NAME_HEADER в env-файле равным имени сайта и снова сгенерируйте compose-файл.
HTTPS и условия, необходимые для его работы
Переопределение compose.https.yaml запускает Traefik на порту 443, перенаправляет на него запросы с порта 80 и запрашивает сертификаты у Let's Encrypt. TLS (transport layer security) предотвращает передачу счетов и сессионных cookie в открытом виде.
Для выпуска сертификата должны быть соблюдены два условия. DNS A-запись для erp.example.com уже должна указывать на VPS. Порты 80 и 443 должны быть доступны из Интернета, так как Let's Encrypt проверяет владение доменом с помощью HTTP-01 challenge на порту 80. Проверьте сетевой брандмауэр провайдера и брандмауэр на самом сервере. Это независимые уровни контроля, и про брандмауэр в панели управления часто забывают.
Сертификаты сохраняются в томе cert-data по пути /letsencrypt/acme.json. Если браузер показывает сертификат по умолчанию вместо вашего, найдите имя прокси-сервиса в docker compose --project-name erpnext ps и изучите его логи на предмет ошибок ACME (automatic certificate management environment). Запускаете другие веб-приложения на этом же сервере? В одном экземпляре Traefik перед несколькими приложениями Docker Compose показано, как использовать общий прокси, чтобы избежать конфликтов за порт 443. Второе приложение на таком сервере часто предназначено для клиентов, а self-hosted служба поддержки Chatwoot работает за тем же прокси, поэтому сотрудники, работающие со счетами, могут отвечать на письма и сообщения клиентов в одном месте.
Исходящая почта, или почему счета не покидают сервер
Это этап, который пропускают большинство руководств по ERPNext, хотя именно он определяет полезность системы. Без настроенной исходящей почты клиенты не получают счета, пользователи не могут сбросить пароль, а запланированные отчеты не доставляются. В составе стека нет собственного почтового сервера.
Не пытайтесь отправлять почту напрямую с VPS через 25 порт. Большинство провайдеров блокируют исходящий 25 порт на новых аккаунтах, а те письма, что проходят, отклоняются или попадают в спам, так как у IP-адреса нового VPS нет репутации отправителя. Используйте аутентифицируемый релей на 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, а не через командную строку. Так параметр будет сохранен в зашифрованном виде и не попадет в историю команд вашей оболочки.
Затем отправьте реальное сообщение. Создайте Sales Invoice, отправьте его на адрес, который вы контролируете, и наблюдайте за очередью в процессе:
docker compose --project-name erpnext logs -f queue-shortИсходящая почта обрабатывается фоновым заданием, поэтому сообщение, которое не дошло, обычно отображается как неудачное задание в этом логе, а не как ошибка в браузере. Опубликуйте записи SPF (sender policy framework) и DKIM (domainkeys identified mail) для домена-отправителя, а затем добавьте политику DMARC. Без них технически корректный счет все равно попадет в папку спама у клиента. Если вы предпочитаете контролировать весь путь доставки, собственный почтовый сервер Mailcow предоставит вам релей под вашим управлением, расположенный на отдельном от 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>'Теперь проверьте восстановление так, как это сделал бы бухгалтер. Откройте отчёт о дебиторской задолженности и сравните итоговый баланс с данными на рабочем сайте. Откройте недавний счёт на оплату и скачайте вложение. Сайт, который просто отображает страницу входа, не доказывает работоспособность системы.
Удалите тестовый сайт после завершения проверки:
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 репозитория в августе 2026 года. Не используйте этот номер в будущем без предварительной проверки. Актуальные релизы перечислены на странице релизов frappe/erpnext, а существующие теги образов доступны на 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, затем выполните рендеринг, загрузку образа и миграцию.
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 с измененным тегом выполнит миграцию вашей рабочей базы данных без вашего контроля. В бизнес-системах запуск миграции должен быть осознанным решением, принятым вами непосредственно перед выполнением.
Защита сервера, содержащего записи клиентов
Смените пароль администратора при первом входе в систему. В демонстрационном файле compose в качестве пароля используется admin, и эта привычка часто переносится в рабочую среду.
Измените DB_PASSWORD, заменив значение 123 в файле example.env. Это значение попадает в сгенерированный файл ~/gitops/erpnext.yaml в открытом виде, поэтому chmod 600 этот файл и не добавляйте его в репозитории git. Для обеспечения более высокого уровня безопасности используйте overrides/compose.mariadb-secrets.yaml, чтобы считывать пароль из файла Docker secret, а не из переменной окружения. В разделе работа с файлами окружения и секретами в Docker Compose описаны преимущества и недостатки этого подхода.
Открывайте только необходимые порты. При использовании HTTPS-проксирования должны быть открыты только порты 80 и 443. Не добавляйте маппинг ports для сервиса db, чтобы упростить подключение клиента базы данных: это сделает MariaDB доступной из публичного интернета. Вместо этого используйте docker compose --project-name erpnext exec backend bench mariadb. На хосте разрешите порты 22, 80 и 443, запретите остальные и проверьте настройки сетевого межсетевого экрана на стороне провайдера.
Включите двухфакторную аутентификацию в настройках системы для всех учетных записей с ролью System Manager. Эта роль позволяет читать любые документы и экспортировать любые таблицы, поэтому относитесь к ней как к учетной записи администратора, а не как к удобному инструменту. Если вы используете несколько self-hosted приложений, решение Authentik как self-hosted провайдер единого входа будет лучше, чем создание отдельного пароля для каждого приложения.
Устанавливайте обновления безопасности на хост и выполняйте перезагрузку для применения обновлений ядра. Прежде чем полагаться на автоматический запуск стека, проверьте сгенерированный файл на наличие политики restart для каждого сервиса, так как стек без этой настройки не запустится после перезагрузки. В разделе настройка автоматического запуска стека Docker Compose после перезагрузки описана работа с systemd.
Когда ERPNext перестает комфортно работать на одном VPS
Один VPS может долгое время обслуживать небольшую компанию. Признаки того, что ресурсов стало недостаточно:
- Фоновые задачи накапливаются, из-за чего электронные письма и импорт данных приходят с задержкой в минуты или часы.
docker inspectсообщает о контейнерах с"OOMKilled": trueили кодом завершения 137.- Отчеты, которые раньше формировались за две секунды, теперь требуют тридцать, а MariaDB потребляет основную часть ресурсов CPU.
- Резервное копирование выполняется настолько долго, что один процесс накладывается на следующий по расписанию.
Начните с выделения MariaDB ресурсов, которые она не будет ни с кем делить, так как база данных и рабочие процессы Python конкурируют за одну и ту же оперативную память, а буферный пул — это именно тот компонент, которому требуется больше всего ресурсов. Увеличение мощности сервера приложений помогает меньше, чем ожидается. запуск базы данных в Docker или на хосте поможет принять это решение, а настройка лимитов памяти в Docker Compose предотвратит ситуацию, когда один контейнер лишает ресурсов остальные, пока вы занимаетесь оптимизацией.
После этого добавьте воркеры очередей, а не веб-мощности. Основная часть медленной работы в ERPNext — это фоновые задачи: генерация отчетов и массовый импорт данных. Дополнительные контейнеры с воркерами стоят дешевле, чем более мощный сервер, и они устраняют проблему, на которую действительно жалуются пользователи.
FAQ
Сколько оперативной памяти требуется ERPNext на VPS?
Официальные рекомендации начинаются с 4 ГБ ОЗУ и 2 vCPU, однако этот уровень предназначен только для ознакомления. Для ежедневной работы в компании планируйте использование 8 ГБ ОЗУ и 4 vCPU с 100 ГБ SSD. При меньших ресурсах механизм OOM Killer завершает работу контейнеров под нагрузкой, что в docker inspect отображается как "OOMKilled": true с кодом завершения 137. Эти значения являются отправной точкой, а не точными замерами, поэтому отслеживайте потребление памяти в течение первого месяца.
Можно ли использовать pwd.yml в промышленной среде?
Нет. В README проекта указано, что этот файл предназначен только для кратковременного тестирования; также отмечено, что в него нельзя устанавливать сторонние приложения. Используйте compose.yaml с переопределениями для MariaDB, Redis и HTTPS, объедините их в один файл с помощью docker compose config и запускайте этот файл.
Почему мой сайт ERPNext недоступен сразу после создания?
По умолчанию фронтенд определяет, какой сайт обслуживать, на основе HTTP-заголовка Host, поэтому имя сайта должно совпадать с доменом в браузере. Сайт, созданный как erpnext, не будет доступен по адресу erp.example.com. Либо создайте сайт, используя домен в качестве его имени, либо установите FRAPPE_SITE_NAME_HEADER в файле окружения, пересоберите compose-файл и перезапустите стек.
Что должно входить в резервную копию 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, включите режим обслуживания, измените ERPNEXT_VERSION в файле окружения, пересоберите compose-файл, выполните pull, поднимите стек, затем запустите bench --site erp.example.com migrate и выключите режим обслуживания. Переходите между мажорными версиями по одной и сначала читайте примечания к выпуску, так как migrate перезаписывает схему и данные документов без возможности отката. Откат к предыдущей версии возможен только через восстановление резервной копии, созданной перед началом процесса.