The Modulix documentation covers every module, step by step.Read the docs
All blog posts
BlogPlatform & IT

Self-hosting your ERP: what to plan for

Running your business system on your own servers gives you control, and some responsibilities. Database, storage, secrets, email, upgrades and backups: what to decide before you go live.

ZTZantrey TeamThe people who build and support Modulix 6 min read
Cover image: Self-hosting your ERP

Some businesses want their ERP on their own infrastructure: to keep data in a particular place, to fit existing operations, or simply to stay in control. Modulix supports that. It also means that a few decisions Zantrey would otherwise make for you become yours. This is the list we walk through with teams before they go live.

Know the shape of the system

Modulix runs as two stateless Node.js services, an API and a web server, in front of a database. Browsers only talk to the web server, which forwards API calls, so cookies stay first-party and you rarely need CORS settings. Uploaded files go to file storage, and emails go through an SMTP server. A one-shot migrate step updates the database before a new version starts. That is the whole picture.

Choose the database before anything else

Modulix supports PostgreSQL and MySQL from one schema definition, with the same features on both. Pick the one your team already operates, monitors and backs up. Choose carefully: there is no tool to move an existing installation from one database to the other. The application creates its own tables; all it needs is an empty database and a user that owns it.

Pick a deployment style

  • Docker Compose suits a single server. The provided file runs the database, the migration step, the API and the web server.
  • Helm suits Kubernetes. The chart deploys the API and the web server and runs migrations as a pre-install and pre-upgrade job. It does not include a database: point it at one you already run.

Both services scale horizontally. Background work (webhook deliveries, imports and exports, workflows, notifications) is claimed in the database, so several replicas can run side by side, and each worker can be switched off per process if you later want dedicated worker replicas.

Decide where files live

With one API instance, a persistent volume is enough. With several replicas, or on Kubernetes where pods have a read-only root filesystem, use S3-compatible storage such as AWS S3, MinIO or Cloudflare R2. Whatever you choose, include it in your backups: documents, receipts and attachments live there, not in the database.

Treat secrets as secrets

  • JWT_SECRET signs access tokens. Make it a long random string, at least 32 characters, kept in a secret store.
  • SECRETS_KEY encrypts stored credentials, such as an organization's email provider key or webhook signing secrets. Set it explicitly. Without it, the key is derived from JWT_SECRET, and rotating JWT_SECRET would make those credentials unreadable.
  • Change the seeded administrator's password after the first sign-in, and turn seeding off once the first install is done.

Make email work on day one

Invitations, password resets and notifications need a mail server. Set SMTP_URL, MAIL_FROM and APP_URL, the public address used in links. Without a mail server, emails are only written to the log, which is fine for a trial and confusing for everyone else. Test it by inviting a colleague.

Put it behind HTTPS

Serve the web service over HTTPS and set TRUST_PROXY on the API behind your load balancer, so rate limits and the audit log see real client addresses. Keep SIGNUP_ENABLED off unless you really want anyone on the internet to create an organization.

Plan upgrades and backups together

An upgrade is three steps: get the new images, run the migrations with the new API image, then roll out the new containers. Helm and Compose do this in order for you. Migrations are not reversed automatically, so back up the database and file storage before every upgrade, and test a restore at least once before you need one.

Monitor the right endpoint

Point your monitoring at the API's /health/ready endpoint: it fails when the database cannot be reached. The API writes structured JSON logs to standard output, with authorization headers, cookies and API keys redacted, so they can go to your existing log platform.

The short version

  1. Choose PostgreSQL or MySQL, and keep it.
  2. Compose for one server, Helm for Kubernetes.
  3. A volume for one replica, S3-compatible storage for several.
  4. Set JWT_SECRET and SECRETS_KEY, and store them safely.
  5. Configure SMTP and APP_URL, and test with an invitation.
  6. HTTPS, TRUST_PROXY, sign-up off.
  7. Back up the database and files, and test a restore.
  8. Monitor /health/ready.

The deployment guide lists every environment variable. And if you would rather not run it yourself, Zantrey Cloud hosting is available by arrangement.

  • self-hosting
  • Docker
  • Kubernetes
  • operations

Related resources