Why this, why now
Docker Desktop has an Extensions Marketplace with its own search and category filter. It lists 119 extensions today and the database category holds about six tools (Oracle SQLcl, Oracle SQL Developer Web, a SQL container manager, a pgAdmin derivative, Mongo Express, Redis Enterprise). Nothing covers more than one engine. A LibreDB Studio extension would be the only entry that opens PostgreSQL, MySQL, MongoDB, Redis, ClickHouse and the rest from one tab.
The door is closed at the moment: since 2026-06-16 Docker has paused new Marketplace submissions "while Docker reviews Marketplace security" (publish docs and the docker/extensions-submissions README carry the same sentence; manual review is paused too). When it reopens, submission is automated: an extension that passes validation is live "within a few hours". This issue is about being ready on that day, with a validated, multi-arch, self-installable extension that we have already used ourselves.
What we already have: the app image ghcr.io/libredb/libredb-studio is published for linux/amd64 and linux/arm64 (306 MB compressed, 19 layers), first run is zero-config (admin credentials generated and printed), seed connections load from a YAML file (SEED_CONFIG_PATH, reloaded with SEED_CACHE_TTL_MS, see docs/SEED_CONNECTIONS.md), and the health endpoint is GET /api/db/health.
What the extension does for a Docker Desktop user
- Opens the Extensions tab, sees "LibreDB Studio".
- The tab lists the database containers currently running on their Docker host (matched by image name and exposed port against the 16 engines we support), with engine, container name, port and status.
- One click on a container opens it in LibreDB Studio inside the same tab, connection already created. No copying of hostnames or ports, no browser hop.
- Everything else is the normal Studio: editor, schema explorer, ER diagram, EXPLAIN, charts, optional AI on the user's own key.
Docker's design guidelines ask for exactly this shape: build the feature natively, do not send the user to a webpage or the CLI, support light and dark themes, use the Docker MUI theme, add a header for context.
Proposed architecture (decision points marked)
Location. packaging/docker-extension/ in this repo, next to packaging/windows/launcher. Own Dockerfile, metadata.json, compose.yaml, ui/. Image name ghcr.io/libredb/libredb-studio-docker-extension, versioned with the app. Decision: in-repo (recommended, shares CI and release tagging) or a separate repo.
Two images, not one. The extension image holds only metadata.json, the static UI and the labels. Its vm.composefile runs the canonical app image ghcr.io/libredb/libredb-studio:<pinned version> as the backend service. The extension image stays tiny, the app image stays the single artifact we test and scan, and an app release only needs a version bump in compose.yaml.
metadata.json.
{
"icon": "libredb-studio.svg",
"ui": { "dashboard-tab": { "title": "LibreDB Studio", "root": "/ui", "src": "index.html" } },
"vm": { "composefile": "compose.yaml" }
}
No host section: we ship no host binaries. Extensions run with the user's privileges (Docker's security page), so the smallest possible footprint is a feature.
compose.yaml. The Studio service with a named volume for the SQLite storage layer (STORAGE_PROVIDER=sqlite, STORAGE_SQLITE_PATH on the volume) so connections survive restarts, a second volume or path for the seed file, AUTH_COOKIE_SECURE=false because traffic inside Desktop is plain HTTP, and NEXT_PUBLIC_AUTH_PROVIDER=local. Decision: whether to publish a host port (see embedding).
Embedding the Studio UI. The dashboard tab is a static React page; the Studio is a Next.js server. Options:
- A. The tab renders an iframe to the backend service on a published port. Simple, full Studio. Docker recommends sockets over ports for backend APIs to avoid collisions, so pick a high fixed port and check how existing full-UI extensions (Portainer, Mongo Express) do this before committing.
- B.
ddClient.extension.vm.service over the socket. Fine for JSON calls, not for hosting a whole app.
- C.
ddClient.host.openExternal to the browser. Works today, but the design guidelines explicitly discourage it. Keep as a fallback button only.
Recommendation: A, verified against two existing extensions in the first spike.
Container discovery and connection creation. The UI has Docker access through the SDK: ddClient.docker.listContainers() returns image, ports and networks. Map image names (postgres, mysql, mariadb, mongo, redis, clickhouse/clickhouse-server, trinodb/trino, apache/druid, elasticsearch, opensearchproject/opensearch, couchbase, mcr.microsoft.com/mssql/server, gvenzl/oracle-free, cassandra, ghcr.io/tursodatabase/libsql-server) to our engine type ids and default ports. Two ways to hand the result to the Studio:
- A. Write a seed file. The UI calls
ddClient.extension.vm.cli.exec to run a small script inside the Studio container that appends the connection to the seed YAML; SEED_CACHE_TTL_MS makes it appear without a restart. No new app API.
- B. Create the connection through the Studio's own storage API with the admin session. Needs token handling in the extension UI.
Recommendation: A for the first version; B only if A hits a wall.
Reaching the databases. The Studio runs as a container in the Desktop VM; user databases run on their own networks. If the database publishes a port, host.docker.internal:<port> works. If it does not, attach the Studio container to that network (docker network connect) from the UI before creating the connection. Decision: support both, or require a published port in v1 and say so in the UI.
Credentials. Zero-config first run generates the admin password; the extension UI must surface it once (or set ADMIN_PASSWORD from a value the UI generates and stores in the volume). Decision in the spike.
Marketplace requirements to bake in from day one
Required image labels (all in the extension Dockerfile; a missing one makes the extension invalid):
org.opencontainers.image.title, org.opencontainers.image.description, org.opencontainers.image.vendor, com.docker.desktop.extension.api.version (use docker extension version and set >= <that version>), com.docker.desktop.extension.icon (svg, visible on light and dark), com.docker.extension.screenshots (JSON array, recommended 2400x1600, hosted URLs), com.docker.extension.detailed-description, com.docker.extension.publisher-url (https://libredb.org), com.docker.extension.changelog (current version only).
Recommended: com.docker.extension.additional-urls (documentation, support, privacy policy), com.docker.extension.categories set to database,utility-tools. Without the categories label the extension does not appear in category filters.
Screenshots and icon need stable public URLs; the repo's public/ or a packaging/docker-extension/assets/ path served from GitHub raw is enough.
Validation, local install and pre-Marketplace distribution
docker extension validate ./metadata.json before the image exists, docker extension validate <image> after.
docker buildx build --push --platform=linux/amd64,linux/arm64 for the extension image; Desktop refuses to install an image without the user's architecture.
docker extension install <image>:<tag> on macOS arm64 and Windows amd64. Both need the Desktop setting "Allow only extensions distributed through the Docker Marketplace" turned off while the Marketplace is closed; the docs page on non-marketplace extensions shows the warning users will see.
docker extension share <image>:<tag> produces a link that opens Desktop with a Marketplace-style preview. This is how we test the listing copy and screenshots before the Marketplace reopens, and how team members install it.
Repo work that comes with it
- CI: build, validate and push the extension image on release tags (extend
.github/workflows/docker-build-push.yml or add a sibling workflow). The validate step is the test; it fails on a missing label or an invalid metadata.json.
distribution/channels.yaml: add docker-desktop-extension with status pending and this issue as links.tracking_issue; scripts/distribution-check.mjs validates the enums, and the login showcase is derived from this file, so run bun run channels:showcase:check and regenerate.
- Docs:
docs/DOCKER_DESKTOP_EXTENSION.md (install, what is discovered, how connections are created, limits), a line in the README install table, .env.example untouched unless a new variable appears.
- Tests for anything that lands in
src/ (100% line coverage gate). Discovery mapping (image name to engine type and port) belongs in a unit-tested module, in the extension UI package or in src/lib, decided in the spike.
Milestones
- Spike (2 days):
docker extension init, read two existing full-UI extensions for the embedding pattern, decide A/B/C above, get the Studio visible inside the tab with a hard-coded connection.
- Discovery and connection creation: container list, image mapping, seed-file write, network attach or published-port rule.
- Listing quality: labels, icon in both themes, screenshots, detailed description, changelog,
docker extension validate green, multi-arch push, share link tested on macOS and Windows.
- Submission: the day the pause lifts. Watch
content/manuals/extensions/extensions-sdk/extensions/publish.md in docker/docs for the "paused" notice to disappear (monthly check is enough); the docs say to contact extensions@docker.com with questions.
Done when
docker extension validate passes on the pushed multi-arch image.
- On a Docker Desktop with a running
postgres and a running redis container, installing the extension and clicking each one opens it in the Studio with a working connection, in light and dark theme, on macOS arm64 and Windows amd64.
- The
docker extension share link shows the intended listing (title, icon, screenshots, description).
distribution/channels.yaml has the channel as pending, docs page exists, CI builds the image on tags.
- A note in this issue records the embedding and discovery decisions and the reason for each.
Sources
Why this, why now
Docker Desktop has an Extensions Marketplace with its own search and category filter. It lists 119 extensions today and the
databasecategory holds about six tools (Oracle SQLcl, Oracle SQL Developer Web, a SQL container manager, a pgAdmin derivative, Mongo Express, Redis Enterprise). Nothing covers more than one engine. A LibreDB Studio extension would be the only entry that opens PostgreSQL, MySQL, MongoDB, Redis, ClickHouse and the rest from one tab.The door is closed at the moment: since 2026-06-16 Docker has paused new Marketplace submissions "while Docker reviews Marketplace security" (publish docs and the
docker/extensions-submissionsREADME carry the same sentence; manual review is paused too). When it reopens, submission is automated: an extension that passes validation is live "within a few hours". This issue is about being ready on that day, with a validated, multi-arch, self-installable extension that we have already used ourselves.What we already have: the app image
ghcr.io/libredb/libredb-studiois published for linux/amd64 and linux/arm64 (306 MB compressed, 19 layers), first run is zero-config (admin credentials generated and printed), seed connections load from a YAML file (SEED_CONFIG_PATH, reloaded withSEED_CACHE_TTL_MS, see docs/SEED_CONNECTIONS.md), and the health endpoint isGET /api/db/health.What the extension does for a Docker Desktop user
Docker's design guidelines ask for exactly this shape: build the feature natively, do not send the user to a webpage or the CLI, support light and dark themes, use the Docker MUI theme, add a header for context.
Proposed architecture (decision points marked)
Location.
packaging/docker-extension/in this repo, next topackaging/windows/launcher. Own Dockerfile,metadata.json,compose.yaml,ui/. Image nameghcr.io/libredb/libredb-studio-docker-extension, versioned with the app. Decision: in-repo (recommended, shares CI and release tagging) or a separate repo.Two images, not one. The extension image holds only
metadata.json, the static UI and the labels. Itsvm.composefileruns the canonical app imageghcr.io/libredb/libredb-studio:<pinned version>as the backend service. The extension image stays tiny, the app image stays the single artifact we test and scan, and an app release only needs a version bump incompose.yaml.metadata.json.
{ "icon": "libredb-studio.svg", "ui": { "dashboard-tab": { "title": "LibreDB Studio", "root": "/ui", "src": "index.html" } }, "vm": { "composefile": "compose.yaml" } }No
hostsection: we ship no host binaries. Extensions run with the user's privileges (Docker's security page), so the smallest possible footprint is a feature.compose.yaml. The Studio service with a named volume for the SQLite storage layer (
STORAGE_PROVIDER=sqlite,STORAGE_SQLITE_PATHon the volume) so connections survive restarts, a second volume or path for the seed file,AUTH_COOKIE_SECURE=falsebecause traffic inside Desktop is plain HTTP, andNEXT_PUBLIC_AUTH_PROVIDER=local. Decision: whether to publish a host port (see embedding).Embedding the Studio UI. The dashboard tab is a static React page; the Studio is a Next.js server. Options:
ddClient.extension.vm.serviceover the socket. Fine for JSON calls, not for hosting a whole app.ddClient.host.openExternalto the browser. Works today, but the design guidelines explicitly discourage it. Keep as a fallback button only.Recommendation: A, verified against two existing extensions in the first spike.
Container discovery and connection creation. The UI has Docker access through the SDK:
ddClient.docker.listContainers()returns image, ports and networks. Map image names (postgres,mysql,mariadb,mongo,redis,clickhouse/clickhouse-server,trinodb/trino,apache/druid,elasticsearch,opensearchproject/opensearch,couchbase,mcr.microsoft.com/mssql/server,gvenzl/oracle-free,cassandra,ghcr.io/tursodatabase/libsql-server) to our engine type ids and default ports. Two ways to hand the result to the Studio:ddClient.extension.vm.cli.execto run a small script inside the Studio container that appends the connection to the seed YAML;SEED_CACHE_TTL_MSmakes it appear without a restart. No new app API.Recommendation: A for the first version; B only if A hits a wall.
Reaching the databases. The Studio runs as a container in the Desktop VM; user databases run on their own networks. If the database publishes a port,
host.docker.internal:<port>works. If it does not, attach the Studio container to that network (docker network connect) from the UI before creating the connection. Decision: support both, or require a published port in v1 and say so in the UI.Credentials. Zero-config first run generates the admin password; the extension UI must surface it once (or set
ADMIN_PASSWORDfrom a value the UI generates and stores in the volume). Decision in the spike.Marketplace requirements to bake in from day one
Required image labels (all in the extension Dockerfile; a missing one makes the extension invalid):
org.opencontainers.image.title,org.opencontainers.image.description,org.opencontainers.image.vendor,com.docker.desktop.extension.api.version(usedocker extension versionand set>= <that version>),com.docker.desktop.extension.icon(svg, visible on light and dark),com.docker.extension.screenshots(JSON array, recommended 2400x1600, hosted URLs),com.docker.extension.detailed-description,com.docker.extension.publisher-url(https://libredb.org),com.docker.extension.changelog(current version only).Recommended:
com.docker.extension.additional-urls(documentation, support, privacy policy),com.docker.extension.categoriesset todatabase,utility-tools. Without the categories label the extension does not appear in category filters.Screenshots and icon need stable public URLs; the repo's
public/or apackaging/docker-extension/assets/path served from GitHub raw is enough.Validation, local install and pre-Marketplace distribution
docker extension validate ./metadata.jsonbefore the image exists,docker extension validate <image>after.docker buildx build --push --platform=linux/amd64,linux/arm64for the extension image; Desktop refuses to install an image without the user's architecture.docker extension install <image>:<tag>on macOS arm64 and Windows amd64. Both need the Desktop setting "Allow only extensions distributed through the Docker Marketplace" turned off while the Marketplace is closed; the docs page on non-marketplace extensions shows the warning users will see.docker extension share <image>:<tag>produces a link that opens Desktop with a Marketplace-style preview. This is how we test the listing copy and screenshots before the Marketplace reopens, and how team members install it.Repo work that comes with it
.github/workflows/docker-build-push.ymlor add a sibling workflow). The validate step is the test; it fails on a missing label or an invalidmetadata.json.distribution/channels.yaml: adddocker-desktop-extensionwith statuspendingand this issue aslinks.tracking_issue;scripts/distribution-check.mjsvalidates the enums, and the login showcase is derived from this file, so runbun run channels:showcase:checkand regenerate.docs/DOCKER_DESKTOP_EXTENSION.md(install, what is discovered, how connections are created, limits), a line in the README install table,.env.exampleuntouched unless a new variable appears.src/(100% line coverage gate). Discovery mapping (image name to engine type and port) belongs in a unit-tested module, in the extension UI package or insrc/lib, decided in the spike.Milestones
docker extension init, read two existing full-UI extensions for the embedding pattern, decide A/B/C above, get the Studio visible inside the tab with a hard-coded connection.docker extension validategreen, multi-arch push, share link tested on macOS and Windows.content/manuals/extensions/extensions-sdk/extensions/publish.mdindocker/docsfor the "paused" notice to disappear (monthly check is enough); the docs say to contact extensions@docker.com with questions.Done when
docker extension validatepasses on the pushed multi-arch image.postgresand a runningrediscontainer, installing the extension and clicking each one opens it in the Studio with a working connection, in light and dark theme, on macOS arm64 and Windows amd64.docker extension sharelink shows the intended listing (title, icon, screenshots, description).distribution/channels.yamlhas the channel aspending, docs page exists, CI builds the image on tags.Sources