diff --git a/docs/roadmap/marketplace-website.md b/docs/roadmap/marketplace-website.md
new file mode 100644
index 0000000..f2f4b21
--- /dev/null
+++ b/docs/roadmap/marketplace-website.md
@@ -0,0 +1,132 @@
+# LibrePortal Marketplace — the marketplace app (Roadmap / Design)
+
+**Status:** direction decided 2026-07-03 (container MVP planned; submissions phase 2) ·
+**Audience:** us, future-self · **Scope:** the browsable marketplace *system* — the
+apps.nextcloud.com analogue — shipped as a LibrePortal app, and how community
+submissions eventually flow through it · Builds on `updates-and-distribution.md` §3/§8.
+
+---
+
+## 1. The decided shape: the marketplace is a LibrePortal app
+
+The marketplace server ships **in this repo as `containers/libreportal-marketplace/`**
+— a normal app definition installed through the normal pipeline, with one twist:
+it is **dev-mode-only** (see §4). The official `marketplace.libreportal.org` is
+simply *our* instance of this app running on our own box — full dogfooding — and
+anyone who wants their own marketplace enables Developer Mode and installs the
+same app. "Open source and self-hostable" is therefore not a promise, it's the
+literal distribution mechanism.
+
+The marketplace has two user-facing surfaces over **one source of truth**:
+
+```
+ ┌────────────────────────────────────────────┐
+ │ THE SIGNED CATALOG (the source of truth) │
+ │ //index.json (+ .minisig) │
+ │ //payloads/.tar.gz │
+ └───────────┬───────────────────┬────────────┘
+ │ │
+ boxes fetch + verify it the marketplace app
+ │ serves + renders it
+ ┌──────────────▼─────────┐ ┌──────▼──────────────────┐
+ │ IN-APP (every box) │ │ libreportal-marketplace │
+ │ App Center grid shows │ │ (dev-mode app): hosts │
+ │ "Available — Add" │ │ the catalog tree + a │
+ │ cards; Add = task → │ │ browsable website: │
+ │ verify → drop-in │ │ browse, search, per-app │
+ └────────────────────────┘ │ pages, submit info │
+ └─────────────────────────┘
+```
+
+Boxes never talk to the website and never trust it — they only trust the
+minisign signature on the catalog. A marketplace instance can be compromised,
+defaced, or down and no install is ever at risk. That property is load-bearing;
+every choice below preserves it.
+
+## 2. What the container serves
+
+One app, one docroot, two things in it:
+
+- **The catalog tree itself** — `//index.json(.minisig)` +
+ `//payloads/…`, exactly the layout `make_app.sh` / `make_hotfix.sh`
+ emit into `dist/`. A box pointed at this instance via `CFG_RELEASE_BASE_URL`
+ consumes it directly.
+- **The browse UI** — static pages (no third-party assets, same rule as the
+ WebUI) rendered **client-side from the same `index.json`**: front page by
+ category, search, one page per app (title, description, icon, publisher +
+ trust badge, version), a copyable `libreportal app add ` snippet, and a
+ "run your own marketplace" page documenting exactly this app + the
+ `CFG_RELEASE_BASE_URL` knob. Client-side rendering means the UI needs no build
+ step and can never disagree with the catalog it serves.
+
+**No backend.** No accounts, no database, no API server in the MVP. Publishing
+*to* a marketplace = running the publisher tools and syncing their `dist/`
+output into the app's docroot (its bind-mounted data dir).
+
+**Server:** `nginx:alpine` static serving (the pattern the retired
+`getlibreportal` container established: JSON no-cache, tarballs/sigs immutable).
+Traefik routing, ports, backup labels — all standard app-definition machinery.
+
+## 3. Community submissions (phase 2 — the F-Droid flow, not the Play flow)
+
+When community apps open up (demand-gated, per `updates-and-distribution.md`
+§8.5 fork 4), submission is a **pull request, not an upload**:
+
+1. Author PRs their app folder (`containers//` shape — the drop-in contract
+ in `docs/contributing/development.md`) to a public catalog repo.
+2. CI validates mechanically: the bundle tarball contract, compose lint, no
+ host-script hooks unless flagged for review, icon/meta present.
+3. A maintainer reviews — *especially* any `tools/*.sh` (the one genuinely
+ dangerous surface, §3.2) — then signs and publishes with the same tools used
+ for first-party apps. The entry lands in the index with `publisher:`,
+ `trust:"community"`.
+4. The marketplace site renders the submission queue from the PR list (links,
+ not accounts) and the published result from the index.
+
+Everything ends in "reviewed → signed → one public catalog" because the boxes
+only trust the signature. An account-based submission portal could be added to
+the marketplace app later, but it would only ever be a nicer way to open the PR
+— it can never become an unreviewed upload channel.
+
+## 4. Dev-mode gating (the important part)
+
+The marketplace app must not confuse regular users browsing their App Center —
+running a marketplace is an operator/developer act. So it ships **hidden unless
+Developer Mode is on**, riding the existing `CFG_DEV_MODE` machinery (10-click
+logo easter egg; auto-enabled on git/local installs):
+
+- New app-level convention: `CFG__DEV_ONLY=true` in the app's `.config`.
+- `webuiGenerateLibrePortalConfig` emits it as `dev_only: true` in `apps.json`.
+- The App Center filters `dev_only` apps out of the grid/search unless
+ `window.systemConfigs.CFG_DEV_MODE === 'true'` (the same flag the `**DEV**`
+ config-field filter uses — see `field-factory.js` `_filterDevKeys`).
+- The CLI stays honest: `libreportal app install libreportal-marketplace` works
+ regardless (dev-only is decluttering, not a security boundary — the security
+ boundary is the signing key, which the app never contains).
+
+The convention is generic — any future operator-grade app (build servers, relay
+infrastructure) can reuse it.
+
+## 5. Explicitly not doing
+
+- **No dynamic marketplace backend** (accounts / uploads / ratings) in the MVP —
+ re-affirming §3's decision; nothing in the in-app UX needs it, and phase 2
+ submissions stay review-and-sign whatever front door exists.
+- **No telemetry** on the marketplace site (same rule as the product); download
+ counts, if ever wanted, come from static server logs on *our* instance only.
+- **No website-only metadata.** If the site needs a field (e.g. screenshots
+ later), it enters the catalog format as an optional envelope/`meta` addition
+ so in-app and website stay one source of truth.
+
+## 6. Sequencing
+
+1. **Now:** the in-app registry slice (`updates-and-distribution.md` §8.7) —
+ catalog format gains `meta` (category/description/icon), `make_app.sh`
+ publishes app bundles. The catalog becomes real.
+2. **Now (after the publisher tool exists):** `containers/libreportal-marketplace/`
+ MVP — nginx app serving the catalog tree + client-rendered browse UI, plus
+ the `CFG__DEV_ONLY` gating convention. End-to-end tests then run against
+ a real marketplace instance instead of `python3 -m http.server`.
+3. **Demand-gated:** the community submission flow (public catalog repo + CI
+ validation + review/sign tooling), yellow-tier rendering, and the in-app
+ community trust-tier UX (host-script quarantine — §8.7 deferred).