Contember
Overview
Contember is an open source (Apache-2.0), self-hosted headless CMS and content-engineering platform. You model content as TypeScript entities, Contember generates a GraphQL content API from that schema, and a React-based Admin/Studio gives editors an interface to manage the content. It suits structured content, editorial workflows and content-as-a-service back ends for websites and apps.
This guide covers deploying and configuring Contember 2.0.8 on Ubuntu 24.04 with the SecureAnyCloud image from the Azure Marketplace. The image runs the official upstream Contember containers, pinned by tag, under a single systemd unit via Docker Compose on a private network:
- Engine:
contember/engine:2.0.8, the Node GraphQL server exposing the/tenant,/systemand/contentAPIs. It listens on the internal Docker network only and is published to127.0.0.1:1481on the host (loopback), never to the public interface. - PostgreSQL:
postgres:16-alpine, the content datastore, on the private network only (no host port). - MinIO:
minio/minio, S3-compatible object storage for media uploads, on the private network only (no host port). - CLI:
contember/cli:2.0.8, run on demand to apply schema migrations at first boot.
The Admin/Studio is built to static assets at image-build time. It is served directly by host nginx on port 80, which also reverse-proxies the engine's GraphQL endpoints. The whole appliance is therefore same-origin and needs no baked-in IP address.
Security model. No privileged credential ships in the image. On first boot, a one-shot service generates a set of secrets unique to your VM:
- the privileged content-API root token
- the Studio superadmin password
- the engine encryption key
- the PostgreSQL and MinIO credentials
The service writes them with mode 0600 to a root-only env file, and only then starts the engine and datastores.
The engine's public login token is a non-secret front-end token. It authorises the login endpoint only, and signing in still requires the per-VM superadmin password. The upstream demo root token (forty zeros) is never present and is rejected.
After the stack is up, a post-boot service applies the content-model migration, seeds a couple of demo articles, and verifies that the superadmin can sign in.
What you get:
- Contember Engine 2.0.8, CLI 2.0.8, PostgreSQL 16 and MinIO as pinned containers under
contember.service(Docker Compose) - The Admin/Studio built to static assets at
/opt/contember/admin-dist, served by nginx on port 80 (single public surface) contember-firstboot.service, which generates the per-VM root token, superadmin password, encryption key and datastore secrets before the stack startscontember-postboot.service, which creates the MinIO bucket, applies the content migration, seeds demo content, and verifies the superadmin sign-in- A demo
Articlecontent type (title, content, published date), migrated and seeded, plus an Articles management page in the Studio, so a fresh instance shows real content - A credential round-trip prover at
/usr/local/sbin/contember-roundtrip.sh - Ubuntu 24.04 LTS base with the latest security patches applied at build time
- 24/7 SecureAnyCloud support with a guaranteed 24 hour response SLA
Prerequisites
Before you deploy, you need:
- An active Azure subscription
- A subscription to the Contember 2 on Ubuntu 24.04 listing on Azure Marketplace
- An SSH public key for VM authentication
- A virtual network and subnet in the target region
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM). The full stack (engine, PostgreSQL and MinIO) uses roughly 150 MB of container memory at idle and fits comfortably on 4 GB.
Scale up for larger content sets, heavier editorial concurrency or media-heavy workloads.
Deploy from the Azure Portal
Open Marketplace in the Azure Portal, search for Contember, select the SecureAnyCloud publisher entry, and click Create.
On the Networking tab, attach a network security group that allows inbound TCP 22 from your management IP range and TCP 80 from the client networks that will use the Studio. Add TLS (port 443) before managing content over the public internet. See Step 9.
Click Review + create, wait for validation, then click Create. Deployment takes around two minutes.
Deploy from the Azure CLI
RG="contember-prod" LOCATION="eastus" az group create --name "$RG" --location "$LOCATION"
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:contember-ubuntu-24-04:default:latest
az vm create \
--resource-group "$RG" \
--name contember-01 \
--image sac1647283583153:contember-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
# Allow SSH (22) and the Studio (80) from your trusted ranges az vm open-port --resource-group "$RG" --name contember-01 --port 22 --priority 1001 az vm open-port --resource-group "$RG" --name contember-01 --port 80 --priority 1002Replace <publisher>:<offer>:<sku> with the URN shown on the Marketplace listing's Usage Information tab.
Connect via SSH
ssh azureuser@<vm-public-ip>Use the private key that matches the public key you supplied at deploy time. The login user for this image is azureuser.
Verify the Contember Services
Confirm that the three containers are healthy, the systemd units are active and the listening sockets are as expected. The engine is published to 127.0.0.1:1481 (loopback only), and nginx on port 80 is the single customer-facing surface.
sudo docker compose --env-file /etc/contember/contember.env -f /etc/contember/compose.yaml ps systemctl is-active docker contember nginx
sudo ss -ltn | grep -E ':80 |127.0.0.1:1481 'Expected output:
SERVICE IMAGE STATUS engine contember/engine:2.0.8 Up (healthy) minio minio/minio:RELEASE.2025-09-07T16-13-09Z Up (healthy) postgres postgres:16-alpine Up (healthy) active active active LISTEN 0 511 0.0.0.0:80 0.0.0.0:* LISTEN 0 4096 127.0.0.1:1481 0.0.0.0:*Confirm the Datastores Are Not Exposed
PostgreSQL and MinIO run on the private Docker network only. They are never published to a host port. The engine is reachable on loopback but not from outside the VM. Confirm both points:
echo "engine loopback only:";
sudo ss -ltn | grep 1481
echo "datastores on host ports (empty = good):";
sudo ss -ltn | grep -E ':5432 |:9000 |:9001 ' || echo "(none published)"Expected output:
engine loopback only: LISTEN 0 4096 127.0.0.1:1481 0.0.0.0:* datastores on host ports (empty = good): (none published)Retrieve Your Per-VM Credentials
On the first boot of your virtual machine, contember-firstboot.service generated a privileged content-API root token, a Studio superadmin password, an engine encryption key, and the PostgreSQL and MinIO secrets, all unique to your instance. Then contember-postboot.service migrated and seeded the demo content and verified the superadmin sign-in.
Retrieve the Studio login with:
sudo cat /root/contember-credentials.txtThe file reports the Studio URL and the superadmin login:
STUDIO_URL=http://<vm-public-ip>/ ADMIN_EMAIL=admin@contember.local ADMIN_PASSWORD=<generated-per-vm> PROJECT=appA shipped prover confirms the whole security model without exposing a privileged secret. It proves that the Studio loads through nginx and that the default forty-zero token and unauthenticated queries are rejected. It also proves that the per-VM root token reads the seeded content, the superadmin signs in, and a wrong password does not:
sudo /usr/local/sbin/contember-roundtrip.shExpected output:
OK round-trip: studio=http://<vm-public-ip>/ admin=admin@contember.local project=app (default zero-token + unauth content rejected; per-VM root token reads content; superadmin signIn ok:true; wrong password rejected)Understand the Access Model
The engine's tokens gate the content API, and the per-VM superadmin login gates the Studio. You can see the token model directly against the content endpoint (/content/app/live) through nginx. The upstream default forty-zero token is rejected, while your per-VM root token reads the seeded articles.
RT=$(sudo grep '^CONTEMBER_ROOT_TOKEN=' /etc/contember/contember.env | cut -d= -f2-)
echo "# default 40-zero token (rejected):" curl -s -X POST http://127.0.0.1/content/app/live -H 'Content-Type: application/json' \
-H "Authorization: Bearer 0000000000000000000000000000000000000000" \
-d '{"query":"{ listArticle { title } }"}' echo;
echo "# per-VM root token (reads seeded content):" curl -s -X POST http://127.0.0.1/content/app/live -H 'Content-Type: application/json' \
-H "Authorization: Bearer $RT" \
-d '{"query":"{ listArticle { title publishedAt } }"}' | jq .Expected output:
# default 40-zero token (rejected): {"errors":[{"code":401,"message":"Authorization failure: API key was not found"}]}
# per-VM root token (reads seeded content): { "data": { "listArticle": [ { "title": "Welcome to Contember", "publishedAt": "2026-01-01T09:00:00.000Z" }, { "title": "Getting started", "publishedAt": null } ] } }Sign In to the Studio
In a browser, go to http://<vm-public-ip>/. Contember shows its sign-in page. Enter the superadmin email (admin@contember.local) and the ADMIN_PASSWORD from Step 6: you need both to sign in.
After you sign in, you land on the Studio dashboard, with the project navigation on the left.
Manage Content
Click Articles in the navigation to open the demo content type. The image ships with a small Article model (a title, content and published date) and two seeded entries, so the Studio shows real content immediately.
From here, editors can sort, search, filter and export. Once you extend the model, they can also create and edit entries.
The same content is available over the GraphQL API for your website or app to consume. Authenticate with the per-VM root token (Step 7) and query /content/app/live. To add content programmatically, issue mutations such as createArticle.
Define Your Own Content Types
The demo Article model is a starting point. The content project lives on the VM at /opt/contember/project. Define entities in api/model/index.ts using Contember's schema definition. Then generate and apply a migration with the bundled CLI container:
cd /opt/contember/project
sudo nano api/model/index.ts
# add or edit entities
# Generate a migration from the model change:
sudo docker compose --env-file /etc/contember/contember.env -f /etc/contember/compose.yaml \
run --rm contember-cli migrations:diff my-change --yes
# Apply it against the running engine:
sudo docker compose --env-file /etc/contember/contember.env -f /etc/contember/compose.yaml \
run --rm contember-cli migrations:execute --yesTo show a new entity in the Studio, add a page under admin/app/pages/ and rebuild the admin. See the Contember documentation for the full modelling and Admin component reference.
Put TLS in Front (Production)
The Studio is served over plain HTTP on port 80. Before you manage content over the public internet, terminate TLS in front of nginx. Add a Let's Encrypt certificate to the bundled nginx:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.comPoint your DNS A record at the VM's public IP first, and open port 443 in the network security group.
Certbot updates the bundled nginx vhost in place and preserves the reverse-proxy and static-serving setup. The admin calls the API on the request origin, so no application change is needed after you add a domain.
Back Up the Content
All content lives in the PostgreSQL container's postgres_data volume, and media in MinIO's minio_data volume. Take a consistent database dump through the running container:
sudo docker compose --env-file /etc/contember/contember.env -f /etc/contember/compose.yaml \
exec -T postgres pg_dump -U contember contember | gzip > contember-backup-$(date +%F).sql.gz ls -lh contember-backup-*.sql.gzStore backups off the VM, for example in Azure Blob Storage. If you rely on uploaded media, snapshot the MinIO minio_data volume alongside the database dump.
Managing the Contember Services
# Status and logs systemctl status contember --no-pager sudo docker compose --env-file /etc/contember/contember.env -f /etc/contember/compose.yaml logs --tail 100 engine
# Restart the stack
sudo systemctl restart contember
# nginx (the front door)
sudo nginx -t && sudo systemctl reload nginxArchitecture Summary
Contember Engine 2.0.8 is the Node GraphQL server (/tenant, /system, /content). It is published to 127.0.0.1:1481 (loopback) and reverse-proxied by nginx. The project slug is app, so the content API is /content/app/live.
PostgreSQL 16 and MinIO are the content datastore and media store. They run on the private Docker network only (no host port), and their data lives in named volumes.
nginx on port 80 serves the static Admin/Studio (the login SPA at /, the app at /app/) and proxies the engine's GraphQL endpoints. It is same-origin, so no per-VM IP is baked into the admin.
The first-boot service generates the per-VM privileged root token, superadmin password, encryption key and datastore secrets before the stack starts. The public login token is a non-secret front-end token. No shared or default privileged credential ships in the image, and the default forty-zero token is rejected.
Post-boot service applies the content migration, seeds demo content, and verifies the superadmin sign-in, so a fresh instance is immediately usable.
Support
SecureAnyCloud provides 24/7 technical support for this Contember image by email (sales@secureanycloud.com) and live chat, with a guaranteed 24 hour response SLA and a one hour average response time for critical issues.
We help with deployment, retrieving and rotating the first-boot credentials, modelling content types and running migrations, building Admin/Studio pages, and consuming the GraphQL content API. We also help with configuring MinIO media storage, adding HTTPS and a custom domain, database and media backup and restore, upgrades and patch management, and troubleshooting. For billing, subscription changes or refund requests, contact sales@secureanycloud.com.
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 Azure
Launch Contember 2 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