Service map
Exposed host ports: server → 3000, dashboard → 8081, postgres → 5433, redis → 6380.
In production, only the server port should be externally reachable (behind Nginx/TLS). Postgres and Redis must not be exposed.
Server container
Built from packages/server/Dockerfile. The server is a Fastify application that:
- Reads the signing key from environment or disk at startup
- Verifies that
policies/, trust/root.pub, trust/trust-root.json, and artifacts/release-manifest.json exist — exits immediately if any are missing
- Connects to Redis (required) and Postgres (optional but strongly recommended)
- Registers all routes and begins listening
The server process does not fork, does not use worker threads, and does not cache decisions in memory. All state lives in Redis (replay) and Postgres (audit).
What the server does on POST /execute
- Validates the request body —
executionId, policyId, policyVersion, signals all required
- Loads and verifies the signed policy bundle from
PARMANA_POLICIES_ROOT
- Normalizes and validates signals against the policy’s
signalsSchema
- Evaluates the policy rules to produce a decision
- Computes
execution_fingerprint = SHA-256 of canonical { policyId, policyVersion, signals }
- Reserves the
executionId in Redis (RESERVED state) — throws replay error if already present
- Signs the governance token with the Ed25519 signing key
- Transitions Redis state to
EXECUTING, then to CONFIRMED
- Records the attestation in Postgres
- Returns the attestation to the caller
If any step fails, Redis state transitions to FAILED and a 422 is returned.
Redis container
Redis 7 with AOF persistence enabled. Used exclusively for replay protection.
The replay store maintains a state machine per executionId:
null → RESERVED → EXECUTING → CONFIRMED
→ OVERRIDDEN
→ FAILED
→ EXPIRED
Redis keys are prefixed with parmana:replay:. Reservation TTL defaults to 300 seconds. Failed state TTL defaults to 30 seconds.
Redis is required. The server will not start without a reachable REDIS_URL.
Postgres container
Postgres 16 with a named volume for persistence. Used by @parmanasystems/audit-db to store:
| Table | Contents |
|---|
audit_decisions | One row per executed decision |
audit_overrides | Override records (approved and rejected) |
audit_verifications | Verification results |
audit_security_events | Security-relevant events (replay attempts, signature failures) |
audit_api_calls | API call log |
Postgres is initialized on first start using POSTGRES_DB, POSTGRES_USER, and POSTGRES_PASSWORD. Schema migrations run automatically when the server connects.
Postgres is optional in the sense that the server will start without it, but health.audit_db will be false and all /audit/* routes will return 503.
Dashboard container
A static React application served by Nginx. The dashboard makes API calls directly from the browser to the server’s /audit/* routes.
The dashboard has no backend of its own. All data comes from the server.
Port: 8081 (host) → 80 (container)
The dashboard connects to the server from the browser, not from within Docker. CORS_ORIGIN on the server must be set to the dashboard’s origin (e.g., http://localhost:8081).
Networking
All four services share the parmana-net bridge network. Services communicate using their compose service names as hostnames:
- Server → Postgres:
postgres:5432
- Server → Redis:
redis:6379
The dashboard communicates with the server from the browser, using the host-mapped port localhost:3000.
Volumes
| Volume | Contents | Backup priority |
|---|
postgres_data | All audit records, decisions, verifications, security events | Critical |
| Redis data (ephemeral by default) | Replay state only — transient | Low — replay state recovers after TTL |
Policy bundles, trust root, and release manifest are baked into the server image or mounted from the host. They are not stored in Docker volumes.
Image sources
| Service | Image |
|---|
postgres | postgres:16-alpine (Docker Hub) |
redis | redis:7 (Docker Hub) |
server | Built locally from packages/server/Dockerfile |
dashboard | ghcr.io/pavancharak/parmanasystems/dashboard:latest (GitHub Container Registry) |
In production, pin the server image to a specific release tag: ghcr.io/pavancharak/parmanasystems/server:${RELEASE_TAG}.