Overview
The Parmana stack consists of four services:
| Service | Image | Port (host) | Role |
|---|
postgres | postgres:16-alpine | 5433 | Audit database |
redis | redis:7 | 6380 | Replay protection store |
server | Built from packages/server/Dockerfile | 3000 | Governance runtime |
dashboard | ghcr.io/pavancharak/parmanasystems/dashboard:latest | 8081 | Audit 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
| Variable | Description |
|---|
POSTGRES_PASSWORD | Password for the Parmana database user |
PARMANA_API_KEY | Bearer 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
| Variable | Default | Description |
|---|
PORT | 3000 | Server listen port |
HOST | 0.0.0.0 | Server bind address |
CORS_ORIGIN | http://localhost:8081 | Allowed CORS origin |
PARMANA_POLICIES_ROOT | /app/policies | Path to compiled policy bundles |
PARMANA_TRUST_ROOT | /app/trust/trust-root.json | Trust root JSON |
PARMANA_TRUST_PUBLIC_KEY | /app/trust/root.pub | Trust root public key (PEM) |
PARMANA_RELEASE_MANIFEST | /app/artifacts/release-manifest.json | Release manifest |
PARMANA_RELEASE_SIGNATURE | /app/artifacts/release-manifest.sig | Release 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: