Como hospedar o ERPNext em um VPS com Docker
Veja como dimensionar o VPS, operar a stack oficial com 11 containers, configurar TLS e email, fixar versões e testar a restauração do backup.
O que está a aceitar ao executar
Alojar o ERPNext num VPS é uma tarefa de operação, não uma instalação com um único comando. A stack oficial do Docker Compose tem onze contentores e contém o seu livro-razão geral e os registos dos seus clientes. Isto eleva o nível de exigência de tudo o que se segue: uma cópia de segurança só é uma cópia de segurança depois de ser restaurada, e uma tag de imagem não fixada é uma migração de esquema à espera de acontecer.
Alguns nomes aparecem ao longo do guia. ERPNext é a aplicação empresarial. Frappe é o framework Python subjacente. Bench é a ferramenta de linha de comandos que gere os sites e já está instalada dentro dos contentores. Um site é um tenant: uma base de dados MariaDB e um diretório de ficheiros carregados. Quase todos os comandos deste guia são executados bench dentro do contentor backend, para um site identificado pelo nome.
Este guia usa o repositório frappe_docker, que corresponde à implementação mantida pelo projeto. Todos os comandos abaixo foram verificados nesse repositório em agosto de 2026. Se o Docker Compose ainda for novo para si, executar o Docker Compose num VPS explica os conceitos de que este guia parte.
De quanto VPS o ERPNext precisa?
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
}
]As orientações publicadas começam em 2 vCPU e 4 GB de RAM antes de um único utilizador iniciar sessão. Esse é o nível para avaliação. Estes valores são pontos de partida, não medições deste guia, e o volume dos seus documentos determina o número real. A última linha nem sequer é um mínimo publicado. É aproximadamente o ponto em que a memória deixa de ser uma preocupação constante.
Seja realista quanto aos planos pequenos. Um VPS com 1 GB ou 2 GB inicia a stack, mas depois falha na primeira importação ou no primeiro relatório demorado, porque nove contentores de execução prolongada, o buffer pool do MariaDB e um worker Python a gerar um relatório não cabem nessa memória. A falha não é controlada. O kernel out of memory killer termina um contentor, e docker inspect nesse contentor passa a mostrar "OOMKilled": true com o código de saída 137. Um worker terminado a meio de um job deixa um documento submetido com o processamento em background incompleto.
Para uma empresa que usa o ERPNext todos os dias, 8 GB de RAM, 4 vCPU e 100 GB de SSD são o mínimo realista. A RAM esgota-se primeiro. O disco cresce mais depressa do que se espera, porque todos os anexos e backups locais são gravados no mesmo volume que a base de dados.
Os onze containers e a função de cada um
Execute docker compose ps depois de a stack estar em execução e de nove containers estarem ativos. Os outros dois, configurator e create-site, executam o trabalho uma vez e terminam. É daí que vem o total de onze containers.
backendexecuta a aplicação Frappe com gunicorn. É aqui quebenchreside.frontendé o nginx. Serve os recursos estáticos e encaminha todo o restante para o backend.queue-shortequeue-longsão workers do RQ (Redis Queue). Executam tarefas em segundo plano, como envio de email, importações e criação de relatórios.schedulerexecuta as tarefas baseadas em horário, incluindo relatórios agendados e documentos de repetição automática.websocketé o processo socket.io responsável pelas atualizações em tempo real no browser.dbé o MariaDB.redis-cacheeredis-queuesão duas instâncias Redis separadas: uma para a cache e outra para a fila de tarefas.
Vale a pena compreender esta separação, porque ela indica qual log deve consultar. Um email bloqueado é um problema do worker da fila, portanto docker compose logs -f queue-short é o comando correto. Uma página que carrega, mas nunca atualiza o contador de notificações, indica um problema de websocket. Consultar os logs de backend em qualquer um dos casos faz perder tempo.
Instale com os ficheiros Compose de produção, não com a demonstração
O repositório inclui pwd.yml, e o README é claro: "Esta configuração destina-se apenas a avaliações de curta duração. Não será possível instalar aplicações personalizadas nesta configuração." Use-a para conhecer o ERPNext durante uma tarde. Não a utilize para gerir uma empresa.
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.envAbra ~/gitops/erpnext.env e altere quatro valores. ERPNEXT_VERSION fixa a tag da imagem. DB_PASSWORD é fornecido como 123 no ficheiro de exemplo. SITES_RULE é a regra de encaminhamento do Traefik, e LETSENCRYPT_EMAIL recebe os avisos relativos aos certificados.
ERPNEXT_VERSION=v16.32.1
DB_PASSWORD=<a long random password>
SITES_RULE=Host(`erp.example.com`)
LETSENCRYPT_EMAIL=ops@example.comAgora gere um único ficheiro Compose e inicie-o.
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 não inicia nada. Funde o ficheiro base com as substituições e apresenta o resultado com todas as variáveis já substituídas. Em seguida, execute o ficheiro gerado. Este passo adicional é útil: a stack em execução fica definida num único ficheiro que pode ler e guardar no repositório. Assim, não pode mudar sem que se aperceba quando alguém edita o ficheiro env ou quando atualiza o repositório. como vários ficheiros Docker Compose são fundidos explica detalhadamente as regras de substituição.
Aguarde que db inicie e que configurator termine. Isto demora alguns segundos. Em seguida, crie o 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.comVerifique:
docker compose --project-name erpnext ps
docker compose --project-name erpnext exec backend bench --site erp.example.com list-appslist-apps deve apresentar frappe e erpnext com as respetivas versões. Um ps saudável apresenta nove serviços no estado running e nenhum no estado restarting.
Há dois problemas frequentes nesta etapa. --mariadb-user-host-login-scope=% não é opcional no Docker. O contentor da aplicação acede ao MariaDB através da rede Docker. Por isso, chega como um host remoto, e um utilizador da base de dados limitado a localhost não consegue iniciar sessão a partir daí. A criação do site falha então com um erro de acesso negado do MariaDB que identifica o utilizador root. O âmbito % concede ao utilizador do novo site acesso a partir de qualquer host nessa rede privada.
O segundo problema é o nome do site. Por predefinição, o frontend escolhe o site a servir com base no cabeçalho HTTP Host. Por isso, um site criado como erpnext não fica acessível em erp.example.com, embora ambos existam. Dê ao site o nome do domínio, como acima, ou defina FRAPPE_SITE_NAME_HEADER no ficheiro env com o nome do site e gere novamente o ficheiro Compose.
HTTPS e o que tem de estar configurado antes de funcionar
O override compose.https.yaml executa o Traefik na porta 443, redireciona a porta 80 para essa porta e solicita certificados ao Let's Encrypt. TLS (transport layer security) impede que uma fatura e um cookie de sessão sejam transmitidos em texto simples.
Duas condições têm de estar satisfeitas. Caso contrário, nenhum certificado será emitido. O registo DNS A de erp.example.com já tem de apontar para o VPS. As portas 80 e 443 têm de estar acessíveis a partir da Internet, porque o Let's Encrypt comprova que controla o nome através de um desafio HTTP-01 na porta 80. Verifique também a firewall de rede do fornecedor e a firewall do próprio servidor. São controlos separados, e a firewall do painel é frequentemente esquecida.
Os certificados são guardados no volume cert-data, em /letsencrypt/acme.json. Se o navegador apresentar um certificado predefinido em vez do seu, identifique o nome do serviço proxy em docker compose --project-name erpnext ps e consulte os respetivos logs para encontrar o erro do ACME (automatic certificate management environment). Está a executar outras aplicações web no mesmo servidor? uma instância do Traefik à frente de várias aplicações Docker Compose mostra como partilhar o proxy em vez de disputar a porta 443.
E-mail de saída, ou as faturas nunca saem do servidor
Este é o passo que a maioria dos guias do ERPNext ignora, mas é ele que determina se o sistema é útil. Sem e-mail de saída funcional, nenhuma fatura chega ao cliente, nenhum pedido de redefinição de palavra-passe é recebido e nenhum relatório agendado é entregue. A stack não inclui um servidor de e-mail.
Não tente enviar e-mail diretamente do VPS pela porta 25. A maioria dos fornecedores bloqueia a porta 25 de saída em contas novas. O que conseguir sair será rejeitado ou classificado como spam, porque um endereço de VPS novo não tem reputação de envio. Use um relay autenticado na porta 587.
O caminho suportado é o ecrã Email Account na interface do ERPNext, que armazena a palavra-passe encriptada. Também pode escrever as chaves na configuração do site:
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 armazena 587 como um número, e não como a string "587". Leia novamente o ficheiro e confirme que esses dois valores não têm aspas:
docker compose --project-name erpnext exec backend \
cat sites/erp.example.com/site_config.jsonDefina mail_password através do ecrã Email Account, e não pela linha de comandos, para que seja armazenado de forma encriptada e nunca entre no histórico da shell.
Depois, envie uma mensagem real. Crie uma Sales Invoice, envie-a por e-mail para um endereço que controle e monitorize a fila durante o processo:
docker compose --project-name erpnext logs -f queue-shortO e-mail de saída é um trabalho em segundo plano. Por isso, uma mensagem que nunca chega normalmente aparece como um trabalho falhado nesse log, e não como um erro no browser. Publique também registos SPF (sender policy framework) e DKIM (domainkeys identified mail) para o domínio de envio. Depois, adicione uma política DMARC. Sem esses registos, uma fatura tecnicamente correta continua a ser colocada na pasta de spam do cliente. Se preferir controlar todo o percurso, um servidor de e-mail Mailcow autoalojado fornece um relay sob o seu controlo, num servidor separado do ERP.
Backups que são realmente restaurados
Um dump da base de dados, por si só, não é um backup do ERPNext. Os anexos e os ficheiros privados ficam no diretório sites, não no MariaDB. Se restaurar apenas a base de dados, todas as ordens de compra carregadas voltarão como ligações quebradas.
docker compose --project-name erpnext exec backend \
bench --site erp.example.com backup --with-filesIsto grava quatro ficheiros em sites/erp.example.com/private/backups dentro do volume sites:
- um dump
-database.sql.gz - um arquivo
-files.tardos ficheiros públicos - um arquivo
-private-files.tardos ficheiros privados - uma cópia
-site_config_backup.jsonda configuração do site
O quarto ficheiro é o que muitas pessoas eliminam. E é precisamente o ficheiro que causa mais problemas. Ele contém encryption_key, a chave que o Frappe usa para encriptar as palavras-passe armazenadas: credenciais de contas de email, chaves de gateways de pagamento e todos os segredos das integrações. Restaure uma base de dados sem a chave correspondente e o site será carregado normalmente, mas o envio de email falhará com:
frappe.exceptions.ValidationError: Encryption key is invalid! Please check site_config.jsonMantenha sempre os quatro ficheiros juntos.
Depois, retire-os do servidor. Um backup dentro do volume não sobrevive à perda do servidor. Além disso, o bench elimina-o: por predefinição, elimina desse diretório os backups com mais de 24 horas.
docker compose --project-name erpnext cp \
backend:/home/frappe/frappe-bench/sites/erp.example.com/private/backups \
~/erpnext-backupsExecute isto a partir do cron e, depois, envie o diretório para um local que não administre. backups restic encriptados para armazenamento externo é a ferramenta adequada, porque encripta os dados antes do carregamento e restic check confirma que o repositório continua legível. Um backup do ERP é uma cópia de todo o seu livro contabilístico. Por isso, deve permanecer encriptado em repouso, num equipamento que não seja este.
Teste a restauração antes de precisar dela
Um backup não testado é apenas uma suposição. Teste-o num segundo site no mesmo servidor, nunca no site em produção.
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>'Copie a chave de encriptação da configuração incluída no backup para o site restaurado. Caso contrário, as integrações continuarão indisponíveis:
docker compose --project-name erpnext exec backend \
bench --site restore-test.example.com set-config encryption_key '<value from site_config_backup.json>'Agora valide a restauração como faria um contabilista. Abra o relatório de Contas a Receber e compare o saldo final com o do site em produção. Abra uma fatura de compra recente e descarregue o respetivo anexo. Um site que apresenta a página de início de sessão não prova nada.
Remova o site de teste quando terminar:
docker compose --project-name erpnext exec backend \
bench drop-site restore-test.example.comPor que o versionamento fixo é mais importante no ERPNext
Num site estático, uma tag de imagem sem versão fixa significa um reinício inesperado. No ERPNext, significa uma migração de esquema. bench migrate reescreve tabelas da base de dados e pode reescrever dados de documentos. Não existe uma forma de desfazer essa operação. Reverter significa restaurar uma cópia de segurança, não executar um docker compose down.
Por isso, fixe a tag. ERPNEXT_VERSION=v16.32.1 era a versão fixada no próprio pwd.yml do repositório em agosto de 2026. Não reutilize esse número sem o confirmar. As versões atuais estão listadas na página de versões do frappe/erpnext, e as tags de imagem existentes estão no Docker Hub. Leia as notas da versão para a qual vai atualizar antes de iniciar a atualização.
A atualização começa com uma cópia de segurança e o modo de manutenção.
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 onEdite ERPNEXT_VERSION em ~/gitops/erpnext.env e, em seguida, renderize, faça pull e execute a migração.
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 offO modo de manutenção é importante porque migrate altera o esquema durante a execução. Se um utilizador enviar um documento contra uma tabela parcialmente migrada, poderá ter de reparar os registos manualmente.
Avance uma versão principal de cada vez, com uma cópia de segurança entre cada etapa. O código de migração de uma versão foi escrito para atualizar a partir da versão anterior. Saltar versões principais executa migrações numa combinação que ninguém testou.
O repositório também inclui overrides/compose.migrator.yaml, que adiciona um contentor que executa bench --site all migrate em cada arranque. É conveniente. Também significa que um docker compose up com uma tag alterada migra a base de dados de produção sem supervisão. Num sistema empresarial, execute a migração como uma decisão tomada conscientemente naquela manhã.
Fortalecer um servidor que armazena registos de clientes
Altere a palavra-passe de Administrator no primeiro início de sessão. O ficheiro Compose de avaliação fornece admin como essa palavra-passe, e esse hábito acompanha as pessoas para ambientes de produção.
Altere DB_PASSWORD para um valor diferente de 123 em example.env. Esse valor acaba no ~/gitops/erpnext.yaml gerado em texto simples, por isso chmod 600 o ficheiro e mantenha-o fora de qualquer repositório git. Para obter mais segurança, overrides/compose.mariadb-secrets.yaml lê a palavra-passe de um ficheiro de secret do Docker, em vez de uma variável de ambiente. gestão de ficheiros env e secrets no Docker Compose explica as vantagens e limitações de cada opção.
Publique apenas o que for necessário. Com a sobreposição HTTPS, apenas as portas 80 e 443 ficam expostas. Não adicione um mapeamento ports ao serviço db para facilitar a ligação de um cliente de base de dados: isso expõe o MariaDB à Internet pública. Use docker compose --project-name erpnext exec backend bench mariadb em vez disso. No host, permita 22, 80 e 443, bloqueie o restante e verifique também a firewall de rede separada do fornecedor.
Ative a autenticação de dois fatores em System Settings para todas as contas que tenham a função System Manager. Essa função pode ler todos os documentos e exportar todas as tabelas, por isso trate-a como uma conta de administrador e não como uma mera conveniência. Se executar várias aplicações self-hosted, Authentik como fornecedor self-hosted de início de sessão único é uma opção melhor do que adicionar mais uma palavra-passe por aplicação.
Atualize o host e reinicie-o para aplicar atualizações do kernel. Antes de depender de que a stack volte a iniciar, verifique no ficheiro gerado se existe uma política restart em cada serviço, porque uma stack sem essa política permanece parada depois desse reinício. fazer com que uma stack Docker Compose volte a iniciar depois de um reboot explica a configuração do systemd.
Quando o ERPNext deixa de ser confortável num único VPS
Um VPS pode alojar uma pequena empresa durante muito tempo. Os sinais de que isso deixou de acontecer são:
- Os trabalhos em segundo plano acumulam-se, por isso os emails e as importações chegam com minutos ou horas de atraso.
docker inspectapresenta contentores com"OOMKilled": trueou código de saída 137.- Os relatórios que demoravam dois segundos passam a demorar trinta, e o processo que mantém a CPU ocupada é o MariaDB.
- Os backups demoram tanto que uma execução se sobrepõe à seguinte execução agendada.
Comece por atribuir ao MariaDB recursos que não sejam partilhados, porque a base de dados e os workers Python competem pela mesma memória, e o buffer pool é a componente que mais precisa dela. Um servidor de aplicações maior ajuda menos do que se espera. executar a base de dados no Docker ou no host aborda essa decisão, e definir limites de memória no Docker Compose impede que um contentor prive os restantes de recursos enquanto faz essa alteração.
Depois disso, adicione workers de filas em vez de aumentar a capacidade web. O trabalho lento do ERPNext é executado em segundo plano: geração de relatórios e importações em massa. Mais contentores de workers custam menos do que um servidor maior e corrigem o problema de que os utilizadores realmente se queixam.
FAQ
Quanta RAM o ERPNext precisa numa VPS?
A orientação publicada começa em 4 GB com 2 vCPU, e esse nível serve apenas para avaliação. Para uma empresa que o utiliza diariamente, planeie 8 GB e 4 vCPU com 100 GB de SSD. Abaixo disso, o OOM killer do kernel para os contentores sob carga, o que docker inspect reporta como "OOMKilled": true com o código de saída 137. Estes valores são pontos de partida, não medições, por isso monitorize a utilização de memória durante o primeiro mês.
Posso executar pwd.yml em produção?
Não. O README do projeto descreve-o como destinado apenas a avaliações de curta duração e indica que não é possível instalar aplicações personalizadas nele. Utilize compose.yaml com os overrides de MariaDB, Redis e HTTPS, renderize-os num único ficheiro com docker compose config e execute esse ficheiro.
Por que motivo o meu site ERPNext fica inacessível logo depois de o criar?
Por predefinição, o frontend escolhe o site a servir a partir do cabeçalho HTTP Host, por isso o nome do site tem de corresponder ao domínio no browser. Um site criado como erpnext não é servido em erp.example.com. Crie o site usando o domínio como nome ou defina FRAPPE_SITE_NAME_HEADER no ficheiro env com o nome do site, renderize novamente o ficheiro compose e reinicie a stack.
O que tem de estar incluído numa cópia de segurança do ERPNext?
Quatro ficheiros, mantidos juntos: o dump de -database.sql.gz, os arquivos de -files.tar e -private-files.tar e a cópia de configuração -site_config_backup.json. A execução de bench --site erp.example.com backup --with-files produz os quatro ficheiros. A cópia de configuração contém encryption_key, por isso uma restauração sem esse ficheiro deixa as palavras-passe armazenadas das integrações impossíveis de desencriptar, o que é apresentado como Encryption key is invalid! Please check site_config.json.
Como atualizo o ERPNext sem danificar os meus dados?
Faça uma cópia de segurança com --with-files, ative o modo de manutenção, altere ERPNEXT_VERSION no ficheiro env, renderize novamente o ficheiro compose, faça pull, inicie a stack, execute bench --site erp.example.com migrate e desative o modo de manutenção. Avance uma versão principal de cada vez e leia primeiro as notas de lançamento, porque migrate reescreve o schema e os dados dos documentos sem possibilidade de desfazer a operação. Reverter significa restaurar a cópia de segurança criada no início.