npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@martijn-on-fhir/docker-desktop-mx

v1.34.0

Published

A desktop client for Docker in WSL, without Docker Desktop: containers, images, volumes, networks, compose and logs.

Readme

Docker Client MX

A desktop client for a Docker daemon: the everyday work of Docker Desktop (containers, images, volumes, networks, compose and logs) without Docker Desktop itself. It was made for Docker running in WSL on Windows, but it talks to any daemon you can reach.

The app does not manage the engine. The daemon already runs somewhere (a WSL distro, a Linux host, Colima, Docker Desktop); Docker Client MX is only the client.

The containers page, grouped by compose project, with live CPU and memory per container

Features

  • Dashboard: engine status, CPU/memory/network charts, disk usage, and a prune dialog.
  • Containers: grouped by compose project; start, stop, restart, pause and remove, also in bulk. The detail page has live logs, inspect, stats, a terminal, a file browser (download and upload by drag and drop) and a one-click diagnosis you can paste into an issue.
  • Images: pull with per-layer progress, run a container from an image, check the registry for newer versions, and recreate compose containers after an update.
  • Volumes and networks: list, remove and prune; back up a volume to a .tar and restore it.
  • Compose: projects as a group, with up and down.
  • Logs: the logs of all running containers merged in one view, with a level filter.
  • Terminal: a root shell on the Docker host itself, next to the per-container shells.
  • Claude (optional): with your own Claude API key, a Claude tab on every container and on the host terminal that reads logs and inspect data and suggests fixes or commands.
  • Docker contexts: switch between them from the status bar.
  • Command palette: Ctrl+K (Cmd+K on macOS) jumps to any page, container, image, volume or network.
  • Background: the app keeps running in the system tray and sends a notification when a container crashes or becomes unhealthy. It can start at login.
  • Updates: the Windows installer version updates itself.
  • Languages and themes: English (the default) and Dutch, chosen under Settings. Technical terms stay in English. Light and dark theme, toggled in the header.

Requirements

  • A running Docker daemon. The app finds it the same way the docker CLI does: DOCKER_HOST first, then the current docker context, then the platform default (npipe:////./pipe/docker_engine on Windows, /var/run/docker.sock elsewhere).
  • The docker CLI with the compose plugin, only for compose up and down. Everything else goes through the Engine API directly.
  • A Claude API key, only for the Claude tab.

Docker in WSL, without Docker Desktop

With Docker Engine installed inside a WSL distro, make the daemon listen on localhost as well as on its socket. In the distro, edit /etc/docker/daemon.json:

{ "hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2375"] }

On systemd distros, also remove the -H fd:// flag from the service, because it conflicts with hosts. Then restart Docker. On Windows, point a context at it:

docker context create wsl --docker host=tcp://localhost:2375
docker context use wsl

Port 2375 has no authentication, so bind it to 127.0.0.1 only, as above. If the distro is not running, the app says so and offers to start it.

A Docker host over SSH

A context with an ssh:// endpoint works too, like it does for the docker CLI. Add one under Settings → Servers (which also tests the connection and can trust a new host key after you compare its fingerprint), or with the CLI:

docker context create server --docker host=ssh://[email protected]

The app logs in and runs docker system dial-stdio on the server, so nothing has to listen on TCP there. What it needs:

  • The server in ~/.ssh/known_hosts. The app never accepts an unknown host key by itself: trust it under Settings → Servers after comparing the fingerprint, or connect once with ssh user@host. A changed host key is refused; that one you check with ssh-keygen -R.
  • A key without a passphrase prompt: the ssh agent (SSH_AUTH_SOCK, or the OpenSSH agent service on Windows), or ~/.ssh/id_ed25519, id_ecdsa or id_rsa without a passphrase. ~/.ssh/config is not read, so put the user name in the address.
  • The docker CLI on the server, and the user in its docker group.

Installing

  • Windows installer: download Docker-Client-MX-Setup.exe from the latest release on Codeberg. It is not code-signed, so SmartScreen asks for confirmation the first time.
  • npm: npx @martijn-on-fhir/docker-desktop-mx, or npm install -g @martijn-on-fhir/docker-desktop-mx and then docker-desktop-mx. The first start downloads Electron (about 100 MB). Starting at login and self-updating only work with the installer.
  • From source: see Development.

Using Claude

Open Settings → Claude and paste an API key from the Claude Console. The key is stored encrypted on this computer and is readable only by your user account. Each question costs a little API usage, billed to your own account.

The same screen picks the model: Opus 5 (the default) reasons best about a stubborn problem, Sonnet 5 and Haiku 4.5 answer faster and cost less.

Claude can only read: container logs, events, stats, inspect data, compose projects and, on the host terminal, what the terminal shows. Secrets such as passwords and tokens are masked before anything is sent. Claude never acts on its own. It can propose an action, such as a restart, which runs only when you click it. It can also propose a command, which goes into the terminal prompt; you decide whether to press Enter.

Development

Built with Angular 22 (standalone, zoneless, signals) in the renderer, Electron 44 as the shell, and dockerode in the main process. Needs Node 20 or later and a running daemon.

npm install
npm start

The original design notes are in docs/plan.md, and how releases work is in RELEASING.md. Both are in Dutch.

Scripts

| Command | What it does | | -------------------------- | ------------------------------------------------------------ | | npm start | builds the app and starts it, the way the installer does | | npm run dev | the same, with ng serve behind it so the renderer reloads | | npm run build | production build of the renderer | | npm run electron:compile | compiles electron/ and bundles the preload | | npm run electron:build | both builds, ready to run with electron . | | npm run electron:package | builds everything and makes an installer with electron-builder | | npm run lint | ESLint over TypeScript, the configs and the Angular templates | | npm run lint:fix | the same, with autofix | | npm test | renderer unit tests (Vitest + jsdom) | | npm run test:electron | main process unit tests (Vitest, node) | | npm run smoke | starts the built app and checks the whole chain end to end | | npm run release | semantic-release; runs in CI, not by hand | | npm run release:installer | builds the Windows installer and attaches it to the release |

The app only runs inside Electron. In a plain browser window.electron does not exist and the shell fails straight away, which is why there is no script that serves only the renderer.

Layout

electron/            main process (TypeScript, compiled to CommonJS)
  api.ts             the contract between main/preload and the renderer
  channels.ts        IPC channel names, without imports
  ipc.ts             the only place where channels are attached to ipcMain
  preload.ts         contextBridge, the renderer's only window into main
  main.ts            window, tray, notifications and app lifecycle
  docker-client.ts   daemon connection and readable errors
  i18n.ts            language of the texts main produces
  *.ts               one file per feature (containers, images, logs, exec, compose, ...)
  *.spec.ts          unit tests, kept out of dist-electron

src/
  styles.css         Tailwind 4 and the theme tokens
  app/core/          stores and services without UI of their own
  app/shell/         header, sidebar, status bar, table, dialogs, toasts, command palette
  app/pages/         one folder per route
  app/i18n/          the dictionaries: en/ is the source, nl/ follows the same shape
  testing/           the fake window.electron for renderer tests

scripts/smoke.js     starts the built app against a real daemon
.forgejo/workflows/  CI on every branch, release on main
eslint-rules/        two custom formatting rules

Adding a feature that needs main

Type it in electron/api.ts, attach the channel in electron/ipc.ts (validate the input with zod there), and add the wrapper in electron/preload.ts. Angular picks up the type through src/electron.d.ts (window.electron).

Translations

Every visible text in the renderer comes from src/app/i18n/. Add a key to the English file in en/, and the compiler requires the same key in nl/. A text that includes a number or a name is a function, so each language can handle plurals its own way. Texts made in the main process use t({ en: '…', nl: '…' }) from electron/i18n.ts. The tests run in Dutch (src/testing/setup.ts, electron/test-setup.ts). Each area also has tests that switch to English.

Theme

The tokens in src/styles.css are a --color-ink-* ramp, where a lower number means more contrast with the page, plus accent, ok, warn, danger and panel. :root[data-theme='dark'] overrides the same names, so every Tailwind utility follows the theme without template changes. src/main.ts sets the theme before Angular starts, which avoids a light flash with a dark theme.

Filters on the list pages and the log view toggles are remembered (app/core/preferences.ts). Search fields deliberately are not: a search term still active after a restart looks as if half your containers have gone.

Code style

The lint config enforces the house style:

  • lines up to 200 characters, and always braces;
  • blank lines around control statements, and one after the { of a function declaration;
  • parameters on one line as long as it fits within 150 characters.

Two custom rules for this live in eslint-rules/. There is deliberately no Prettier, because it conflicts with these rules. angular-eslint also lints the .html templates.

Commits follow conventional commits, which commitlint checks through .husky/commit-msg. The messages decide the version number: every push to main releases automatically.

Security

The window runs with contextIsolation: true, nodeIntegration: false and sandbox: true. The renderer has no Node access and no socket. Everything goes through contextBridge in electron/preload.ts, which never passes ipcRenderer itself along. Otherwise the renderer could call any channel instead of only the actions we meant to expose.

src/index.html sets a Content-Security-Policy that allows only the app's own scripts. Two consequences that fail silently if you forget them:

  • The preload must be one self-contained file. A sandboxed preload cannot load local modules, so electron:compile bundles it with esbuild.
  • inlineCritical is off in the production build. It adds an inline onload handler to the stylesheet, which the CSP blocks, and then part of the CSS is never applied.

npm run smoke guards both.

License

MIT, see LICENSE.