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/payloadfor the SQLite database and uploaded media - A per-VM
PAYLOAD_SECRETand a unique admin user, generated on first boot payload.serviceandnginx.serviceas 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 1010Step 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.serviceBoth 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.txtThe 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/adminStep 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; echoA 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/payloadThe 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/payloaddata 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.tsand theDATABASE_URLin/opt/payload/.env. - Service. Run
sudo systemctl restart payloadafter configuration changes, and read the logs withjournalctl -u payload. After you edit collections, rebuild withsudo -u payload npm run buildin/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
Overview
Payload is an open source, code-first, TypeScript headless content management system built natively on Next.js. Teams define content models in code, manage entries through a polished admin panel, and deliver everything over a fully typed REST and GraphQL API.
This SecureAnyCloud image runs Payload CMS under systemd, with the production Next.js bundle pre-built, so a production-grade content platform is running within minutes of launch.
What you get:
- The Payload application installed under
/opt/payload. It runs as a dedicated unprivilegedpayloadsystem account under a systemd service that starts it on boot and restarts it on failure. - A content store at
/var/lib/payloadthat holds the SQLite database (payload.db) and your uploaded media, on a dedicated, independently resizable EBS data volume - Payload bound to the loopback interface only (
127.0.0.1:3000) and never exposed directly, with an nginx reverse proxy that publishes the admin panel and the content API on port 80 - Payload's own admin authentication at
/admin. The nginx proxy is a plain pass-through and adds no second auth layer. - An admin password generated on the first boot of every instance, so two instances launched from the same Amazon Machine Image never share a password. It is written to
/root/payload-cms-credentials.txtwith mode 0600, so only root can read it.
Prerequisites
Before you deploy this image, make sure you have:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network and port 80 for the admin panel and the content API
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line
Launch the instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for Payload CMS. Select the SecureAnyCloud listing, choose Select, then choose Continue on the subscription summary.
Pick an instance type of t3.large or larger. Choose your EC2 key pair under Key pair (login).
Under Network settings, select your VPC and subnet. Create or select a security group that opens port 22 from your management network and port 80 for the admin panel and the API. Leave the root volume at the default size or larger.
Select Launch instance. First boot initialisation takes a short time after the instance state becomes Running and the status checks pass. It generates the application secret and the admin user, then starts the service.
Launch the instance from the AWS CLI
The following command launches an instance from the SecureAnyCloud Payload CMS Marketplace AMI into an existing subnet and security group. Replace the values as follows:
- <ami-id>: the AMI ID shown on the Marketplace listing
- <key-name>: your EC2 key pair name
- <subnet-id>: your subnet ID
- <security-group-id>: a security group that opens ports 22 and 80 as described above
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type t3.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=payload-cms}]'Wait for the instance to reach the Running state and pass its status checks. Then note its public IP address or DNS name from the EC2 console or with aws ec2 describe-instances.
Connect to your instance
Connect over SSH using your key pair and the login user for your operating system variant. For Ubuntu 24.04, the login user is ubuntu.
ssh -i <key-name>.pem ubuntu@<public-ip>Retrieve the admin password
The admin password is unique to your instance and was generated on first boot. Read it as root:
sudo cat /root/payload-cms-credentials.txtThe file lists the admin panel URL, the admin email (admin@sac.local) and the generated password. Store the password somewhere safe.
Sign in to the admin panel
nginx serves the Payload admin panel on port 80 at the /admin path. In a browser, go to:
http://<instance-public-ip>/adminSign in with the email admin@sac.local and the password from the credentials file. You land on the dashboard, which lists the collections defined in your Payload config.
Open a collection from the sidebar or the dashboard to see its list view, where you browse, search, filter and manage entries. Every collection you define in code automatically gets a list view, an editor, access control and a typed API.
Select an entry to open the field-based document editor, where you edit the fields defined for that collection. The same editor powers your own collections once you add them to the Payload config.
Confirm Payload is running
Over SSH, confirm that the Payload service and the nginx proxy are active and that the ports are listening:
sudo systemctl is-active payload nginx
sudo ss -tlnp | grep -E ':(80|3000) 'Both services report active. Payload listens on 127.0.0.1:3000 (loopback only), and nginx listens on port 80.
Use the content API
Payload exposes a fully typed REST and GraphQL content API on the same port 80, under the /api path. Obtain a session token by posting your admin credentials to the login endpoint. From the instance, call the API over loopback:
PASS=$(sudo grep '^payload.admin.pass=' /root/payload-cms-credentials.txt | cut -d= -f2-)
curl -s -H 'Content-Type: application/json' \
--data "{\"email\":\"admin@sac.local\",\"password\":\"${PASS}\"}" \
http://127.0.0.1/api/users/login | head -c 400; echoThe response contains a session token and the authenticated user. From outside the instance, send the same request to http://<instance-public-ip>/api/users/login.
List the documents in a collection with a GET to /api/<collection-slug>, for example the users collection:
curl -s http://127.0.0.1/api/users?limit=1 | head -c 400; echoThe GraphQL endpoint is at /api/graphql, and the GraphQL Playground is at /api/graphql-playground. Generate API keys and manage access control from the admin panel.
Define your own collections
Payload is code-first. You define collections and globals in TypeScript in /opt/payload/src, and Payload generates the admin panel, the typed API and the database schema for you.
The configuration entry point is /opt/payload/src/payload.config.ts, and the collection definitions live under /opt/payload/src/collections. View the current config:
sudo sed -n '1,40p' /opt/payload/src/payload.config.tsAfter you change the config or add a collection, generate a new database migration and rebuild the production bundle as the payload user, then restart the service. SQLite requires migrations to apply schema changes in production:
cd /opt/payload
sudo -u payload env HOME=/opt/payload NODE_ENV=production npx payload migrate:create my_change
sudo -u payload env HOME=/opt/payload NODE_ENV=production npx payload migrate
sudo -u payload env HOME=/opt/payload NODE_ENV=production npm run build
sudo systemctl restart payloadSee the Payload documentation at https://payloadcms.com/docs for the full configuration, field type and access control reference.
Check the data volume
The content store lives on a dedicated EBS volume mounted at /var/lib/payload. This keeps the SQLite database and your uploaded media off the operating system disk and lets you resize or snapshot the volume independently. Confirm the mount and the database location:
df -h /var/lib/payload
sudo ls -la /var/lib/payloadThe listing shows payload.db and the media directory on the mounted volume. To grow the content store, expand the EBS volume in the AWS console. Then grow the filesystem on the instance with sudo resize2fs on the underlying device.
Enable HTTPS
nginx serves the admin panel and API over plain HTTP on port 80. For production use, place them behind TLS.
Obtain a certificate for your domain, for example a managed certificate on an Application Load Balancer in front of the instance, or Certbot installed on the instance. Then configure nginx to listen on 443 with your certificate and proxy to 127.0.0.1:3000, exactly as the bundled site does for port 80.
Restrict the security group so that ports 80 and 443 are reachable only from the networks that need the admin panel and the API. When you serve Payload over a public domain, set the serverURL in your Payload config to the HTTPS URL and rebuild.
Back up and maintain the instance
Back up the content store by snapshotting the /var/lib/payload EBS volume. The snapshot captures the SQLite database and all uploaded media. For an application-level backup, you can also copy the database file off the instance, for example sudo cp /var/lib/payload/payload.db /path/to/backup/payload.db.
Apply operating system security updates with sudo apt-get update && sudo apt-get upgrade, and reboot when a new kernel is installed. Payload and nginx start automatically on boot.
Support
SecureAnyCloud publishes and supports this image. Support covers deployment, content modelling, the REST and GraphQL API, access control, media and upload configuration, database and storage, TLS and runtime tuning. Contact SecureAnyCloud through the support channel listed on the AWS Marketplace listing.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on AWS
Launch Payload CMS on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com