E-commerce Azure

Unchained Engine on Ubuntu 24.04 on Azure User Guide

| Product: Unchained Engine on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Unchained Engine on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Unchained Engine is an open source headless ecommerce engine. Rather than shipping a storefront you have to bend into shape, it gives you the commerce domain itself as a single GraphQL API: products and variants, assortments and filters, carts and orders, quotations, subscriptions and enrollments, pricing and discounts, delivery, payment and warehousing, plus users, roles and a background work queue. Your storefront, mobile app, point of sale terminal or kiosk is whatever you choose to build against that API.

Because the engine is headless, it also ships a full browser admin console for the people who run the shop day to day: creating and publishing products, organising assortments, working through orders and quotations, managing customers and roles, and importing or exporting catalogue data in bulk.

The cloudimg image ships the free and open source, EUPL-1.2 licensed Unchained Engine 4.8.25, with the engine, its MongoDB datastore, the prebuilt admin console 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, which matters more than usual with this product: every upstream example application commits a published administrator password and published signing secrets to a public repository. This image ships neither. A single administrator with a random 24 character password, a fresh session signing secret and fresh MongoDB passwords are generated on the first boot of each VM, before the engine is allowed to start. Backed by 24/7 cloudimg support.

Unchained is a trademark of its respective owner. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by Unchained Commerce. It ships the free and open source EUPL-1.2 licensed self hosted software, with the deployment specific choices described under "Differences from a stock install" below.

The mongod, nginx, unchained and unchained-firstboot services all reporting active, with Node.js 22, MongoDB 8.0 and nginx versions, the installed Unchained Engine release and its EUPL-1.2 licence, and HTTP 200 from both the admin console and the GraphQL endpoint

What is included:

  • Unchained Engine 4.8.25 (EUPL-1.2), installed from the published npm packages at exact pinned versions
  • The Unchained admin console, served at / and calling the API same origin, so it works at any address with no reconfiguration
  • The full GraphQL commerce API at /graphql
  • Node.js 22 LTS running the engine as unchained.service, bound to the loopback interface only
  • MongoDB 8.0 Community with SCRAM authentication, bound to the loopback interface only
  • nginx on port 80 as the single front door
  • The base plugin preset: postal shipping, invoice payment, store warehousing, the full pricing chain, quotations, enrollments, GridFS file storage and the whole worker set
  • unchained-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

A note on the screenshots below. The admin console screenshots in this guide were taken with a small demonstration catalogue and two example orders, so you can see what a working shop looks like. Your VM starts with an empty catalogue and no orders. The demonstration data is destroyed before the image is captured; the base shop configuration (language, currency, country, a shipping provider and an invoice payment provider) is what your VM creates for itself on first boot.

Prerequisites

  • Active Azure subscription, SSH public key, VNet and subnet in target region
  • Subscription to the Unchained Engine listing on Azure Marketplace

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point for evaluation, development and a small production shop. For a large catalogue or heavy order volume, 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 and the API. Port 443/tcp is used once you attach your own certificate, as shown in Step 9.

Step 1: Deploy from the Azure Portal

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Unchained Engine 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="unchained-prod"; LOCATION="eastus"; VM_NAME="unchained-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/unchained-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 unchained-vnet --address-prefix 10.100.0.0/16 --subnet-name unchained-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name unchained-nsg
az network nsg rule create -g "$RG" --nsg-name unchained-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 unchained-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 unchained-vnet --subnet unchained-subnet --nsg unchained-nsg --public-ip-sku Standard

Step 3: Confirm the services are running

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

systemctl is-active mongod nginx unchained unchained-firstboot

Expected output:

active
active
active
active

Check the engine release that is actually installed, its licence, and that both the console and the API answer:

node -p "'Unchained Engine ' + require('/opt/unchained/node_modules/@unchainedshop/platform/package.json').version + '  (licence ' + require('/opt/unchained/node_modules/@unchainedshop/platform/package.json').license + ')'"
node --version
mongod --version | head -1
curl -s -o /dev/null -w 'Unchained admin console: HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'Unchained GraphQL API:   HTTP %{http_code}\n' -X POST http://127.0.0.1/graphql \
  -H 'content-type: application/json' --data '{"query":"{shopInfo{version}}"}'

Expected output:

Unchained Engine 4.8.25  (licence EUPL-1.2)
v22.23.2
db version v8.0.31
Unchained admin console: HTTP 200
Unchained GraphQL API:   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 stat -c '%n  mode=%a  owner=%U:%G' /root/unchained-credentials.txt
sudo grep -o '^[a-z.]*=' /root/unchained-credentials.txt | tr -d '='

Expected output:

/root/unchained-credentials.txt  mode=600  owner=root:root
unchained.url
unchained.graphql
unchained.admin.username
unchained.admin.email
unchained.admin.password
mongo.admin.username
mongo.admin.password
mongo.app.username
mongo.app.password
mongo.database

The per VM credentials file proven to be mode 600 owned by root, the names of the keys it holds with no values printed, and the first boot sentinel and bootstrap ready marker both present

Read the values when you need them, with sudo cat /root/unchained-credentials.txt. Copy the administrator username and password into your password manager now, then continue.

Step 5: Sign in to the admin console

Browse to http://<your-vm-public-ip>/. Signing in takes two steps: enter the unchained.admin.username value, choose Continue, then enter the unchained.admin.password value.

The Unchained admin console sign in page, with the per VM administrator username entered and no default credential shown anywhere on the form

You can prove the same credential works 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:

JAR="$(mktemp)"
ADMIN_USER="$(sudo grep '^unchained.admin.username=' /root/unchained-credentials.txt | cut -d= -f2-)"
ADMIN_PASS="$(sudo grep '^unchained.admin.password=' /root/unchained-credentials.txt | cut -d= -f2-)"
curl -s -c "$JAR" -X POST http://127.0.0.1/graphql -H 'content-type: application/json' \
  --data "{\"query\":\"mutation{loginWithPassword(username:\\\"${ADMIN_USER}\\\",password:\\\"${ADMIN_PASS}\\\"){_id}}\"}" > /dev/null
curl -s -b "$JAR" -X POST http://127.0.0.1/graphql -H 'content-type: application/json' \
  --data '{"query":"{me{username roles}}"}'
echo
rm -f "$JAR"

Expected output:

{"data":{"me":{"username":"admin","roles":["admin"]}}}

Once signed in you land on the dashboard, which confirms the shop is configured and summarises the catalogue, customers, sales and orders.

The Unchained admin console dashboard reporting the shop setup as complete, with product, assortment and customer counts and the total sales and total orders charts populated from real orders

Step 6: Create your first product

Your VM starts with an empty catalogue. Add a product from Products in the console sidebar, choose Add Product, pick a product type and give it a title, then set a price under the product's Commerce tab and publish it.

The Unchained admin console products list showing a catalogue of published products with their type, status and tags

The same thing over the API. This signs in, creates a product with an English title and a price, publishes it, reads it back, and then removes it again so your catalogue is left exactly as it was:

JAR="$(mktemp)"
ADMIN_USER="$(sudo grep '^unchained.admin.username=' /root/unchained-credentials.txt | cut -d= -f2-)"
ADMIN_PASS="$(sudo grep '^unchained.admin.password=' /root/unchained-credentials.txt | cut -d= -f2-)"
gql() { curl -s -c "$JAR" -b "$JAR" -X POST http://127.0.0.1/graphql -H 'content-type: application/json' --data "$1"; }

gql "{\"query\":\"mutation{loginWithPassword(username:\\\"${ADMIN_USER}\\\",password:\\\"${ADMIN_PASS}\\\"){_id}}\"}" > /dev/null

PRODUCT_ID="$(gql '{"query":"mutation($p:CreateProductInput!,$t:[ProductTextInput!]){createProduct(product:$p,texts:$t){_id}}","variables":{"p":{"type":"SIMPLE_PRODUCT","tags":["guide-example"]},"t":[{"locale":"en","title":"Guide example product","slug":"guide-example-product"}]}}' | sed -n 's/.*"_id":"\([^"]*\)".*/\1/p')"

gql "{\"query\":\"mutation{updateProductCommerce(productId:\\\"${PRODUCT_ID}\\\",commerce:{pricing:[{amount:2500,currencyCode:\\\"USD\\\",countryCode:\\\"US\\\",isTaxable:true,isNetPrice:false}]}){_id}}\"}" > /dev/null
gql "{\"query\":\"mutation{publishProduct(productId:\\\"${PRODUCT_ID}\\\"){_id}}\"}" > /dev/null

gql "{\"query\":\"{product(productId:\\\"${PRODUCT_ID}\\\"){status texts{title} ... on SimpleProduct{simulatedPrice{amount currencyCode}}}}\"}"
echo

gql "{\"query\":\"mutation{removeProduct(productId:\\\"${PRODUCT_ID}\\\"){_id}}\"}" > /dev/null
rm -f "$JAR"

Expected output:

{"data":{"product":{"status":"ACTIVE","texts":{"title":"Guide example product"},"simulatedPrice":{"amount":2500,"currencyCode":"USD"}}}}

Product is a GraphQL interface, so the price lives on the concrete type: the inline fragment ... on SimpleProduct is what reaches it. Prices are held in minor units, so 2500 is $25.00.

Step 7: Orders

Orders placed by your storefront, mobile app or any other client appear under Orders, with the order number, date, computed total, customer and status. From an order you can inspect its items, delivery and payment, and move it through the fulfilment lifecycle.

The Unchained admin console orders list showing confirmed orders with their order number, order date, computed total, customer and status

Everything the console does here is available over the API, which is how your own storefront drives it. A cart is created by adding a product, then given a contact and billing address and a delivery and payment provider, then checked out:

JAR="$(mktemp)"
ADMIN_USER="$(sudo grep '^unchained.admin.username=' /root/unchained-credentials.txt | cut -d= -f2-)"
ADMIN_PASS="$(sudo grep '^unchained.admin.password=' /root/unchained-credentials.txt | cut -d= -f2-)"
gql() { curl -s -c "$JAR" -b "$JAR" -X POST http://127.0.0.1/graphql -H 'content-type: application/json' --data "$1"; }

gql "{\"query\":\"mutation{loginWithPassword(username:\\\"${ADMIN_USER}\\\",password:\\\"${ADMIN_PASS}\\\"){_id}}\"}" > /dev/null
gql '{"query":"{deliveryProviders{_id type}paymentProviders{_id type}}"}'
echo
rm -f "$JAR"

Expected output (your identifiers will differ):

{"data":{"deliveryProviders":[{"_id":"027340fe3023d629d1bda2d1","type":"SHIPPING"}],"paymentProviders":[{"_id":"bc8eb70f383b2db30274fc05","type":"INVOICE"}]}}

Those two providers are created for you on first boot, so the engine can price and accept an order out of the box. The full cart and checkout flow is addCartProduct, then updateCart with a contact, billing address and those two provider identifiers, then checkoutCart.

The engine's catalogue and the orders it has priced and confirmed, read back through the authenticated GraphQL API

Step 8: Understand the security model

Four properties are worth verifying yourself.

JAR="$(mktemp)"
ADMIN_USER="$(sudo grep '^unchained.admin.username=' /root/unchained-credentials.txt | cut -d= -f2-)"
ADMIN_PASS="$(sudo grep '^unchained.admin.password=' /root/unchained-credentials.txt | cut -d= -f2-)"
curl -s -c "$JAR" -X POST http://127.0.0.1/graphql -H 'content-type: application/json' \
  --data "{\"query\":\"mutation{loginWithPassword(username:\\\"${ADMIN_USER}\\\",password:\\\"${ADMIN_PASS}\\\"){_id}}\"}" > /dev/null
curl -s -b "$JAR" -X POST http://127.0.0.1/graphql -H 'content-type: application/json' \
  --data '{"query":"{usersCount}"}' | sed 's/.*"usersCount":\([0-9]*\).*/accounts on this VM: \1/'
rm -f "$JAR"

for P in password admin; do
  printf 'upstream default admin/%s -> ' "$P"
  curl -s -X POST http://127.0.0.1/graphql -H 'content-type: application/json' \
    --data "{\"query\":\"mutation{loginWithPassword(username:\\\"admin\\\",password:\\\"${P}\\\"){_id}}\"}" \
    | grep -qo '"_id"' && echo 'ACCEPTED' || echo 'refused'
done

curl -s -X POST http://127.0.0.1/graphql -H 'content-type: application/json' \
  --data '{"query":"mutation{createProduct(product:{type:SIMPLE_PRODUCT}){_id}}"}' \
  | python3 -c "import json,sys; print('anonymous createProduct -> ' + (json.load(sys.stdin).get('errors') or [{}])[0].get('message','ALLOWED'))"

ss -tlnH | awk '{print $4}' | grep -E ':(80|4010|27017)$' | sort -u | sed 's/^/listening on /'

Expected output:

accounts on this VM: 1
upstream default admin/password -> refused
upstream default admin/admin -> refused
anonymous createProduct -> The user "anonymous" has no permission to perform the action "manageProducts" in "createProduct"
listening on 0.0.0.0:80
listening on 127.0.0.1:27017
listening on 127.0.0.1:4010
listening on [::]:80

Exactly one account on the VM, both upstream published default credentials refused, an anonymous product creation refused by the engine's access control, and the listening sockets showing nginx on port 80 with the engine and MongoDB on loopback only

  • One account, no default. Every upstream example application creates admin with the password password and commits the signing secrets alongside it. Those values do not exist in this image; the build fails closed if any of them ever reaches the shipped configuration. Your VM's single administrator was created on first boot with a random password.
  • The engine and the database are private. The engine listens on 127.0.0.1:4010 and MongoDB on 127.0.0.1:27017. Only nginx on port 80 is reachable from the network.
  • Administrative operations are role gated. The commerce API is deliberately reachable, because that is what a headless engine is for and it is how your storefront talks to it. Everything administrative is refused to anyone without the role for it, as the anonymous createProduct above shows.
  • Customer sign up is open by design. The console's sign in page offers a Sign up link, and the API exposes account creation, because a shop needs customers to be able to register. New accounts get no administrative rights whatsoever. If you do not want public registration, restrict access to port 80 with a network security group as described in Step 9, or put your own storefront in front of the API and expose only that.

Step 9: 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.

Once you are serving over HTTPS, tighten the session cookie. The image ships a cookie that a browser will accept over plain HTTP, which necessarily means it is not marked Secure. Edit /opt/unchained/.env, remove the UNCHAINED_COOKIE_INSECURE=1 line, and restart the engine:

sudo sed -i '/^UNCHAINED_COOKIE_INSECURE=1$/d' /opt/unchained/.env
sudo systemctl restart unchained

Also update ROOT_URL and EMAIL_WEBSITE_URL in the same file to your https:// address so any absolute links the engine generates point at your domain.

Step 10: Send email

The engine queues order confirmations, password resets and account messages onto its work queue and hands them to an SMTP server. No SMTP server is configured out of the box, so those work items fail until you set one. Add your SMTP URL to /opt/unchained/.env and restart:

sudo sed -i 's|^MAIL_URL=.*|MAIL_URL=smtps://user:pass@smtp.example.com:465|' /opt/unchained/.env
sudo systemctl restart unchained

Until you do, the console's dashboard may show a Problems detected in the Work Queue banner counting failed EMAIL items. That is the expected state of an engine with no mail server attached, not a fault with the image. Review or retry the items under Work queue in the console sidebar.

Step 11: Back up and restore

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

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

Uploaded media is stored inside MongoDB by the GridFS file adapter, so the dump above covers it. To restore, use mongorestore with the same credentials.

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.username and mongo.admin.password values from the credentials file, with admin as the authentication database.

Server components

Component Version Purpose
Unchained Engine 4.8.25 Headless commerce engine and GraphQL API
Unchained admin console 4.8.25 Browser application for running the shop
Node.js 22 LTS Runtime for the engine
MongoDB Community 8.0 Datastore, loopback only, SCRAM authentication
nginx 1.24 Front door on port 80, reverse proxy to the engine
Ubuntu Server 24.04 LTS Operating system

Filesystem layout

Path Purpose
/opt/unchained Engine host application, its npm packages and .env
/opt/unchained/.env Runtime configuration and per VM secrets, mode 0640
/opt/unchained/LICENSE The upstream EUPL-1.2 licence text
/var/lib/mongodb MongoDB data directory
/var/log/unchained Engine stdout and stderr
/root/unchained-credentials.txt Per VM credentials, mode 0600 root only
/var/lib/cloudimg First boot sentinel and bootstrap ready marker

Scripts and log files

Path Purpose
/usr/local/sbin/unchained-firstboot.sh Generates every per VM secret and the single administrator
/usr/local/sbin/unchained-roundtrip.sh Proves the per VM credential authenticates end to end
/var/log/cloudimg-firstboot.log First boot log
/var/log/unchained/stdout.log Engine standard output
/var/log/unchained/stderr.log Engine standard error
/var/log/mongodb/mongod.log MongoDB log

On startup

unchained-firstboot.service runs once, on the very first boot of each VM. It resolves the VM's public address, generates the MongoDB administrator and application passwords, the session signing secret, the magic key secret and the GridFS upload secret, writes them to /opt/unchained/.env, creates exactly one administrator with a random 24 character password, writes everything to /root/unchained-credentials.txt with mode 0600, then releases the bootstrap gate and starts the engine.

unchained.service carries ConditionPathExists=/var/lib/cloudimg/unchained-bootstrap-ready. That marker is absent in the shipped image and is created by first boot only after the secrets are in place, so the engine can never start against the blank configuration the image ships with. Once first boot has completed, the unit starts normally on every subsequent reboot.

Differences from a stock install

The engine itself is the unmodified upstream release, installed from the published npm packages at exact pinned versions. Unchained is a framework rather than a server binary, so every deployment supplies its own small host application; this image's is at /opt/unchained/boot.js, derived from upstream's own minimal example. The deployment specific choices, recorded here so the image is auditable:

  • The engine binds to the loopback interface. Upstream's example listens on all interfaces; here nginx on port 80 is the single front door.
  • Session cookies are SameSite=Lax and not marked Secure. Upstream defaults to SameSite=None, which browsers only honour on a Secure cookie, and a Secure cookie is discarded over plain HTTP. On an appliance first reached at http://<ip>/ that combination makes signing in impossible in a browser. Step 9 shows how to restore the stricter setting once you have TLS.
  • The base shop configuration is created on first boot (English, US dollars, United States, a postal shipping provider and an invoice payment provider) so the engine can price and accept an order immediately.
  • Exactly one administrator is created on first boot, from a secret handed to the engine through a file on tmpfs that the engine deletes as soon as it has read it. The password is never written to the environment file.
  • The base plugin preset is enabled, not all. The all preset additionally loads the Stripe, PayPal, Braintree, Datatrans, Payrexx and cryptopay adapters, each of which needs vendor API credentials that a marketplace image cannot hold. Add whichever you use to /opt/unchained/package.json and import it in /opt/unchained/boot.js.

Troubleshooting

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

systemctl is-active unchained
curl -s -o /dev/null -w 'GraphQL through nginx: HTTP %{http_code}\n' -X POST http://127.0.0.1/graphql \
  -H 'content-type: application/json' --data '{"query":"{shopInfo{version}}"}'
sudo tail -n 30 /var/log/unchained/stderr.log

The engine did not start after a rebuild or restore. unchained.service will not start until first boot initialisation has completed, which is deliberate: it prevents the engine ever running with a blank signing secret. Confirm the markers exist:

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

Signing in fails in the browser but works with curl. That is the session cookie being rejected by the browser. Check the cookie the engine issues:

curl -s -D - -o /dev/null -X POST http://127.0.0.1/graphql -H 'content-type: application/json' \
  --data '{"query":"mutation{loginWithPassword(username:\"nobody\",password:\"nobody\"){_id}}'"'"'}' \
  | grep -i '^set-cookie:' || echo 'no cookie issued for a failed login, which is correct'

A working cookie is HttpOnly; SameSite=Lax with no Secure flag while you are on plain HTTP. If you have added TLS, follow Step 9 so the cookie is marked Secure too.

You lost the administrator password. Reset it directly against the engine's datastore is not supported, but you can mint a replacement administrator by clearing the first boot state and letting first boot run again. This destroys the current administrator account, so only do it if you have lost access:

echo "This would re-run first boot and mint a new administrator."
echo "Contact support@cloudimg.co.uk before running it on a shop with live data."

Security recommendations

  • Restrict inbound 22/tcp to your management network and 80/tcp (or 443/tcp) to the networks that need the console and API, using an Azure network security group.
  • Terminate TLS with your own certificate and follow Step 9 to mark the session cookie Secure.
  • Change the generated administrator password from the console after your first sign in, and create individual named accounts for the people who need them rather than sharing one.
  • Keep the VM patched. Unattended security upgrades are enabled on the image.
  • Take regular backups of the unchained database as shown in Step 11.

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.