Signals DPG Setup
The Signals DPG is the network-aware backend (API + UI). It runs standalone —
no other DPG required. Its local-setup/ brings up everything it depends on:
Postgres, Redis, Keycloak and Mailpit — plus, optionally,
signals-search for relevance ranking.
- Repository: Blue-Dots-Economy/signals-dpg
- Canonical local guide:
local-setup/LOCAL_SETUP.md— the self-containedlocal-setup/folder is the source of truth for running locally.
Pick a track: A — Docker-only (fastest, one command) or B — hybrid dev (run the API/UI from source with hot-reload).
Track A — one command (Docker)
Section titled “Track A — one command (Docker)”git clone https://github.com/Blue-Dots-Economy/signals-dpg.gitcd signals-dpg/local-setupdocker login dhi.io # app images build FROM dhi.iocp .env.example .env # ships working dev values for both secrets# Rotate them in place (macOS/BSD sed; on Linux drop the '' after -i).sed -i '' "s|^INSTANCE_SHARED_SECRET=.*|INSTANCE_SHARED_SECRET=$(openssl rand -hex 32)|" .envsed -i '' "s|^SIGNALS_PII_KEY=.*|SIGNALS_PII_KEY=$(openssl rand -base64 32)|" .envdocker compose --profile keycloak up -d --buildThis builds a Postgres image with pgvector + PostGIS (the extensions
db:init needs), applies the schema, starts Keycloak and imports the realm, then
starts the API and UI.
The --profile keycloak flag is required: Keycloak, Mailpit and the realm-import
job are profiled services, so a plain docker compose up -d skips them and login
will not work.
There is a second profile, search, which adds relevance-ranked discover and
match scores. Without it the stack works but returns results in recency order —
see Adding search below. Profiles combine:
docker compose --profile keycloak --profile search up -d --build| Open this | URL |
|---|---|
| Signals UI | http://localhost:5173 (must be :5173 — CORS) |
| Signals API | http://localhost:2742 (/api/reference = Swagger) |
| Keycloak | http://localhost:8080 (login screen + admin console) |
| Mailpit | http://localhost:8025 (catches login OTP emails) |
| Signals Search | http://localhost:3100 (only with --profile search) |
Track B — hybrid dev (hot-reload)
Section titled “Track B — hybrid dev (hot-reload)”Run the backing services from local-setup/, then the API + UI from source:
cd signals-dpg/local-setup && cp .env.example .envsed -i '' "s|^INSTANCE_SHARED_SECRET=.*|INSTANCE_SHARED_SECRET=$(openssl rand -hex 32)|" .envdocker compose --profile keycloak up -d \ postgres redis keycloak keycloak-init mailpit # backing services only
cd .. # repo rootpnpm installcp .env.example .env # point at the Docker DB/Redis (see the guide)# Root .env is a separate file from local-setup/.env — rotate its secrets too:# SIGNALS_PII_KEY=<openssl rand -base64 32> # must decode to exactly 32 bytessed -i '' "s|^SIGNALS_PII_KEY=.*|SIGNALS_PII_KEY=$(openssl rand -base64 32)|" .envpnpm db:push:api && pnpm db:init:api # schema + extensions/tablespnpm dev:api # API on :2742 (terminal 1)pnpm dev:ui # UI on :5173 (terminal 2)Full env values, resets, and troubleshooting are in the
local-setup/LOCAL_SETUP.md guide.
Because the model is schema-driven,
you add item types and forms through network.json schemas rather than code.
link: /guides/installation/local-setup/local-stack/ label: “Path 8 of 10: Local Stack”
Adding search (relevance ranking)
Section titled “Adding search (relevance ranking)”Out of the box, discover returns results in recency order and match scores
are unavailable — the UI shows “Showing basic matches — relevance ranking is
temporarily unavailable”. Adding the search profile fixes both:
cd signals-dpg/local-setupcp .env.search.example .env.search # then mint an apikey (below)docker compose --profile keycloak --profile search up -dThis adds the search query API on :3100, an ingestion worker, and a TEI
embedding server running BAAI/bge-m3 — the same model production uses, baked
into the image so nothing downloads it at runtime. All images are pulled from
public GHCR; no extra checkout, no registry login.
Two required steps that are easy to miss:
- Mint an apikey. Search authenticates against the
apikeytable in the shared Signals database — there is no API-key environment variable. Rundocker compose run --rm signals-bootstrap sh -lc "pnpm --filter api db:seed:services", which prints ansk_signals_…key on first run only, and put the raw value in.env.search. - Set both variable sets. Relevance-ranked discover reads
SIGNALS_SEARCH_URL; match score readsSIGNALS_SEARCH_ENDPOINT— the same URL under a different name — plusMATCH_SCORE_PROVIDER=signals_search. Setting only one leaves the other silently broken.
/health on the search API is unauthenticated and checks neither the database
nor the embedder, so a 200 from it does not mean the stack can answer a query.
Test with a real POST /v1/search carrying x-api-key.
Full reference — the embedding-dimension rules, network.json requirements and a
troubleshooting table keyed by the exact error message — is §7 of
local-setup/LOCAL_SETUP.md.

