Skip to main content

Overview

The Parmana stack consists of four services:
ServiceImagePort (host)Role
postgrespostgres:16-alpine5433Audit database
redisredis:76380Replay protection store
serverBuilt from packages/server/Dockerfile3000Governance runtime
dashboardghcr.io/pavancharak/parmanasystems/dashboard:latest8081Audit UI
Redis is required. The server throws [SYS-REPLAY-001] on startup if REDIS_URL is not set or Redis is unreachable. Replay protection cannot be disabled.

The docker-compose.yml

services:

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: Parmana_audit
      POSTGRES_USER: Parmana
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5433:5432"
    restart: unless-stopped
    networks:
      - parmana-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U Parmana -d Parmana_audit"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  redis:
    image: redis:7
    command:
      - redis-server
      - --appendonly
      - yes
    ports:
      - "6380:6379"
    restart: unless-stopped
    networks:
      - parmana-net

  server:
    build:
      context: .
      dockerfile: packages/server/Dockerfile
    volumes:
      - /secure/parmana:/secure/parmana:ro
    environment:
      PORT: 3000
      HOST: 0.0.0.0
      CORS_ORIGIN: http://localhost:8081
      REDIS_URL: redis://redis:6379
      AUDIT_DATABASE_URL: postgresql://Parmana:${POSTGRES_PASSWORD}@postgres:5432/Parmana_audit
      PARMANA_API_KEY: ${PARMANA_API_KEY}
      PARMANA_SIGNING_PROVIDER: ${PARMANA_SIGNING_PROVIDER}
      PARMANA_SIGNING_PRIVATE_KEY_PATH: ${PARMANA_SIGNING_PRIVATE_KEY_PATH}
      PARMANA_SIGNING_PUBLIC_KEY_PATH: ${PARMANA_SIGNING_PUBLIC_KEY_PATH}
      PARMANA_POLICIES_ROOT: /app/policies
      PARMANA_TRUST_ROOT: /app/trust/trust-root.json
      PARMANA_TRUST_PUBLIC_KEY: /app/trust/root.pub
      PARMANA_RELEASE_MANIFEST: /app/artifacts/release-manifest.json
      PARMANA_RELEASE_SIGNATURE: /app/artifacts/release-manifest.sig
    ports:
      - "3000:3000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - parmana-net

  dashboard:
    image: ghcr.io/pavancharak/parmanasystems/dashboard:latest
    ports:
      - "8081:80"
    depends_on:
      server:
        condition: service_started
    restart: unless-stopped
    networks:
      - parmana-net

networks:
  parmana-net:
    driver: bridge

volumes:
  postgres_data:

Environment variables

Create a .env file at the repository root. The compose file reads from it automatically.

Required

VariableDescription
POSTGRES_PASSWORDPassword for the Parmana database user
PARMANA_API_KEYBearer token required on all API requests

Signing key — choose one option

Option A — Key files on the host:
PARMANA_SIGNING_PROVIDER=disk
PARMANA_SIGNING_PRIVATE_KEY_PATH=/secure/parmana/private.pem
PARMANA_SIGNING_PUBLIC_KEY_PATH=/secure/parmana/public.pem
The host path /secure/parmana is mounted read-only into the container at /secure/parmana. Adjust the volume path in docker-compose.yml to match your actual key location. Option B — Keys in environment:
PARMANA_SIGNING_PROVIDER=env
PARMANA_SIGNING_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\n...
PARMANA_SIGNING_PUBLIC_KEY=-----BEGIN PUBLIC KEY-----\n...
Do not put private key material in .env files committed to version control. For production, use Docker secrets or a secrets manager. See Production Deployment.

Optional with defaults

VariableDefaultDescription
PORT3000Server listen port
HOST0.0.0.0Server bind address
CORS_ORIGINhttp://localhost:8081Allowed CORS origin
PARMANA_POLICIES_ROOT/app/policiesPath to compiled policy bundles
PARMANA_TRUST_ROOT/app/trust/trust-root.jsonTrust root JSON
PARMANA_TRUST_PUBLIC_KEY/app/trust/root.pubTrust root public key (PEM)
PARMANA_RELEASE_MANIFEST/app/artifacts/release-manifest.jsonRelease manifest
PARMANA_RELEASE_SIGNATURE/app/artifacts/release-manifest.sigRelease manifest signature

Required directory structure

The server verifies these paths exist on startup (relative to the container’s working directory /app):
/app/
  policies/                     ← compiled policy bundles
  trust/
    root.pub                    ← trust root public key (PEM)
    trust-root.json             ← trust root metadata
  artifacts/
    release-manifest.json       ← release manifest
If any of these are missing, the server exits immediately with an error listing the missing path.

Commands

# Start all services in the background
docker compose up -d

# View server logs
docker compose logs server -f

# View all logs
docker compose logs -f

# Stop all services (preserve volumes)
docker compose down

# Stop and remove all volumes (deletes all audit data)
docker compose down -v

# Restart only the server
docker compose restart server

# Rebuild the server image after code changes
docker compose up -d --build server

Verifying the stack

# Runtime health
curl http://localhost:3000/health | jq .

# Runtime manifest (version, capabilities)
curl http://localhost:3000/runtime/manifest | jq .

# Audit statistics
curl http://localhost:3000/audit/stats \
  -H "Authorization: Bearer $PARMANA_API_KEY" | jq .

Troubleshooting

Server exits immediately on startup
docker compose logs server --tail=50
Common causes:
  • [SYS-REPLAY-001] REDIS_URL is required — Redis container not up yet, or REDIS_URL not set
  • Parmana server must be started from repository root. Missing: /app/policies — policy directory not present in the built image
  • [SYS-KEY-001] — signing key not found at the configured path
Postgres not accepting connections
docker compose logs postgres --tail=20
docker compose exec postgres pg_isready -U Parmana -d Parmana_audit
If pg_isready fails, POSTGRES_PASSWORD in .env may not match the password used when the volume was first initialized. Remove the volume and restart:
docker compose down -v
docker compose up -d
Dashboard shows “Cannot connect to server” The dashboard connects to the server from the browser, not from within Docker. The browser must be able to reach http://localhost:3000. Verify the server port is exposed and CORS_ORIGIN is configured for http://localhost:8081. Port already in use Modify the host port in docker-compose.yml. The container ports stay the same. For example, to move the server to port 3100:
ports:
  - "3100:3000"