Manifold
Overview
Manifold is a free, open-source platform for scholarly and digital publishing. It is developed by the University of Minnesota Press, the Graduate Center at CUNY and Cast Iron Coding. It turns monographs and journals into living, annotatable digital works.
Readers move through a text in an interactive reader, highlight passages, and leave annotations that become part of a shared scholarly conversation. A resource library holds the images, video and data that accompany each edition.
The SecureAnyCloud image delivers Manifold 9.2.0 fully installed and configured on Ubuntu 24.04 as a single-VM Docker Compose stack. A complete publishing platform is running within minutes of launch.
Manifold is licensed under the GNU General Public License v3.0 (GPL-3.0). All product and company names are trademarks or registered trademarks of their respective holders. This image repackages the upstream open-source release with SecureAnyCloud's provisioning and support.
What you get:
- Manifold 9.2.0, deployed from the official prebuilt v9 container images pinned to the release, so the image ships the pinned version and never upgrades silently
- A single-host Docker Compose stack of five services:
postgres:15-alpine(the database and full-text search), the Rails/Puma API, a Postgres-backedgood_jobworker, the Node/React server-rendered client, andnginx:1.27-alpineas the only public listener on port 80 - PostgreSQL full-text search (no Elasticsearch) and Postgres-backed background jobs (no Redis, no Sidekiq), tuned to fit Standard_B2s (2 vCPU / 4 GiB) with no swap
- Local-filesystem object storage on a Docker named volume (no MinIO, no S3 keys)
- A per-VM administrator, database password and Rails secret generated on first boot and written to a root-only file, so no default or shared login ships in the image
manifold.serviceanddocker.serviceas systemd units, enabled and active- Backed by 24/7 SecureAnyCloud support
Prerequisites
Before you deploy, you need an active Azure subscription, an SSH key pair, and a virtual network with a subnet in the target region.
Standard_B2s (2 vCPU, 4 GiB RAM) is the recommended starting point. Scale up for larger catalogues or heavier reading traffic.
Configure the network security group (NSG) inbound rules as follows:
- Allow
22/tcpfrom your management network. - Allow
80/tcp(HTTP) from your readers. - Allow
443/tcpif you enable HTTPS.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure portal and choose Create a resource. Search the Marketplace for Manifold 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).
Then choose 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:manifold-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name manifold \
--image sac1647283583153:manifold-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardOpen HTTP so that you can reach the platform:
az vm open-port --resource-group <your-rg> --name manifold --port 80 --priority 900Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>Step 4 - Confirm the stack is running
Manifold runs as a Docker Compose project at /var/lib/manifold. The manifold.service and docker.service units should both report active. All five containers should be Up, and only nginx on port 80 is exposed.
Run:
systemctl is-active manifold.service docker.service cd /var/lib/manifold && sudo docker compose ps --format '{{.Name}}\t{{.Status}}' curl -s -o /dev/null -w 'api /api/up -> %{http_code}\n' http://127.0.0.1/api/up curl -s -o /dev/null -w 'client / -> %{http_code}\n' http://127.0.0.1/The expected output is two lines of active, then the five manifold-* containers Up (with manifold-db reporting (healthy)), then two 200 responses.
The API (:3011), client (:3010) and PostgreSQL (:5432) listen only on the internal Docker network. nginx on port 80 is the single exposed application port.
Step 5 - Retrieve your administrator password
On the first boot of every VM, a one-shot service (manifold-firstboot.service) generates every per-VM secret: the Rails secret key, the PostgreSQL password and a random administrator password. It brings the stack up on a fresh empty database, creates a per-VM administrator and confirms that it authenticates. It then writes the credentials to a root-only file.
No shared or default login ships in the image.
Read the credentials file:
sudo cat /root/manifold-credentials.txtThe file (mode 0600, root only) contains the platform URL (manifold.url), the administrator email (MANIFOLD_ADMIN_EMAIL) and the administrator password (MANIFOLD_ADMIN_PASSWORD).
You can verify the sign-in from the VM's own shell. The following command reads the per-VM credentials and posts a real sign-in. A correct password returns a bearer token, which the authenticated /api/v1/me endpoint then accepts:
E=$(sudo grep '^MANIFOLD_ADMIN_EMAIL=' /root/manifold-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^MANIFOLD_ADMIN_PASSWORD=' /root/manifold-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -s -X POST http://127.0.0.1/api/v1/tokens \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg e "$E" --arg p "$P" '{email:$e,password:$p}')" | jq -r '.meta.authToken')
echo "bearer token length -> ${#TOKEN}" curl -s http://127.0.0.1/api/v1/me -H "Authorization: Bearer $TOKEN" \
| jq '{fullName:.data.attributes.fullName, role:.data.attributes.role}'The expected output is a non-zero token length, then "role": "admin". This confirms that the password in the file signs in as the administrator.
Step 6 - Sign in
Browse to http://<vm-public-ip>/ and choose Log In (top right). Enter the administrator email and password from the credentials file.
Reader self-registration is available too. A self-registered account is only ever an ordinary reader, and it never grants administrator access.
Step 7 - Browse the project library
The home page presents your library of projects. Each project is a scholarly work, such as a monograph, an edited collection or a journal. It has its own landing page describing the edition, its texts and its resources.
Open a project to reach its landing page. The page shows the title, description, cover, and the texts and resources that make up the edition.
Step 8 - Read a work in the interactive reader
Choose Read on a project's text to open Manifold's interactive reader. Move through the text section by section using the Contents menu, and adjust the typography.
When you are signed in, you can highlight any passage to annotate it or add it to a reading group. This turns a static publication into a living, annotatable digital work.
Step 9 - Confirm the security model and the pinned stack
Manifold ships with no known bootstrap credential. The image carries the pulled container images and the Compose configuration. It has an empty database and no admin until first boot creates the per-VM secrets.
Confirm that anonymous and wrong-password access are refused while the per-VM administrator works:
E=$(sudo grep '^MANIFOLD_ADMIN_EMAIL=' /root/manifold-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^MANIFOLD_ADMIN_PASSWORD=' /root/manifold-credentials.txt | cut -d= -f2-)
echo -n 'anonymous /api/v1/me -> '; curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/api/v1/me
BAD=$(curl -s -X POST http://127.0.0.1/api/v1/tokens -H 'Content-Type: application/json' \
-d "$(jq -nc --arg e "$E" '{email:$e,password:"definitely-wrong"}')" | jq -r '.meta.authToken // "null (rejected)"')
echo "wrong-password token -> $BAD"
sudo docker port manifold-db || echo '(no host port: PostgreSQL is not published to the host)'The expected output is 401 for the anonymous request, null (rejected) for the wrong password, and no host port mapping for the database.
The stack is pinned to Manifold 9.2.0 and uses PostgreSQL full-text search with no Elasticsearch, Redis or MinIO. It ships with no swap so that it fits Standard_B2s. Check it:
cd /var/lib/manifold
sudo docker compose images | awk 'NR>1{print $2":"$3}' | sort -u sudo swapon --show || echo '(no swap)' free -mThe expected output is the four pinned images (manifold-api:9.2.0, manifold-client:9.2.0, nginx:1.27-alpine, postgres:15-alpine), no swap devices, and memory well within the 4 GiB of a Standard_B2s.
Publishing your own works
You manage Manifold projects and texts from the backend and the command line. As the administrator, create a project in the backend UI, then ingest a text (Markdown, HTML, EPUB or a Google Doc) into it.
From the VM, you can ingest a local file straight into an existing project:
sudo docker compose -f /var/lib/manifold/docker-compose.yml exec api \
bin/rails "manifold:project:ingest[<project-id>,/path/to/text.html]"The ingestion runs through the Postgres-backed worker. It creates a text with its sections and table of contents, so the text renders in the interactive reader.
See the Manifold documentation for the full authoring and ingestion workflow, including EPUB and Google Docs sources.
First-boot service and security model
A one-shot manifold-firstboot.service runs after the network is up, and it is gated so that it runs exactly once. It performs these steps:
- Generates the Rails secret key, the PostgreSQL password and a random administrator password, and writes them mode
0600, root only, to/var/lib/manifold/.env - Resolves the VM's reachable address and points the client URLs at it
- Brings the stack up on the fresh empty database and loads the schema and seeds
- Creates a per-VM administrator and confirms that it authenticates
- Writes
/root/manifold-credentials.txt
manifold.service is ordered after first boot, so the platform never starts before the per-VM secrets and administrator exist.
systemctl is-active manifold-firstboot.service
sudo stat -c '%a %U:%G %n' /root/manifold-credentials.txt /var/lib/manifold/.envThe expected output is active, then 600 root:root for both files.
Enabling HTTPS with your own domain
The image serves the platform over plain HTTP on port 80. For a browser-trusted certificate, point a DNS record at the VM, open port 443 in the NSG, and install a certificate from your certificate authority (for example Let's Encrypt).
nginx terminates the connection in front of the stack. Update DOMAIN, CLIENT_URL, CLIENT_BROWSER_API_URL and CLIENT_BROWSER_API_CABLE_URL in /var/lib/manifold/.env to your https://your-domain.example.com address, then restart:
sudo apt-get update && sudo apt-get install -y certbot
sudo systemctl restart manifold.serviceBackup and maintenance
All persistent state lives in two Docker named volumes:
manifold_pgdataholds the PostgreSQL database, including full-text search.manifold_uploadsholds the uploaded resources and generated files.
Back them up together with a database dump and a volume archive:
sudo docker compose -f /var/lib/manifold/docker-compose.yml exec -T db \
pg_dump -U postgres manifold_production | gzip > manifold-$(date +%F).sql.gzSnapshot the OS disk from the Azure portal for a full point-in-time copy.
Keep the OS current with sudo apt-get update && sudo apt-get upgrade. The image ships with unattended security updates enabled.
Review the Manifold documentation before moving between releases, and always back up before an upgrade.
Support
This image is backed by 24/7 SecureAnyCloud support. Support covers:
- Deployment and initial configuration
- Retrieving the first-boot administrator password
- Publishing and ingesting texts (Markdown, HTML, EPUB and Google Docs)
- Configuring reading groups and annotation
- HTTPS and custom domain setup
- Performance tuning and storage administration
Email sales@secureanycloud.com or use the live chat in the support portal. Critical issues receive a one-hour average response.
Deploy on Azure
Launch Manifold 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