OpenCatalogi is a Nextcloud app built on OpenRegister. Reading about what it does is not the same as clicking through it, and standing up a Nextcloud by hand to find out is more work than most people will do on a first look.
This tutorial removes that step. One file, two commands, and you are looking at the real thing.
Connext is not one app. It is a registry (OpenRegister) with a set of applications built on top of it, and the interesting behaviour lives in how they fit together — a catalogue that publishes, a portal that renders it, a connector that feeds it. Reading about that is not the same as clicking through it.
This tutorial gets the whole thing running locally so you can click through it.
This is a demo environment, not a development environment. It runs the real software and behaves the same way, so what you learn here applies to a production deployment. But nothing here is backed up, and docker compose down -v permanently deletes all of it. Explore freely; do not put real data in it.
You need v2.23 or newer. If it prints something older, upgrade before continuing.
This matters more than a version check usually does. The compose file declares its install scripts inline, using a Compose feature older versions do not have — and they do not complain about it. They ignore the field, start Nextcloud with no applications installed, and give you an empty instance with nothing in the logs to explain why.
Nextcloud then installs itself and enables the apps in dependency order — OpenRegister first, because every other app declares its registers and schemas against it, and a leaf app enabled before it finds no register to attach to.
The stack is ready when this returns "installed":true:
Start with the portal. It is public — no login — and that is the point: it is what a citizen would see. Signing in as admin shows you a different and more permissive view.
One instance can host several portals, and Portaliq resolves which to serve in exactly two ways: an explicit ?portal=<slug>, or a request hostname matching a portal's verified domain.
There is deliberately no third mode and no default-portal fallback. A default is precisely 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 at all, because an install hook has no business claiming a hostname on your behalf — so the slug parameter is how you reach it.
On a throwaway box you can bind localhost yourself under Portaliq → Portals → demo → Domains, mark it verified, and then drop the parameter.
This is the step people skip, and it is the one worth doing.
A page loading is not a page working. Nextcloud serves its page shell before an app decides whether it has anything to render, so the portal 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.
So check content instead:
# Should name the portal — not answer {"error":"not_found"}
The second one deserves a note. An empty directory does not mean "no federation peers" — it means the register configuration was never imported. Those two states are indistinguishable from the outside, and one of them is a broken install. That ambiguity is exactly why the check is worth running rather than trusting the absence of an error.
By default each app resolves to its newest release, pre-releases included, because most Connext apps do not yet publish a stable one. That is honest about what exists, but it is not reproducible.
You may notice this compose file downloads release archives rather than pointing at repositories, and that no volume maps to a directory on your machine. Both are deliberate.
Nextcloud installs and updates an app by deleting the app directory and extracting a fresh archive over it. Point that at a git checkout and an app-store update will delete your working tree — we measured exactly that on a development machine in August 2026: an updater fired on a container restart and removed every top-level file from a bind-mounted checkout, including its .git directory.
There is a second reason, and it is the one that bites quietly. A release archive is a complete application: it carries its vendor/ directory and its built JavaScript. 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 silently missing.
To work on these apps rather than with them, you want a development environment instead. This file cannot serve that purpose and does not try.
Troubleshooting
Something not behaving? Find the matching situation below.
!`app-installer` exits and some apps are missing
The log ends with !! NOT INSTALLED: <names>. An archive could not be downloaded — usually a pinned version with no matching release. The rest of the stack still starts; run up again to retry the missing ones.
!The whole thing stops with `openregister missing; aborting`
OpenRegister could not be downloaded, and the installer refuses to continue without it. 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. Check your connection and run up again.
!The portal renders but has no styling
NLDesign is not installed or not enabled. The theme resolver deliberately renders unthemed rather than wrong when it is absent, so this is a cosmetic failure, not a broken portal.
!The directory API answers `{"results":[],"total":0}`
The register configuration was not imported. Re-run it from Settings → OpenCatalogi → Reload configuration.
!Everything returns 404 or a maintenance page after a restart
Nextcloud is waiting for an upgrade. Run docker compose -f connext-compose.yml exec -u www-data nextcloud php occ upgrade.
!Ports 8700 is already in use
Set another one: CONNEXT_PORT=9000 docker compose -f connext-compose.yml up -d. The port also has to appear in the trusted-domain list, which the compose file handles for you from the same variable.
Next step
A running stack is the entry point, not the destination.
OpenCatalogi is the publishing layer for Conduction apps. Where OpenRegister stores your data and Integriq fills it from elsewhere, OpenCatalogi turns a register you own into a public, browsable, harvestable catalog: a set of API endpoints that anyone on the internet can read, and a DCAT-AP-NL feed that national portals like data.overheid.nl and data.europa.eu can harvest. It does this without a "publish" button and without copying your data anywhere — publishing is a rule on the data, enforced by OpenRegister, and a catalog is just a filter that says which data belongs in the shop window.
This tutorial takes the Pet Store register you built in the first two parts and makes it public. You scope a catalog to the register, set up the visibility rule, publish nine pets while deliberately keeping one hidden, prove the rule from a logged-out browser, and expose the whole thing as a DCAT-AP-NL feed. No code, no second server, no export.
The first three parts of this series set up the registers, the files, and a public catalog for Dutch open-government data. Conduction also has a three-tutorial pipeline that teaches the same three apps from the ground up, end to end, on the canonical Pet Store sample:
This capstone is the Woo & DCAT overlay on that pipeline. Rather than repeat the mechanics, it points you at each deep tutorial and then layers the open-government specifics on top: the canonical registers you import instead of hand-building, the open-data source you map to a Dataset, and the DCAT-AP-NL feed that national portals harvest. The shape is identical to the Pet Store chain — grab data from a source, and publish it on a catalog — only the domain changes.
In Part 1 you imported a DCAT register for open data. In this tutorial you make its datasets findable: you scope a catalog to the register, set one date field that controls visibility, and expose the catalog as a DCAT-AP-NL feed. National portals like data.overheid.nl harvest that feed.