> ## Documentation Index
> Fetch the complete documentation index at: https://docs.manthan.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Container Architecture

> How the four Docker services relate to each other and to the governance chain

## Service map

```mermaid theme={null}
flowchart TB
    Client["Client\n(browser / SDK / curl)"]

    subgraph Docker["Docker network: parmana-net"]
        Server["server:3000\nFastify governance runtime"]
        Postgres["postgres:5432\nAudit database"]
        Redis["redis:6379\nReplay protection store"]
        Dashboard["dashboard:80\nAudit UI (nginx)"]
    end

    Client -->|"HTTP :3000"| Server
    Client -->|"HTTP :8081"| Dashboard
    Dashboard -->|"API calls via browser"| Server
    Server -->|"AUDIT_DATABASE_URL"| Postgres
    Server -->|"REDIS_URL"| Redis
```

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:

| 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)

<Note>
  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`).
</Note>

***

## 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}`.
