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

docker-mdns-bridge

v0.1.0

Published

Relay a Docker container's Bonjour/mDNS advertisements onto your real LAN — no host networking, no macvlan.

Readme

docker-mdns-bridge

Relay a Docker container's Bonjour/mDNS advertisements onto your real LAN — without host networking, macvlan, or any Docker networking mode changes.

Background

This exists for situations like fault-finding a smart speaker's poor Bonjour/AirPlay discoverability on the LAN, or developing a Homebridge plugin against a real running instance — cases where you want to test against a container without installing anything bare-metal on the host just to debug a networking quirk. Docker is the obvious way to keep that disposable, but Docker Desktop on Mac is exactly where mDNS discovery breaks.

The problem

Docker Desktop (Mac and Windows) runs containers inside a hidden VM whose network is NAT'd to the host. Published ports (-p 8080:8080) work fine — but IP multicast, which mDNS/Bonjour/Avahi depends on for service discovery, never crosses that NAT boundary. A service advertised inside a container (Homebridge, a printer daemon, a Chromecast-style receiver, anything using Bonjour/Avahi for discovery) is invisible to other devices on your LAN, even though its TCP port is reachable fine once you know the address.

macvlan/--network host are either unsupported or unreliable on Docker Desktop for Mac — there's no host-side network interface for them to attach to.

The fix

This tool periodically asks the container (via docker exec ... avahi-browse) what it's advertising, and re-publishes an equivalent mDNS record from the host's own network stack — dns-sd — pointed at the container's already-published port. Other devices on the LAN discover the host's advertisement and connect over the normal published TCP port; the container never needs multicast access at all.

It keeps re-syncing on an interval, so:

  • TXT record changes (pairing state, config versions, anything that mutates at runtime) are picked up automatically.
  • Services appearing or disappearing inside the container are reflected on the host.
  • A stale record is deregistered cleanly on exit (Ctrl+C / SIGTERM).

Installation

npm install -g docker-mdns-bridge

Or run it directly with npx docker-mdns-bridge ....

Usage

docker-mdns-bridge --container <name> [options]

| Option | Description | |---|---| | -c, --container <name> | Docker container to read advertisements from (required) | | -f, --filter <pattern> | Only bridge services whose name or type matches (repeatable; */? wildcards; default: bridge everything) | | -x, --exclude <pattern> | Never bridge services whose name or type matches (repeatable; */? wildcards) | | -i, --interval <secs> | Re-sync interval in seconds (default: 5) | | --once | Sync once and exit, instead of running continuously | | --dry-run | Print what would be published, without publishing anything | | -v, --verbose | Log every poll cycle, not just changes |

Example: bridging a Homebridge container

docker-mdns-bridge --container my-homebridge --filter _hap._tcp --verbose

Your phone's Home app (or any HAP controller) will then discover the accessory advertised under your Mac's own hostname, on the container's mapped HAP port.

Example: bridge everything except noisy sshd advertisements

docker-mdns-bridge --container my-container --exclude '*ssh*' --verbose

Example: bridge everything, once, to see what's there

docker-mdns-bridge --container my-homebridge --once --dry-run --verbose

Requirements

  • Docker CLI on the host, with the target container running.
  • avahi-browse inside the container. If missing, this tool tries to install it automatically (avahi-utils via apt-get, avahi-tools via apk/yum/dnf) — if none of those package managers are present, install it yourself in the image and re-run.
  • dns-sd on the host (built in to macOS, no install needed). macOS is the only supported host platform.
  • Node.js >= 24 (current LTS), no other dependencies.

[!WARNING] Running this tool against a container that doesn't already have avahi-browse installed will run apt-get/apk/yum/dnf inside your already-running container via docker exec, installing avahi-utils/avahi-tools directly into its live filesystem. This mutates the running container, not its image: it doesn't touch your Dockerfile, won't survive the container being removed and recreated, and adds to that container's writable layer on disk for as long as it lives. If you'd rather this tool not modify the container at all, bake avahi-utils/avahi-tools into your image yourself ahead of time — nothing is installed once avahi-browse is already present.

How it works

  1. docker exec <container> avahi-browse -a -r -p -t -k dumps every currently-advertised service the container's Avahi daemon knows about, in parsable (-p) form — always every type; --filter/--exclude narrow the results afterwards, not the avahi-browse invocation itself. -k/--no-db-lookup is mandatory, not cosmetic: without it, avahi-browse's browse-all mode silently substitutes well-known type strings with a human-friendly description from its local database (e.g. _ssh._tcp becomes SSH Remote Terminal) — republishing that string as the actual mDNS type would produce a broken advertisement, so -k is always passed to keep the real type string intact.
  2. Each resolved record is parsed into { name, type, domain, port, txt }, unescaping Avahi's \DDD decimal escapes.
  3. --filter/--exclude patterns (if given) are applied to the parsed list, matching against each service's name or type.
  4. For each unique remaining service (by name+type+domain), a signature is computed from its port and TXT record. If it's new or the signature changed since the last poll, the previous publish process (if any) is killed and a new one spawned: dns-sd -R "<name>" <type> <domain> <port> <k=v> ...
  5. Any previously-published service that no longer appears in the (filtered) results is deregistered.
  6. On exit, every spawned publish process is killed, removing the host-side records.

A transient failure to reach the container (e.g. it's mid-restart) is treated as "unknown", not "gone" — the last-known-good record stays published rather than flapping on every blip.

Development

Written in TypeScript (^7.0.2). The CLI logic is split into small, independently testable modules under src/: types.ts (shared ParsedService/PublishHandle types), parse.ts (Avahi output parsing/escaping), filter.ts (--filter/--exclude glob matching), docker.ts (docker exec/avahi-browse plumbing), publish.ts (host-side dns-sd backend), cli.ts (argument parsing, via commander), and sync.ts (the diff/publish engine, with browse/publish injected so it can be tested without touching Docker or a real mDNS stack). src/main.ts wires the real implementations together and is the package's bin entry: package.json points straight at its compiled output, dist/main.jsnpm link/npm install make it executable automatically.

npm install
npm run build          # tsc -> dist/
npm test               # unit tests (fast, no Docker required)
npm run test:integration   # integration tests (needs Docker; pulls real images)
npm run docs           # generates HTML API docs into docs/ via TypeDoc

Tests live under src/__tests__/{unit,integration}/ and run via Node's native stripTypeScriptTypes (see jest.strip-types-transformer.cjs) rather than ts-jest — TypeScript 7 ("tsgo") is a from-scratch Go rewrite that doesn't expose the in-process JS Compiler API ts-jest relies on. tsconfig.json enforces erasableSyntaxOnly, so nothing here needs real type-directed codegen (no enums, no parameter properties); type-checking itself happens via npm run build, not the test run. The docs script pins a [email protected]/typedoc pair via npx for the same reason — TypeDoc also needs that Compiler API.

Integration tests (src/__tests__/integration/) use Testcontainers to spin up real, well-known Bonjour-advertising software — not a synthetic fixture — and exercise this tool's browse/parse path against their actual output:

  • homebridge/homebridge — advertises _hap._tcp; also exercises the apt-get auto-install path for avahi-browse.
  • mikebrady/shairport-sync — advertises _raop._tcp/_airplay._tcp (AirPlay audio); exercises the apk auto-install path. Its always-running sshd (_ssh._tcp/_sftp-ssh._tcp) also verifies the -k friendly-name substitution described above, and that --exclude can silence that kind of noise.

Limitations

  • This only solves discovery. The container's port must already be published normally (docker run -p <port>:<port>) for the host's advertisement to actually be reachable.
  • TXT records containing binary/non-UTF8 data are not supported (Avahi's parsable output is text).
  • One mDNS domain is supported per run: local (the standard LAN discovery domain).

License

MIT