Local Adapter Development Setup¶
The canonical, published version of this guide lives on pq-web at
howto/enable-dev.html. This markdown is the in-repo reference for developers who have the monorepo.
This guide explains how to configure your PQ installation for local adapter development when running your adapter outside Docker Compose (e.g., in Visual Studio or Rider).
Prerequisites¶
- PQ installed via Docker Compose (
.install/compose/) - Your adapter project in Visual Studio / Rider
- Adapter connects to PQ via NATS and Internal API
The Problem¶
By default, PQ's Docker Compose configuration does not expose internal services externally:
- NATS (port 4222) — used for messaging between adapters and server
- Internal API (
/Internal/*) — used by adapters to fetch authorizations, device config, etc.
This is intentional for production security. Adapters running inside Docker Compose access these via internal network. But during development, your adapter runs on the host machine and needs external access.
Solution: Development Profile¶
PQ includes a dev profile that exposes the necessary ports and allows external API access.
Step 1: Start PQ with Dev Profile¶
cd .install/compose
# Start with dev profile (exposes NATS port)
INTERNAL_API_ALLOW_EXTERNAL=true docker compose --profile dev up -d
This does two things:
- Starts
nats-devservice that exposes NATS onlocalhost:4222 - Sets
InternalApi__AllowExternal=trueto allow/Internal/*API calls from your host machine
Step 2: Configure Your Adapter¶
Recommended — global dev config (one-time): run the helper script from the repo root:
It writes ~/.pq/adapter-dev.json with the NATS connection (localhost:4222, pq-master / pq-master) and sets OTEL_EXPORTER_OTLP_ENDPOINT so adapter logs flow to Grafana. The framework reads this file as a fallback (AdapterFramework.ApplyMqFallbackConfiguration) for any locally-run adapter that has no complete Mq config from a stronger source — so you configure NATS once instead of per adapter.
Alternative — per-adapter: set it explicitly in your adapter's appsettings.Development.json (this wins over the global fallback):
The server API URL is automatically provided during adapter registration, so you don't need to configure it manually.
Step 3: Run Your Adapter¶
Start your adapter from Visual Studio / Rider. It will:
- Connect to NATS at
localhost:4222 - Register with the server
- Receive API base URL in registration response
- Call
/Internal/Adapter/*endpoints for device config, authorizations, etc.
Branch B — Installed Box (No Repo)¶
If you only have an installer-deployed stack — no monorepo checkout — you don't have the dev compose profile or setup-adapter-dev-config.ps1. Use this path instead.
Step 1: Expose NATS¶
The deployed compose has no dev profile, so publish NATS with a standalone socat sidecar on the Docker host:
docker run -d --name nats-dev --network pq_default -p 4222:4222 alpine/socat:latest TCP-LISTEN:4222,fork,reuseaddr TCP:nats:4222
This does not survive docker compose down or a redeploy — re-run it afterward. It publishes NATS on 0.0.0.0 (LAN-reachable) using the shared pq-master / pq-master credentials.
Step 2: Internal API — normally nothing to do¶
A host-run adapter reaches /Internal/* through the nginx front door (port 5443) using the base URL and X-Api-Key it receives automatically at registration. The adapter's HTTP client bypasses TLS validation, so there's no CA setup either. On a standard installed box, /Internal/* is not IP-blocked from the host.
Only as a fallback — if /Internal/* returns HTTP 403 — does an admin need to set InternalApi__AllowExternal=true on the server service and restart it.
Step 3: Configure Your Adapter¶
There's no repo checkout to run setup-adapter-dev-config.ps1 from, so hand-write ~/.pq/adapter-dev.json:
Then set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 as a persistent user environment variable — the Alloy collector is always running, so no compose profile is needed to turn it on. Adapter logs appear in Grafana at https://localhost:5443/grafana/d/pq-home.
Security Note¶
The nats-dev sidecar publishes NATS on 0.0.0.0 with the shared pq-master / pq-master credentials — only run this on a development box you control. Never leave InternalApi__AllowExternal=true set outside of active troubleshooting.
Stopping Development Mode¶
# Stop all services
docker compose --profile dev down
# Or restart in production mode (no external access)
docker compose up -d
Security Notes¶
- Never use
--profile devin production — it exposes internal services - The
INTERNAL_API_ALLOW_EXTERNAL=trueflag bypasses IP-based protection on/Internal/*endpoints - In production, adapters run inside Docker Compose and access services via internal network
Troubleshooting¶
NATS Connection Refused¶
Make sure you started with --profile dev:
Verify NATS is exposed:
403 Forbidden on /Internal/* Endpoints¶
Make sure you set the environment variable:
Or restart with the variable:
docker compose --profile dev down
INTERNAL_API_ALLOW_EXTERNAL=true docker compose --profile dev up -d
Adapter Not Receiving Commands¶
- Check NATS connection in adapter logs
- Verify adapter registered successfully (look for
AdapterConfigurationResponsein logs) - Check that device is started in PQ UI
Architecture Overview¶
┌─────────────────────────────────────────────────────────┐
│ Docker Compose │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────┐ │
│ │ DB │ │ NATS │ │ Server │ │ nats-dev │ │
│ │ :5432 │ │ :4222 │ │ :5443 │ │ (profile) │ │
│ └─────────┘ └────┬────┘ └────┬────┘ └─────┬─────┘ │
│ │ │ │ │
│ internal network │ ports:4222 │
└────────────────────┼────────────┼──────────────┼────────┘
│ │ │
│ localhost:5443 localhost:4222
│ │ │
┌──────┴────────────┴──────────────┴──────┐
│ Host Machine │
│ ┌─────────────────────────────────┐ │
│ │ Your Adapter (Visual Studio) │ │
│ │ - Connects to localhost:4222 │ │
│ │ - Calls localhost:5443/Internal│ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘