Run a local demo
This page gets a working Thematiq running on your own machine in two commands. You end with government theming applied to a real portal.
It is a demo, not a development environment. Nothing is mounted from a checkout, and that is deliberate — see What this is not.
What you need
Docker, with Compose v2.23 or newer. Nothing else — no PHP, no Node, no Nextcloud.
docker --version
docker compose version
If docker compose version prints v2.22 or older, upgrade first. The compose file declares its scripts inline via configs, and older versions ignore the content: field silently — which produces an instance with no apps installed and nothing in the logs to explain why.
Step 1 — get the compose file
curl -fsSLO https://raw.githubusercontent.com/ConductionNL/thematiq/development/thematiq-compose.yaml
A single self-contained file. There is nothing else to fetch and nothing to edit.
Step 2 — start it
docker compose -f thematiq-compose.yaml up -d
The first run takes a few minutes: it pulls three images and downloads the application archives. Watch it work if you like:
docker compose -f thematiq-compose.yaml logs -f app-installer
You are looking for:
==> installing openregister <version>
==> installing thematiq <version>
==> installing integriq <version>
==> installing portaliq <version>
==> apps present: integriq openregister portaliq thematiq
Then Nextcloud installs itself and enables the apps in dependency order. OpenRegister goes first: it owns the registers and schemas the others declare against, and a leaf app enabled before it finds no register to attach to.
That is done when this returns "installed":true:
curl -s http://localhost:8604/status.php
Step 3 — open the demo
| What | Where |
|---|---|
| Public portal | http://localhost:8604/apps/portaliq/site?portal=demo |
| Thematiq | http://localhost:8604/apps/thematiq/ |
| Admin interface | http://localhost:8604 — admin / admin |
Why the portal needs ?portal=demo
An instance can host several portals, and Portaliq resolves which one to serve in exactly two ways: an explicit ?portal=<slug> parameter, or a request hostname matching a portal's verified domain.
There is deliberately no third mode and no "default portal" fallback, because a default is how a multi-tenant host ends up serving one tenant's content under another tenant's domain. The seeded demo portal ships with no domains — an install hook has no business claiming a hostname on your behalf — so the slug parameter is how you reach it.
To drop the parameter on a throwaway box, bind localhost to the portal yourself under Portaliq → Portals → demo → Domains and mark it verified.
What gets installed, and why more than one app
| App | Why |
|---|---|
openregister | Required. Every Connext app declares its registers and schemas against OpenRegister. |
thematiq | Optional. Government theming. Absent, the UI renders unthemed rather than wrong. |
integriq | Optional. The connector, for feeding in data from systems you do not control. |
thematiq | The app this page is about. |
portaliq | Renders the public portal. |
That OpenRegister dependency is not declared in appinfo/info.xml — no app in the fleet declares an <app> dependency — so nothing stops the App Store from installing thematiq without it. It would then load, find no register to attach to, and show you an empty app rather than an error. The compose file encodes the dependency the manifest does not.
Verifying it actually worked
A page loading is not the same as a page working. Nextcloud serves its shell before the app decides whether it has anything to render, so an app URL returns HTTP 200 even when it resolves to nothing at all. A smoke test that checks for a 200 would call that a success.
Check content instead:
# Should name the portal, not answer {"error":"not_found"}
curl -s "http://localhost:8604/apps/portaliq/api/content/site?portal=demo"
# OpenRegister has registers — an empty list means the configuration
# was never imported, which is not the same as "nothing configured yet"
curl -s -u admin:admin "http://localhost:8604/apps/openregister/api/registers" | head -c 300
Changing the defaults
The port and every version are overridable:
DEMO_PORT=9000 \
THEMATIQ_VERSION=1.2.3 \
docker compose -f thematiq-compose.yaml up -d
Leaving a version empty resolves the newest release for that app, pre-releases included — which is what most Connext apps still ship, so that is the default.
Tearing it down
# Stop, keep the data
docker compose -f thematiq-compose.yaml down
# Stop and delete everything, including the database
docker compose -f thematiq-compose.yaml down -v
What this is not
It is not a development environment, and it cannot be turned into one by adding a bind mount.
Nextcloud installs and updates an app by deleting the app directory and extracting a fresh archive over it. Point that at a checkout and an app-store update will delete your working tree — measured on a development machine on 27 August 2026, where \OC\Updater::upgradeAppStoreApp fired on a container restart and removed every top-level file from a bind-mounted checkout, including its .git directory. Only the subdirectories it lacked permission to unlink survived.
So this compose keeps its apps in a named volume and installs them from release archives. That also happens to be the only thing that works: a release archive is a complete app carrying vendor/ and the built js/ bundle, while a git clone carries neither — and a Nextcloud app with no vendor/ does not fail loudly. It warns once and keeps loading, so the app appears installed while every service that needs a dependency is quietly absent.
To work on these apps rather than with them, use the development environment instead.
Troubleshooting
app-installer exits non-zero. It could not download an archive. Check the log for the URL it tried; the most common cause is a pinned version with no matching release.
It stops with openregister missing; aborting. Deliberate. Every other app declares registers against OpenRegister, so a stack without it would start and then fail in a dozen confusing ways instead of one clear one.
The UI renders unthemed. Thematiq is not installed or not enabled. Expected, and cosmetic — the theme resolver renders unthemed rather than wrong when it is absent.
Everything returns 404 or a maintenance page after a restart. Nextcloud is waiting for an upgrade. Run docker compose -f thematiq-compose.yaml exec -u www-data nextcloud php occ upgrade.