Skip to main content

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:
  1. Reads the signing key from environment or disk at startup
  2. Verifies that policies/, trust/root.pub, trust/trust-root.json, and artifacts/release-manifest.json exist — exits immediately if any are missing
  3. Connects to Redis (required) and Postgres (optional but strongly recommended)
  4. 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

  1. Validates the request body — executionId, policyId, policyVersion, signals all required
  2. Loads and verifies the signed policy bundle from PARMANA_POLICIES_ROOT
  3. Normalizes and validates signals against the policy’s signalsSchema
  4. Evaluates the policy rules to produce a decision
  5. Computes execution_fingerprint = SHA-256 of canonical { policyId, policyVersion, signals }
  6. Reserves the executionId in Redis (RESERVED state) — throws replay error if already present
  7. Signs the governance token with the Ed25519 signing key
  8. Transitions Redis state to EXECUTING, then to CONFIRMED
  9. Records the attestation in Postgres
  10. 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:
TableContents
audit_decisionsOne row per executed decision
audit_overridesOverride records (approved and rejected)
audit_verificationsVerification results
audit_security_eventsSecurity-relevant events (replay attempts, signature failures)
audit_api_callsAPI 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

VolumeContentsBackup priority
postgres_dataAll audit records, decisions, verifications, security eventsCritical
Redis data (ephemeral by default)Replay state only — transientLow — 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

ServiceImage
postgrespostgres:16-alpine (Docker Hub)
redisredis:7 (Docker Hub)
serverBuilt locally from packages/server/Dockerfile
dashboardghcr.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}.