Skip to content

Docker Desktop extension: open every database container on the host in LibreDB Studio from one tab #560

Description

@cevheri

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

  1. Opens the Extensions tab, sees "LibreDB Studio".
  2. 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.
  3. 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.
  4. 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

  1. 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.
  2. Discovery and connection creation: container list, image mapping, seed-file write, network attach or published-port rule.
  3. 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.
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

deploymentPaaS / one-click deployment platform integrations and listingsdockerContainer base image dependenciesenhancementNew feature or request

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions