Applications Azure

IDURAR ERP CRM on Ubuntu 24.04 on Azure User Guide

| Product: IDURAR ERP CRM on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of IDURAR ERP CRM on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. IDURAR is a self hosted, open source ERP and CRM for small businesses and consultancies: it manages customers, raises quotes, turns them into invoices, records payments against those invoices, and renders any of them as a PDF you can send to a client. It is built on the MERN stack, with a React and Ant Design admin console in front of an Express API and a MongoDB datastore.

The cloudimg image ships the free and open source, AGPL-3.0 licensed IDURAR release 4.1.0, with the API, the prebuilt admin console, MongoDB and nginx already wired together and captured into the VM, so your instance is usable within a minute of first boot.

Nothing here ships with a known credential. That matters more than usual with this product: the upstream setup routine hard codes an administrator account, and upstream commits an environment file containing a placeholder token signing secret. This image ships neither. The seeded account is destroyed during the build, and a single administrator with a random 24 character password, a fresh signing secret and fresh MongoDB passwords are generated on the first boot of each VM, before the application is allowed to start. IDURAR has no public sign up route, so that one administrator is the only way in. Backed by 24/7 cloudimg support.

IDURAR is a trademark of its respective owner. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by IDURAR. It ships the free and open source AGPL-3.0 licensed self hosted software, with a single documented change described under "Differences from a stock install" below.

The mongod, nginx, idurar and idurar-firstboot services all reporting active, with Node.js 22, MongoDB 8.0 and nginx versions, the pinned IDURAR release and a HTTP 200 from the web console

What is included:

  • IDURAR ERP CRM 4.1.0 (AGPL-3.0), pinned by git tag and verified by SHA-256 checksum at build time
  • The React and Ant Design admin console, prebuilt to same origin relative API paths so it works at any address with no reconfiguration
  • Node.js 22 LTS running the Express API as idurar.service, bound to the loopback interface only
  • MongoDB 8.0 Community with SCRAM-SHA-256 authentication, bound to the loopback interface only
  • nginx on port 80 serving the console and reverse proxying /api, /download and /public
  • PhantomJS 2.1.1 as the PDF rendering engine for invoices, quotes and payment receipts
  • idurar-firstboot.service, which generates every secret and the single administrator account on first boot
  • No administrator account, no signing secret and no database password baked into the image
  • Ubuntu 24.04 LTS base with latest security patches applied at build time
  • Azure Linux Agent for seamless cloud integration and SSH key injection
  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region
  • Subscription to the IDURAR ERP CRM listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point for a small team. For heavier use, or a large invoice history, choose Standard_D2s_v5 or larger. NSG inbound: allow 22/tcp from your management network and 80/tcp from the networks that use the console. Port 443/tcp is used once you attach your own certificate, as shown in Step 10.

Step 1: Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for IDURAR ERP CRM by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create and Create.

Step 2: Deploy from the Azure CLI

RG="idurar-prod"; LOCATION="eastus"; VM_NAME="idurar-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/idurar-erp-crm-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name idurar-vnet --address-prefix 10.100.0.0/16 --subnet-name idurar-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name idurar-nsg
az network nsg rule create -g "$RG" --nsg-name idurar-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name idurar-nsg --name allow-http --priority 110 \
  --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name idurar-vnet --subnet idurar-subnet --nsg idurar-nsg --public-ip-sku Standard

Step 3: Confirm the services are running

Connect over SSH as azureuser, then confirm all four units are active. idurar-firstboot.service is a one shot unit that reports active once it has completed successfully.

systemctl is-active mongod nginx idurar idurar-firstboot

Expected output:

active
active
active
active

Check the release that is actually installed, and that the console answers:

cat /var/lib/cloudimg/idurar-version
curl -s -o /dev/null -w 'IDURAR web console: HTTP %{http_code}\n' http://127.0.0.1/

Expected output:

idurar.tag=4.1.0
idurar.package_json=4.1.0
idurar.tarball_sha256=556e732f03f5292a6e0f0d7789660f4e9213b0fd0f5fe9d7d04504e07ec1afaa
phantomjs.version=2.1.1
node.version=v22.23.1
IDURAR web console: HTTP 200

Step 4: Retrieve your per VM credentials

Every secret on this VM was generated on its first boot and exists nowhere else. They are written to a root only file.

sudo cat /root/idurar-credentials.txt

The per VM credentials file showing the console URL, the administrator email, and the MongoDB administrator and application accounts with their passwords masked, and the file confirmed as mode 600 owned by root

The file is readable only by root. Confirm that for yourself:

sudo stat -c '%n  mode=%a  owner=%U:%G' /root/idurar-credentials.txt

Expected output:

/root/idurar-credentials.txt  mode=600  owner=root:root

Copy the administrator email and password into your password manager now, then continue.

Step 5: Sign in to the console

Browse to http://<your-vm-public-ip>/. You are presented with the IDURAR sign in page. Enter the idurar.admin.email and idurar.admin.password values from the credentials file.

The IDURAR sign in page with the administrator email entered and the password masked

You can prove the same credentials work from the command line before you open a browser. This reads the generated password straight from the credentials file, so there is nothing to paste:

ADMIN_EMAIL="$(sudo grep '^idurar.admin.email=' /root/idurar-credentials.txt | cut -d= -f2-)"
ADMIN_PASSWORD="$(sudo grep '^idurar.admin.password=' /root/idurar-credentials.txt | cut -d= -f2-)"
curl -s -X POST http://127.0.0.1/api/login \
  -H 'Content-Type: application/json' \
  -d "{\"email\":\"${ADMIN_EMAIL}\",\"password\":\"${ADMIN_PASSWORD}\"}" \
  | jq '{success, email: .result.email, role: .result.role}'

Expected output:

{
  "success": true,
  "email": "admin@example.com",
  "role": "owner"
}

Once signed in you land on the dashboard, which summarises invoices, quotes and customers for the current month.

The IDURAR dashboard showing invoice and quote totals for the month, a breakdown of invoice and quote status, and the active customer gauge

Step 6: Change the administrator address and password

The administrator email is a documented placeholder, not a secret. Change it to a real address you control, and set your own password, from Settings in the console sidebar, or from the profile menu at the top right. Do this before you invite anyone else.

Step 7: Add a customer and raise an invoice

The core workflow is Customers → Quote → Invoices → Payments. Add a customer from Customers → Add New Client, then raise an invoice from Invoices → Add New Invoice, choosing that customer, adding line items, and selecting a tax rate.

The IDURAR invoice list showing several invoices with their number, customer, dates, totals and status

You can do the same over the API. This creates a customer and an invoice, then reads back the calculated totals:

ADMIN_EMAIL="$(sudo grep '^idurar.admin.email=' /root/idurar-credentials.txt | cut -d= -f2-)"
ADMIN_PASSWORD="$(sudo grep '^idurar.admin.password=' /root/idurar-credentials.txt | cut -d= -f2-)"
TOKEN="$(curl -s -X POST http://127.0.0.1/api/login -H 'Content-Type: application/json' \
  -d "{\"email\":\"${ADMIN_EMAIL}\",\"password\":\"${ADMIN_PASSWORD}\"}" | jq -r .result.token)"

CLIENT_ID="$(curl -s -X POST http://127.0.0.1/api/client/create \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"name":"Example Customer Ltd","country":"United Kingdom","email":"accounts@example.com"}' \
  | jq -r .result._id)"

curl -s -X POST http://127.0.0.1/api/invoice/create \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d "{\"client\":\"${CLIENT_ID}\",\"number\":5001,\"year\":$(date -u +%Y),\"status\":\"draft\",\"date\":\"$(date -u +%Y-%m-%d)\",\"expiredDate\":\"$(date -u -d '+30 days' +%Y-%m-%d)\",\"taxRate\":20,\"items\":[{\"itemName\":\"Consulting\",\"description\":\"Example line item\",\"quantity\":2,\"price\":500,\"total\":1000}]}" \
  | jq '{success, number: .result.number, subTotal: .result.subTotal, taxTotal: .result.taxTotal, total: .result.total}'

Expected output:

{
  "success": true,
  "number": 5001,
  "subTotal": 1000,
  "taxTotal": 200,
  "total": 1200
}

Step 8: Download an invoice as a PDF

Open any invoice and choose Download PDF. The document is rendered on the VM by PhantomJS.

An individual IDURAR invoice showing the customer, status, subtotal, tax and total, the line items, and the Download PDF and Send By Email actions

The same is available over HTTP. The download path is /download/invoice/invoice-<invoice-id>.pdf:

ADMIN_EMAIL="$(sudo grep '^idurar.admin.email=' /root/idurar-credentials.txt | cut -d= -f2-)"
ADMIN_PASSWORD="$(sudo grep '^idurar.admin.password=' /root/idurar-credentials.txt | cut -d= -f2-)"
TOKEN="$(curl -s -X POST http://127.0.0.1/api/login -H 'Content-Type: application/json' \
  -d "{\"email\":\"${ADMIN_EMAIL}\",\"password\":\"${ADMIN_PASSWORD}\"}" | jq -r .result.token)"
INVOICE_ID="$(curl -s http://127.0.0.1/api/invoice/list -H "Authorization: Bearer ${TOKEN}" | jq -r '.result[0]._id')"
curl -s -o /tmp/invoice.pdf -w 'invoice PDF: HTTP %{http_code}, %{size_download} bytes\n' \
  "http://127.0.0.1/download/invoice/invoice-${INVOICE_ID}.pdf"
file /tmp/invoice.pdf

Expected output:

invoice PDF: HTTP 200, 70693 bytes
/tmp/invoice.pdf: PDF document, version 1.4, 1 page(s)

The authenticated invoice list returned by the API, and an invoice rendered to a real PDF document by the PhantomJS engine

Step 9: Understand the security model

Three properties are worth verifying yourself.

cd /opt/idurar/backend && echo "administrator accounts: $(sudo node cloudimg-admin.js count)"
curl -s -o /dev/null -w 'upstream default credential -> HTTP %{http_code}\n' \
  -X POST http://127.0.0.1/api/login -H 'Content-Type: application/json' \
  -d '{"email":"admin@admin.com","password":"admin123"}'
curl -s -o /dev/null -w 'anonymous API request       -> HTTP %{http_code}\n' http://127.0.0.1/api/client/list

Expected output:

administrator accounts: 1
upstream default credential -> HTTP 404
anonymous API request       -> HTTP 401

Exactly one administrator account, the upstream default credential rejected with HTTP 404, anonymous API access refused with HTTP 401, and the listening sockets showing nginx on port 80 with the API and MongoDB on loopback only

  • One account, no default. The upstream project's setup routine creates admin@admin.com with the password admin123. That account is deleted during the build and the build fails if any account survives, so it does not exist in this image. Your VM's single administrator was created on first boot with a random password.
  • No self service sign up. IDURAR exposes no registration route at all, so a passer by cannot enrol on a public instance. New users are added by the administrator from the console.
  • The API and the database are private. The Express API listens on 127.0.0.1:8888 and MongoDB on 127.0.0.1:27017. Only nginx on port 80 is reachable from the network. Confirm with:
ss -tlnp 2>/dev/null | awk 'NR==1 || /:80 |:8888 |:27017 /' | sed 's/users:.*//'

One caveat to be aware of. Upstream mounts the PDF download route outside its authentication middleware, so anyone who knows an invoice's identifier can fetch that invoice's PDF without signing in. This is upstream behaviour rather than something this image introduces, but it means you should not expose port 80 to the public internet without the TLS and access restrictions in Step 10.

Step 10: Put your own domain and TLS in front

The image serves plain HTTP on port 80 so it works immediately at any address. For anything beyond a trial, terminate TLS with your own certificate and restrict access with a network security group.

Point a DNS A record at the VM's public IP, allow 443/tcp in the NSG, then install a certificate. Replace the placeholders with your own values:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> -m <your-email> --agree-tos --redirect

Because the admin console calls the API on the same origin using relative paths, no rebuild or reconfiguration is needed when you move to a hostname or to HTTPS. It simply works at the new address.

Step 11: Back up and restore

All business data lives in the idurar MongoDB database. Take a dump with the application credentials from the credentials file:

MONGO_APP_PASSWORD="$(sudo grep '^mongo.app.password=' /root/idurar-credentials.txt | cut -d= -f2-)"
sudo mkdir -p /var/backups/idurar
sudo mongodump --host 127.0.0.1 --port 27017 \
  -u idurar -p "${MONGO_APP_PASSWORD}" --authenticationDatabase idurar \
  --db idurar --out /var/backups/idurar/$(date -u +%Y%m%d)

Uploaded files (customer logos and attachments) live outside the database, so include them too:

sudo mkdir -p /var/backups/idurar
sudo tar czf /var/backups/idurar/uploads-$(date -u +%Y%m%d).tar.gz \
  -C /opt/idurar/backend/src/public uploads

To restore, use mongorestore with the same credentials and untar the uploads back into place.

Step 12: Connect a MongoDB client

MongoDB is deliberately not reachable from the network. To use a graphical client from your workstation, forward the port over SSH:

ssh -N -L 27017:127.0.0.1:27017 azureuser@<vm-ip>

Then connect to mongodb://127.0.0.1:27017 using the mongo.admin.user and mongo.admin.password values from the credentials file, with admin as the authentication database.

Differences from a stock install

This image is the unmodified upstream release with one deliberate change, recorded here so the image is auditable:

  • The API is bound to the loopback interface. Upstream calls app.listen(port) with no host argument, which binds every interface and leaves the API reachable on port 8888 from the network, bypassing nginx. The image adds a HOST environment override defaulting to 127.0.0.1. Setting HOST=0.0.0.0 restores the upstream behaviour.

Two further notes on what this image does not change:

  • The release is 4.1.0, not the newer 4.1.1 tag. Upstream's 4.1.1 release removed three data models that its own console still depends on, which silently removes the quotes, taxes and payment modes features from the API while the console continues to link to them. 4.1.0 is the newest release whose API and console agree, so that is what ships here.
  • The seeded configuration is upstream's own. The default tax rate and payment mode created by the upstream setup routine are preserved; only the seeded administrator account is removed.

Troubleshooting

The console loads but every action fails. Check the API is running and reachable behind nginx:

systemctl is-active idurar
curl -s -o /dev/null -w 'API through nginx: HTTP %{http_code}\n' http://127.0.0.1/api/client/list
sudo journalctl -u idurar.service -n 30 --no-pager

An anonymous request to that endpoint returning 401 is correct and healthy; it means the API is up and refusing unauthenticated access.

The application did not start after a rebuild or restore. idurar.service will not start until first boot initialisation has completed, which is deliberate: it prevents the application ever running with a placeholder secret. Confirm the marker exists:

ls -l /var/lib/cloudimg/idurar-bootstrap-ready /var/lib/cloudimg/idurar-firstboot.done
sudo systemctl status idurar-firstboot.service --no-pager | head -20

You lost the administrator password. There is no password reset without email configured, but you can mint a new administrator directly on the VM:

cd /opt/idurar/backend
sudo CLOUDIMG_ADMIN_EMAIL="you@example.com" CLOUDIMG_ADMIN_PASSWORD="a-long-random-password" \
  node cloudimg-admin.js create

This replaces every existing administrator with the one you specify and prints the resulting account count.

Support

24/7 technical support is included with this image. Email support@cloudimg.co.uk with your Azure subscription ID and the VM name, and we respond within 24 hours.