Deployment guide

Payload CMS

Overview

Payload is a modern, open source headless CMS and application framework built on Next.js and React. It is code-first: you define your collections, fields and access control in TypeScript, and Payload generates a fully customisable admin panel together with a REST and GraphQL API.

This SecureAnyCloud image installs Payload CMS 3.85.1 on Node.js 22 LTS and builds the Next.js production app. It runs the app as a systemd service behind an nginx reverse proxy on port 80, and it generates the application secret and a unique admin user on the first boot of every VM.

What you get:

  • Payload CMS 3.85.1 on Node.js 22 LTS with the Next.js admin panel pre-built
  • The admin panel (/admin) and the REST and GraphQL content API, published on port 80 through nginx
  • A dedicated Azure data disk at /var/lib/payload for the SQLite database and uploaded media
  • A per-VM PAYLOAD_SECRET and a unique admin user, generated on first boot
  • payload.service and nginx.service as systemd units, enabled and active
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.

Standard_B2ms (2 vCPU / 8 GiB RAM) is a good starting size.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 80/tcp for the admin panel and API. For public exposure, put TLS in front of port 80 (see Enabling HTTPS).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Payload CMS by SecureAnyCloud, and select Create.

On the Basics tab, 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).

Review the dedicated data disk on the Disks tab, then select Review + create and Create.

Step 2 - Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:payload-cms-ubuntu-24-04:default:latest
az vm create \
  --resource-group <your-rg> \
  --name payload \
  --image sac1647283583153:payload-cms-ubuntu-24-04:default:latest \
  --size Standard_B2ms \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name payload --port 80 --priority 1010

Step 3 - Connect to your VM

Connect over SSH to your VM:

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

Check that the Payload and nginx services are active:

systemctl is-active payload.service nginx.service

Both services report active. On first boot Payload generates its secret, creates the admin user and builds a clean database.

Step 5 - Retrieve your admin credentials

Your VM generates a unique admin password on first boot and writes it to a root-only file. Read the file as root:

sudo cat /root/payload-credentials.txt

The file contains payload.admin.email (admin@sac.local) and payload.admin.pass. Store the password somewhere safe.

Step 6 - Check the admin panel responds

The Payload admin panel is served on port 80. A healthy panel returns 200:

curl -s -o /dev/null -w '%{http_code}\n' http://localhost/admin

Step 7 - Open the admin panel

Browse to http://<vm-public-ip>/admin and sign in with admin@sac.local and the password from Step 5.

The admin dashboard gives you an overview of your collections, globals and account.

Payload generates the admin UI from the collections you define in code. The blank image ships the Users and Media collections. Add your own in /opt/payload/src/collections.

The systemd services and version are shown below.

Step 8 - Authenticate against the API

Payload issues an auth token from /api/users/login. Confirm that the generated credentials work:

curl -s -H 'Content-Type: application/json' --data '{"email":"admin@sac.local","password":"<PAYLOAD_ADMIN_PASSWORD>"}' http://localhost/api/users/login | head -c 120; echo

A successful login returns a JSON object that contains a "token" and the authenticated user.

Step 9 - Use the content API

Every collection you define is served over REST and GraphQL on the same port 80. The following example queries the built-in users and media collections:

# REST - list documents in a collection (auth required for protected collections)
curl http://<vm-public-ip>/api/media
# GraphQL endpoint
curl -X POST http://<vm-public-ip>/api/graphql \
  -H 'Content-Type: application/json' \
  --data '{"query":"{ Users { totalDocs } }"}'

When you add collections and fields in your TypeScript config, Payload regenerates the admin UI and the API automatically.

Step 10 - Confirm content lives on the dedicated disk

The SQLite database and uploaded media are stored on the dedicated Azure data disk, so they survive OS changes and can be resized independently. Confirm the mount:

findmnt /var/lib/payload

The mount is backed by a separate Azure data disk that is captured into the image and re-provisioned on every VM. The database is /var/lib/payload/payload.db, and uploads are written under /var/lib/payload/uploads.

Enabling HTTPS

The nginx reverse proxy serves plain HTTP on port 80. For public exposure, put a certificate in front of it. Add a DNS name for the VM and install certbot, or use the companion SecureAnyCloud nginx-ssl-certbot image as a TLS reverse proxy.

Keep Payload bound to loopback (127.0.0.1:3000) so that the TLS-terminated proxy is the only public surface. Set serverURL in /opt/payload/src/payload.config.ts to your HTTPS address and restart Payload.

Maintenance

  • Backups. Snapshot the /var/lib/payload data disk to back up the database and media.
  • Database. The image uses SQLite, which suits a single server. For production scale, switch to PostgreSQL by changing the adapter in /opt/payload/src/payload.config.ts and the DATABASE_URL in /opt/payload/.env.
  • Service. Run sudo systemctl restart payload after configuration changes, and read the logs with journalctl -u payload. After you edit collections, rebuild with sudo -u payload npm run build in /opt/payload, then restart.
  • Security patches. unattended-upgrades stays enabled, so the OS continues to receive security updates automatically.

Support

SecureAnyCloud provides 24/7 expert support for this image. Contact sales@secureanycloud.com for help with your deployment.

Deploy on Azure

Launch Payload CMS on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure