Local package -> Docker image workflow¶
This guide walks through building Node Wire packages locally (as wheels) and using those wheels to build the Docker images in docker/.
The Dockerfiles in this repo install local wheel artifacts from packages/**/dist/*.whl, so you must build wheels first.
Prerequisites¶
- Python 3.11, 3.12 or 3.13 available in your shell
- Docker installed and running
- Build tooling installed:
Run all commands from the repository root. The build context must be the repository root (.) so the Dockerfiles' COPY src/ and COPY config/ resolve.
Build wheel packages locally¶
Build the wheels for every package (runtime, bindings, ToolHive and the connectors):
Build only specific packages (faster when iterating):
bash scripts/build-packages.sh \
packages/runtime \
packages/connectors/smtp \
packages/connectors/stripe
The script (scripts/build-packages.sh):
- builds one pure-Python (
py3-none-any) wheel per package, which installs in the images and on your machine alike, - writes artifacts under each package's
dist/folder, - fails if a wheel is not
py3-none-anyor contains compiled.so/.pydfiles.
Confirm wheel artifacts exist¶
Quick check (example for SMTP):
ls packages/runtime/dist/*.whl
ls packages/connectors/smtp/dist/*.whl
ls packages/connectors/stripe/dist/*.whl
If ls fails, rebuild that package before continuing.
Build Docker images from local wheels¶
Build all MCP connector images¶
Run the build script:
Each image is tagged latest and the version, which defaults to the one in pyproject.toml. Pass --version to override it:
The script builds the following images:
| Image | Dockerfile |
|---|---|
nw-google-drive |
docker/google-drive/Dockerfile |
nw-smartonfhir-epic |
docker/fhir-epic/Dockerfile |
nw-smartonfhir-cerner |
docker/fhir-cerner/Dockerfile |
nw-smtp |
docker/smtp/Dockerfile |
nw-stripe |
docker/stripe/Dockerfile |
nw-salesforce |
docker/salesforce/Dockerfile |
nw-slack |
docker/slack/Dockerfile |
Build one image manually¶
Build a single image with docker build:
Run them with Docker Compose¶
docker-compose.mcp.yml starts every MCP server as a stdio container, which is useful for local
validation before configuring ToolHive. It needs the images built and your .env populated with
the credentials of the connectors you run. Each service pins NW_ALLOWED_CONNECTORS to its own
connector, so a broad value in .env does not make a per-connector image import optional
dependencies it does not contain. To start every server, or a single one, run:
docker compose -f docker-compose.mcp.yml up --build
docker compose -f docker-compose.mcp.yml up --build nw-smartonfhir-epic # one server only
To register these images in ToolHive, see the ToolHive agent scenario.
Wheel requirements by image¶
Each Dockerfile expects specific wheel files to exist in dist/. Keep this table in sync with the Dockerfiles in docker/ — add a row here whenever you add a Tier 3 standalone MCP image (see the ship checklist).
| Image | Required wheels |
|---|---|
docker/smtp/Dockerfile |
packages/runtime/dist/*.whl, packages/connectors/smtp/dist/*.whl |
docker/google-drive/Dockerfile |
packages/runtime/dist/*.whl, packages/connectors/google_drive/dist/*.whl |
docker/fhir-epic/Dockerfile |
packages/runtime/dist/*.whl, packages/connectors/fhir_epic/dist/*.whl |
docker/fhir-cerner/Dockerfile |
packages/runtime/dist/*.whl, packages/connectors/fhir_cerner/dist/*.whl |
docker/stripe/Dockerfile |
packages/runtime/dist/*.whl, packages/connectors/stripe/dist/*.whl |
docker/salesforce/Dockerfile |
packages/runtime/dist/*.whl, packages/connectors/salesforce/dist/*.whl |
docker/slack/Dockerfile |
packages/runtime/dist/*.whl, packages/connectors/slack/dist/*.whl |
Dockerfile (unified MCP server) |
runtime + connector wheels (http_generic, stripe, smtp, slack, google_drive, fhir_epic, fhir_cerner, salesforce) |
Common failures and fixes¶
COPY ... dist/*.whl failed: no source files were specified¶
A required wheel is missing. Re-run scripts/build-packages.sh for the missing package(s), then rebuild the image.
Docker build cannot find src/ or config/¶
Use repo root as build context (.):
Do not run docker build from inside docker/<name>/.
Docker daemon not running¶
Start Docker Desktop (or daemon) and retry package/image builds.
Recommended local loop¶
A typical iteration rebuilds the changed packages, then the image, then checks it:
# 1) Rebuild changed packages
bash scripts/build-packages.sh packages/runtime packages/connectors/smtp
# 2) Build image(s)
docker build -f docker/smtp/Dockerfile -t nw-smtp:local .
# 3) Verify image exists
docker images --filter reference=nw-smtp
Related docs¶
- Packaging and publishing — package inventory and release process
- ToolHive agent scenario — register the images in ToolHive