Установка ERPNext на VPS через Docker: пошаговое
Развертывание ERPNext на собственном сервере с использованием Docker Compose. Настройка стека из 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 ядра остановит контейнер, и команда 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 в обоих случаях — пустая трата времени.
Установка с использованием production-файлов 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.
Исходящая почта: почему счета не уходят с сервера
Этот этап пропускают в большинстве руководств по ERPNext, хотя именно от него зависит полезность системы. Без настроенной исходящей почты клиенты не получают счета, пользователи не могут сбросить пароль, а запланированные отчеты не доставляются. В составе стека нет собственного почтового сервера.
Не пытайтесь отправлять почту напрямую с VPS через 25 порт. Большинство провайдеров блокируют исходящий 25 порт на новых аккаунтах, а те письма, что всё же уходят, отклоняются или попадают в спам, так как у IP-адреса нового 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, а не через командную строку. Так параметр будет сохранен в зашифрованном виде и не попадет в историю команд вашей оболочки.
После этого отправьте реальное сообщение. Создайте 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. Перед переходом на новую версию обязательно ознакомьтесь с примечаниями к ней.
Само обновление начинается с создания резервной копии и перевода системы в режим обслуживания.
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 как провайдер единого входа (SSO) будет лучше, чем создание отдельного пароля для каждого приложения.
Устанавливайте обновления безопасности на хост и перезагружайте его при обновлении ядра. Прежде чем полагаться на автоматический запуск стека, проверьте итоговый файл на наличие политики restart для каждого сервиса, иначе стек не запустится после перезагрузки. В статье настройка автозапуска стека Docker Compose после перезагрузки описана настройка на стороне systemd.
Когда ERPNext перестает комфортно работать на одном VPS
Один VPS может долгое время обслуживать небольшую компанию. Признаки того, что ресурсов больше не хватает:
- Фоновые задачи накапливаются, из-за чего электронные письма и импорт данных приходят с задержкой в минуты или часы.
docker inspectсообщает о контейнерах с"OOMKilled": trueили кодом завершения 137.- Отчеты, которые раньше формировались за две секунды, теперь требуют тридцать, а процесс MariaDB потребляет все ресурсы CPU.
- Резервное копирование выполняется настолько долго, что один процесс накладывается на следующий по расписанию.
Начните с выделения MariaDB ресурсов, которые она не будет ни с кем делить, так как база данных и рабочие процессы Python конкурируют за одну и ту же память, а буферному пулу (buffer pool) требуется её всё больше. Увеличение мощности сервера приложений помогает меньше, чем ожидается. В разделе запуск базы данных в Docker или на хосте рассматривается это решение, а настройка лимитов памяти в Docker Compose предотвращает ситуацию, когда один контейнер лишает ресурсов остальные, пока вы занимаетесь оптимизацией.
После этого добавьте рабочие процессы очереди (queue workers), а не веб-мощности. Медленная работа 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 недоступен сразу после создания?
Frontend по умолчанию выбирает сайт для обслуживания на основе 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 перезаписывает схему и данные документов без возможности отмены. Откат возможен только через восстановление резервной копии, созданной перед началом процесса.