Make Blueprints Work With Docker And OpenShell
Blueprints often fail for boring environmental reasons: a path exists on macOS but not inside Docker, a port looks busy because Docker pre-published it, or a service health…
Make Blueprints Work With Docker And OpenShell
Blueprints often fail for boring environmental reasons: a path exists on macOS but not inside Docker, a port looks busy because Docker pre-published it, or a service health check points at the wrong side of a network boundary. This guide explains the boundaries and the patterns that avoid those failures.
Use it when a blueprint reads local files, opens ports, runs a web UI, calls a host service, or runs inside OpenShell.
The Mental Model
A blueprint launch can touch several different filesystems and networks:
| Place | What Runs There | What It Can See |
|---|---|---|
| Native OS | OtterDesk, mn-api, mn, validation before launch | Native files such as ~/docs, native localhost ports |
| Core Docker container | MirrorNeuron core, core-side validation, HostLocal workers in local installs | Submitted bundle under /tmp/bundle_*, payloads under payloads/, container localhost |
| HostLocal workspace | A copied payload directory for one agent | Only files copied from the submitted payload upload_path |
| OpenShell sandbox | Sandboxed worker command | Only uploaded sandbox files and explicitly allowed network/filesystem resources |
The mistake to avoid: never assume a path or port that worked in native validation will work in the runtime worker.
File Inputs
Static files that ship with a blueprint belong under payloads/. Runtime worker
commands should read them through paths relative to the node workdir.
User-selected native folders or files need staging. Do not pass a native path such
as ~/Downloads/tax2025 directly to a Docker or OpenShell worker.
Instead:
- Validate the native path before launch.
- Copy the selected files into the submitted payload.
- Rewrite the runtime config to point at the copied payload path.
- Make core-side validation accept the staged path.
For folder inputs, use the blueprint support local_inputs.folders contract in
config/default.json:
{
"tax_documents": {
"folder_path": ""
},
"local_inputs": {
"folders": [
{
"config_path": "tax_documents.folder_path",
"payload_path": "tax_workflow/mn_local_inputs/tax_documents",
"runtime_path": "mn_local_inputs/tax_documents",
"allowed_extensions": [".pdf", ".txt", ".json"],
"linked_config_paths": ["inputs.payload.document_folder"]
}
]
}
}payload_path is where launch preparation writes files in the submitted payload.
It must be inside the payload tree that the node uploads. If the node uses
upload_path: "tax_workflow" and its workdir is /sandbox/job/tax_workflow,
then payload_path should start with tax_workflow/.
runtime_path is what the worker reads after upload. With
workdir: "/sandbox/job/tax_workflow", the worker reads
mn_local_inputs/tax_documents.
Avoid hidden staging directories such as .mn_local_inputs. Some packaging,
copy, archive, or inspection tools skip dot-prefixed paths. Prefer
mn_local_inputs.
HostLocal Upload Paths
HostLocal copies files from the submitted payload into an agent workspace:
{
"runner_module": "MirrorNeuron.Runner.HostLocal",
"upload_path": "worker",
"upload_as": "worker",
"workdir": "/sandbox/job/worker",
"command": ["python3.11", "scripts/run_blueprint.py"]
}If a worker needs staged user files, the staged payload_path must be inside
worker/:
{
"payload_path": "worker/mn_local_inputs/documents",
"runtime_path": "mn_local_inputs/documents"
}If several nodes need the same staged input and each uploads a different directory, either put the staged files under each node's upload path or change the manifest to use a shared upload root. Do not rely on another node's workspace.
Validators
Input validation can run twice:
- API or CLI preflight, where native paths may exist.
- Core preflight, where only the submitted bundle and payloads exist.
Command validators should read MN_BLUEPRINT_CONFIG_JSON. If launch preparation
rewrites a native input to a staged runtime path, validators must understand both
forms.
Example validator resolver:
from pathlib import Path
def resolve_folder(config: dict, folder: str) -> Path:
native = Path(folder).expanduser()
if native.exists():
return native
for spec in (config.get("local_inputs") or {}).get("folders") or []:
if spec.get("runtime_path") != folder:
continue
payload_path = spec.get("payload_path")
if not payload_path:
continue
staged = Path("payloads") / payload_path
if staged.exists():
return staged
return nativeAlso watch blank initial inputs. Runtime messages can contain defaults such as
document_folder: "". Worker code should not let a blank runtime value override a
valid staged config path.
Ports And Web UIs
Docker-published ports look busy from the native host because Docker already owns the host listener. That is expected. A blueprint web UI is an explicit long-running service node, not a side process injected from configuration. Declare its command, port resource, service name, tags, and health check in the manifest:
{
"node_id": "example_web_ui",
"type": "stream",
"config": {
"runner_module": "MirrorNeuron.Runner.HostLocal",
"command": ["python3.11", "example_web_ui.py"]
},
"resources": {
"ports": [{"label": "web_ui", "port": 61000, "protocol": "http"}]
},
"services": [
{
"name": "example-web-ui",
"port": 61000,
"tags": ["web_ui", "blueprint", "example", "json-render"],
"checks": [{"name": "http-ready", "type": "http", "path": "/healthz"}]
}
]
}The blueprint is responsible for choosing a port that fits the deployment
contract. The runtime owns lifecycle and health supervision; it does not infer
or reserve a global dashboard port range from config.web_ui.
Localhost Means Different Things
localhost always means "this process's own network namespace."
| From | localhost Means | Use For Host Services |
|---|---|---|
| Native OS | The macOS/Linux/WSL host | localhost |
| Core Docker container | The core container | host.docker.internal on Docker Desktop, or a reachable host IP |
| OpenShell sandbox | The sandbox | Usually a service DNS name, gateway address, or explicit host mapping |
Examples:
- A dashboard URL shown to a browser on the native OS can be
http://localhost:61000. - A worker inside Docker calling an Ollama server on the host often needs
http://host.docker.internal:11434or the LAN IP. - A service health check should use the address that is reachable from the process running the check, not necessarily the public browser URL.
OpenShell Rules
OpenShell should be treated as a stricter runtime than HostLocal.
Put everything the sandbox must read under payloads/:
my_blueprint/
manifest.json
payloads/
worker/
scripts/run.py
policy.json
requirements.txtReference payload-local files in the manifest:
{
"runner_module": "MirrorNeuron.Sandbox.OpenShell",
"upload_path": "worker",
"sandbox_upload_path": "/sandbox/job",
"workdir": "/sandbox/job/worker",
"command": ["/usr/bin/python3", "scripts/run.py"]
}Do not pass native paths for policies, SSH keys, prompt files, model caches, or input data unless the OpenShell integration explicitly supports and validates that local path. Prefer copying those files into the payload.
OpenShell network access is also explicit. If a worker needs a model provider, web API, local service, or dashboard callback, document the required hostname and test it from inside the sandbox.
Service Discovery And Health Checks
If a blueprint starts a long-lived process, register it as a runtime service.
Do not rely only on log messages or a local *.json side file.
For blueprint-owned services:
- Give the service a stable name.
- Add tags such as
blueprint,<blueprint_id>, and a capability tag. - Put the public URL and run identity in
meta. - Add a health check that becomes passing only after the service is reachable.
- Let the service lifecycle follow the job or agent lifecycle.
Verify service discovery:
mn service show <blueprint-web-ui-service-name> --tag <blueprint_id>Expected output:
<blueprint-web-ui-service-name>For API clients, use:
curl http://127.0.0.1:54001/api/v1/services/<blueprint-web-ui-service-name>/resolutionDebug Checklist
When a blueprint works in validation but fails at runtime:
- Check whether the worker received the same config as validation.
- Check whether any blank
initial_inputsvalue overwrote a nonblank config value. - Check whether the needed file is inside the submitted payload path.
- Check whether the file is inside the node's
upload_path. - Check whether the path is visible from the node workdir.
- Avoid dot-prefixed staging directories.
- Remember core validation runs inside Docker for local Compose installs.
When a port fails:
- Check whether Docker is intentionally publishing the range.
- Check the manifest-declared port resource and active runtime services.
- Separate bind host, health-check address, and public browser URL.
Useful commands:
mn blueprint validate /path/to/bundle
mn blueprint run /path/to/bundle
mn service list --all
mn service show <blueprint-web-ui-service-name> --tag <blueprint_id>For a local development runtime, inspect the submitted bundle from the job
details. The manifest_ref.cache_path points at the bundle cache inside the core
container:
docker exec mirror-neuron-core find /tmp/mirror_neuron/bundle_cache -maxdepth 4 -type fDesign Checklist
Before declaring a blueprint ready:
- Native files are either packaged under
payloads/or declared throughlocal_inputs. - Runtime config paths are relative to the worker workdir.
- Validators work both before staging and after staging.
- Worker code reads
MN_BLUEPRINT_CONFIG_JSON. - Blank runtime inputs do not erase staged config paths.
- Long-lived web UIs are runtime-registered services.
- Web UI ports and health checks are explicit manifest declarations.
- OpenShell inputs, policies, and dependencies are payload-local.
- Host service URLs are tested from the runtime namespace that will call them.
Related Pages
Deployments
MirrorNeuron deployments provide versioned updates for long-running workloads. The design is inspired by Nomad rolling and canary deployments, but uses MirrorNeuron's job…
Reliability Guide
MirrorNeuron is a durable, retryable, message-driven runtime for local and federated AI systems. It is not a consensus workflow engine. Reliability is anchored to one job…