OpenFactory Nextcloud lab with Postgres, Redis cache and Collabora Online Office

Build a Self-Hosted Nextcloud on OpenFactory

A four-VM cloud productivity stack: Nextcloud + Postgres + Redis + Collabora, from one prompt

May 23, 2026

← Back to Blog

Nextcloud is the most-deployed self-hosted alternative to Google Drive / Microsoft 365, with file sync, calendar, contacts, talk, and collaborative office docs in one app. It's consistently in the top of every r/selfhosted survey and the seventh-most-deployed in 2026.

This post walks through the production-shaped Nextcloud stack on OpenFactory: four buildable VMs — the Nextcloud app, Postgres, Redis cache, and Collabora Online for in-browser office docs — from one prompt, shipped as bootable ISOs.

What you'll build

  • nextcloud-app (10.76.0.10:80) — the app server with a ready config.php.example wired to all three backends.
  • nextcloud-db (10.76.0.20:5432) — Postgres configured for the Nextcloud schema with subnet ACLs in place.
  • nextcloud-redis (10.76.0.30:6379) — Redis for memcache.local and file-lock backends; absolute requirement for multi-user Nextcloud performance.
  • collabora (10.76.0.40:9980) — CODE host for in-browser doc / sheet / slide editing.

Why build it on OpenFactory

  • The ISO is the spec. config.php.example already points at Postgres, Redis, and Collabora — copy it to config.php at deploy and you're wired.
  • Redis is in the recipe, not an afterthought. Single-user Nextcloud runs without it; multi-user Nextcloud needs it to keep sync from feeling like 2008.
  • Office docs ship with the stack. Collabora Online is its own VM so the office workload doesn't fight the web UI for CPU.
  • Scenario assertions ride along. The build fails closed if Nextcloud's status endpoint doesn't report installed, if Collabora's discovery isn't wired, or if any backend port doesn't listen.

Topology

Four Debian Trixie VMs on 10.76.0.0/24. The app server is the only one with public-facing intent; the database, cache, and office host stay on the lab subnet.

The prompt

Paste this verbatim into the chat builder at console.openfactory.tech. Nothing above or below it — the builder expects the prompt body to start at the “Build a compact multi-node lab…” line.

Build a compact multi-node lab named `nextcloud-cloud-stack`.

Output discipline: keep the plan small. Use one startup script per node, about 25 shell lines or less. Do not install Nextcloud server PHP code, Apache or PHP-FPM, Collabora Online CODE binaries, or external `apt` repos at build time. Write deployment-time config examples and tiny Python stdlib or shell compatibility stubs only. The goal is a buildable preparation lab, not a production deployment.

## Topology

Create 4 buildable `debian-trixie` nodes, all `x86_64`, SSH enabled, DHCP/default route intact with lab aliases, firewall disabled, DNS `1.1.1.1` and `8.8.8.8`, user `ops` password `nc-ops` in `sudo`. Every recipe must set top-level `test_config` to `{ "enabled": false, "tests": [] }`.

- `nextcloud-app`: role `cloud-app`, 3 GB RAM, 24 GB disk, alias `10.76.0.10/24`, x `230`, y `60`
- `nextcloud-db`: role `database`, 2 GB RAM, 16 GB disk, alias `10.76.0.20/24`, x `110`, y `220`
- `nextcloud-redis`: role `cache`, 1 GB RAM, 8 GB disk, alias `10.76.0.30/24`, x `350`, y `220`
- `collabora`: role `office`, 2 GB RAM, 12 GB disk, alias `10.76.0.40/24`, x `230`, y `380`

Connections: `nextcloud-app` to `nextcloud-db:5432`, `nextcloud-redis:6379`, `collabora:9980`.

## Common Recipe Requirements

All nodes: features `headless`, `ssh`; packages `openssh-server`, `python3`, `curl`, `jq`, `iproute2`, `netcat-openbsd`, `ca-certificates`. Each startup script adds the alias with `IFACE=$(ip route show default | awk '{print $5; exit}')`, `ip link set "$IFACE" up || true`, and `ip addr add <alias> dev "$IFACE" || true`. If `os.startup_scripts[].after` is present, it must be the string `"network-online.target"`, not an array.

## Node Requirements

`nextcloud-app`: features `headless`, `ssh`. Write `/etc/nextcloud/config.php.example`:
```
<?php
$CONFIG = array (
  'dbtype' => 'pgsql',
  'dbhost' => '10.76.0.20',
  'dbname' => 'nextcloud',
  'dbuser' => 'nextcloud',
  'dbpassword' => 'nextcloud',
  'redis' => array('host' => '10.76.0.30', 'port' => 6379),
  'memcache.local' => '\OC\Memcache\Redis',
  'datadirectory' => '/var/lib/nextcloud/data',
  'overwrite.cli.url' => 'https://cloud.lab',
);
```
Create `/var/lib/nextcloud/{data,apps}` mode `0750 ops:ops`. Add a Python stdlib service on `0.0.0.0:80` exposing:
- `GET /status.php` -> `200 {"installed":true,"maintenance":false,"productname":"Nextcloud","version":"compat-1.0","versionstring":"compat-1.0"}`
- `GET /ocs/v2.php/cloud/capabilities?format=json` -> `200 {"ocs":{"meta":{"status":"ok","statuscode":200},"data":{"version":{"string":"compat-1.0"},"capabilities":{}}}}`
- `GET /metrics` -> `nextcloud_compat_up 1`
Register `nextcloud-compat.service`.

`nextcloud-db`: features `headless`, `ssh`, `postgresql`; packages `postgresql`, `postgresql-client`. Listen on `0.0.0.0:5432`, best-effort create role/database `nextcloud` password `nextcloud`, allow `10.76.0.0/24`. Expose `:9187/metrics` with `pg_compat_up 1`.

`nextcloud-redis`: features `headless`, `ssh`, `redis`; packages `redis-server`. Bind to localhost plus `10.76.0.30`. Expose `:9121/metrics` with `redis_compat_up 1`.

`collabora`: features `headless`, `ssh`. Write `/etc/coolwsd/coolwsd.xml.example` with `<allowed_hosts><host desc="lab">10\.76\.0\..*</host></allowed_hosts>` and `<ssl><enable type="bool">false</enable></ssl>`. Add a Python stdlib service on `0.0.0.0:9980` exposing `GET /hosting/discovery` -> `200 <?xml version="1.0" encoding="utf-8"?><wopi-discovery><net-zone name="external-http"/></wopi-discovery>` with `Content-Type: text/xml`, `GET /hosting/capabilities` -> `200 {"convert-to":{"available":false},"hasMobileSupport":true,"hasProxyPrefix":false}`, `GET /metrics` with `collabora_compat_up 1`. Register `collabora-compat.service`.

## Scenario

Emit exactly one group scenario named `nextcloud-cloud-stack-validation`. Put `custom_tests[].assertions[]` inside the scenario entry; leave `scenarios[].tests` empty. Every assertion needs `on_vm`. Use only `port_listening`, `command_output`, and `http_responds`; do not emit `vm_boots`, `network_reachable`, or `service_running`.

- `Stack ports listen`: `port_listening` for `nextcloud-app:80`, `nextcloud-db:5432`, `nextcloud-redis:6379`, `collabora:9980`.
- `Nextcloud status`: on `nextcloud-app`, `curl -fsS http://localhost/status.php | jq -e '.installed == true and .productname == "Nextcloud"' >/dev/null && echo nc-ok`.
- `Nextcloud capabilities`: on `nextcloud-app`, `curl -fsS 'http://localhost/ocs/v2.php/cloud/capabilities?format=json' | jq -e '.ocs.meta.statuscode == 200' >/dev/null && echo caps-ok`.
- `Collabora discovery`: on `collabora`, `curl -fsS http://localhost:9980/hosting/discovery | grep -q 'wopi-discovery' && echo collabora-ok`.
- `App reaches backends`: on `nextcloud-app`, `nc -z -w 5 10.76.0.20 5432 && nc -z -w 5 10.76.0.30 6379 && nc -z -w 5 10.76.0.40 9980 && echo backends-reachable`.

Preserve warnings that real Nextcloud PHP application install (or AIO image), PHP-FPM tuning, opcache and APCu, Redis cluster setup, Collabora Online CODE binary distribution, S3/object-storage external storage, real TLS termination, backup of `/var/lib/nextcloud/data`, app store integrations, and `10.76.0.0/24` aliasing are deployment-time concerns.

Running it

  1. Open the chat builder at console.openfactory.tech and paste the prompt into a new conversation.
  2. Review the streamed build plan. You'll see the topology, per-node recipes, and the scenario assertions that will run after boot. Edit the prompt and re-run if anything is off.
  3. Click Build group. OpenFactory fans the plan out to per-node ISO builds. When every ISO reaches built, boot the group on the runner network from the same UI.
  4. Exercise the stack. The scenario assertions run automatically against the live VMs. From the host you can also hit the service ports directly to confirm end-to-end behavior.

Driving OpenFactory from an AI agent instead of the browser? The same flow is exposed through the OpenFactory MCP server — submit the prompt programmatically, get the build-plan preview back, and call create_build / start_vm on the resulting recipes. Single-image builds go straight through the openfactory CLI.

What's still your responsibility

The prompt produces a buildable preparation lab — the right topology, the right ports listening, deployment-time config templates dropped in the right places, and tiny compatibility services that prove the wiring works. A few things still sit outside the recipe and need operator attention before this carries real load:

  • Real Nextcloud install. Install from the upstream PHP release or use the Nextcloud AIO container. The config is already pointing at the right backends.
  • PHP-FPM tuning. Worker count, opcache, APCu — Nextcloud's admin-overview page will yell at you until they match the doc.
  • Real Collabora binary. Drop in CODE from the upstream tarball; the YAML stub is already in place.
  • Real TLS. Layer the nginx + TLS reverse proxy pattern in front, or use Caddy with automatic ACME.
  • S3 external storage. If the data volume outgrows local disk, wire S3 / Wasabi / R2 via the External Storage app.
  • Off-host backups. The data directory + the Postgres dump together. Restoring Nextcloud from only one of them is a bad day.

Where to go next

Cloud storage gets a password manager next. See the Vaultwarden password vault post. For locking down the kernel under regulated data, read the runtime attestation post. And the Enterprise & GxP page covers fleet deployments.

Ready to ship this in production?

OpenFactory's free flow is for browsing. Persistent VMs, SSH access, snapshots, your own ISO, and fleet deployment live on a paid plan.