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

# Self hosted configuration reference

> Every setting, file, service, volume and port of the self hosted deployment, with defaults and exact behavior.

Everything on this page is defined in `docker-compose.yml` and `docker/local/` in the repository.

## Settings

Set these in the shell before running `docker compose`, or in a file named `.env` in the repository root. Compose reads that file only to fill in these values. Nothing else in it reaches the containers.

| Variable | Default | When it is read | Meaning |
| - | - | - | - |
| `PARMANA_DB_PASSWORD` | `parmana-local` | Every start | Password of the database user `parmana`. Postgres stores it when the database is first created. To change it later, follow [Change the database password](/self-hosted/operations#change-the-database-password). |
| `PARMANA_BIND` | `127.0.0.1` | Every start | Host address the API is published on. The default accepts connections from the same machine only. |
| `PARMANA_PORT` | `3000` | Every start | Host port the API is published on. Inside Docker the API always listens on `3000`. |
| `PARMANA_LOCAL_ALLOWED_CAPABILITIES` | `paytm:refund` | First start only | Comma separated capabilities the first API key may use. Ignored once `parmana-local/api-keys.json` exists. Use [API keys](/self-hosted/api-keys) to change keys later. |
| `PARMANA_LOCAL_CALLER_ID` | `local-operator` | First start only | Caller ID of the first API key. Ignored once `parmana-local/api-keys.json` exists. |

<Warning>
  Set `PARMANA_DB_PASSWORD` to your own value **before the first start**. The
  default is public.
</Warning>

<Warning>
  Setting `PARMANA_BIND` to anything other than `127.0.0.1` publishes the API
  over plain HTTP. Put a TLS terminating proxy in front of it first. The API key
  travels in every request.
</Warning>

## Fixed server settings

The `api` service always runs with these values. They are set in `docker-compose.yml`.

| Variable | Value | Meaning |
| - | - | - |
| `NODE_ENV` | `production` | Every production check is on. None can be switched off. |
| `PARMANA_STORAGE` | `postgres` | Records are stored in Postgres. |
| `DATABASE_URL` | `postgresql://parmana:<PARMANA_DB_PASSWORD>@postgres:5432/parmana` | The deployment's own database. |
| `PARMANA_KEY_DIR` | `/app/parmana-local/keys` | Where the server reads its signing keys. |
| `PARMANA_API_KEYS` | The contents of `parmana-local/api-keys.json` | Read when the `api` container starts. |

Any other server variable, for example a connector's, is added as described in [Connectors](/self-hosted/connectors). Every variable the server reads is listed in the [Environment variable reference](/deployment/environment-variables).

## Services

| Service | Image | Starts after | Exits | Restart policy |
| - | - | - | - | - |
| `setup` | `parmana-api:local` | nothing | Yes | none |
| `postgres` | `postgres:16` | nothing | No | `unless-stopped` |
| `migrate` | `postgres:16` | `postgres` is healthy | Yes | none |
| `seed` | `parmana-api:local` | `migrate` finished without error | Yes | none |
| `api` | `parmana-api:local` | `setup` and `seed` finished without error | No | `unless-stopped` |

`parmana-api:local` is built from the repository's `Dockerfile` by `docker compose up --build`.

### `setup`

Runs `docker/local/setup.mjs` as root inside the image, then hands the files to uid 1000, the user the API runs as. On each start:

| If this file is missing | It does |
| - | - |
| `keys/default.private.pem` and `keys/default.public.pem` | Makes a new Ed25519 key pair `default`. |
| `keys/gateway.private.pem` and `keys/gateway.public.pem` | Makes a new Ed25519 key pair `gateway`. |
| `api-keys.json` | Makes one API key, writes its hash there and the key to `api-key.txt`. |

If only one file of a key pair exists, it stops with an error and changes nothing, so a missing file never silently changes your signing key.

### `migrate`

Runs `docker/local/migrate.sh`:

1. Creates the roles `anon`, `authenticated` and `service_role` if they do not exist. Some migrations grant to them. They cannot log in.
2. Creates the table `parmana_schema_migrations` if it does not exist.
3. For each file in `supabase/migrations/`, in filename order, that is not listed in that table: applies it and records it, in one transaction. A migration that fails changes nothing, the service exits with an error, and the API does not start.

It prints `[migrate] <n> applied, <m> already applied`.

### `seed`

Runs `docker/local/seed-policies.mjs`. For every policy version under `policies/` in the image, it inserts the version into the `policies` table if that name and version are not there yet. It never changes an existing row. It prints `[seed-policies] <n> policies in the image, <a> added, <k> already present and kept`.

A seeded policy is **not approved**. Until two people approve it, a request under it is refused. See [Approve a policy](/self-hosted/policy-approval).

### `api`

The Parmana server, unchanged from the hosted API. The health check calls `GET /ready` every 10 seconds and marks the container healthy when it returns `"status":"READY"`.

## Files in `./parmana-local`

| Path | Created by | Contains | Mode |
| - | - | - | - |
| `keys/default.private.pem` | `setup` | The private key that signs authorizations, Trust Records, Refusal Records and Execution Intents. | `0600` |
| `keys/default.public.pem` | `setup` | Its public key. Give it to anyone who verifies your records. | `0644` |
| `keys/gateway.private.pem` | `setup` | The private key the Execution Gateway signs its attestations with. | `0600` |
| `keys/gateway.public.pem` | `setup` | Its public key. | `0644` |
| `api-keys.json` | `setup`, `api-keys.mjs` | The API keys the server accepts: caller ID, SHA-256 hash of the key, and permissions. Never the keys themselves. | `0644` |
| `api-key.txt` | `setup` | The first API key, in plain text. Move it to a secret store, then delete this file. | `0600` |
| `offline-check/` | `setup`, offline check | The Trust Record and public keys saved by the [offline check](/self-hosted/offline-verification). | `0755` |

On Linux these files belong to uid 1000. Read them through a container, as the pages of this guide show, or as root.

`./parmana-local` is listed in `.gitignore` and `.dockerignore`, so it is never committed and never copied into an image.

<Warning>
  Deleting `./parmana-local` deletes your signing keys. Records already signed
  still verify with the public keys, if you kept them. The next start makes new
  keys, and new records are signed with those.
</Warning>

## Volumes and ports

| Name | Kind | Holds | Removed by |
| - | - | - | - |
| `parmana_postgres-data` | Docker volume | The whole database | `docker compose down -v` only |
| `./parmana-local` | Host folder | Keys and API keys | Only by you |

| Port | Published to the host | Serves |
| - | - | - |
| `3000` in `api` | `PARMANA_BIND:PARMANA_PORT` | The Parmana API |
| `5432` in `postgres` | No | The database, to the other services only |

## Helper scripts

All run inside the API image through the `setup` service, so the host needs only Docker:

| Script | Purpose | Page |
| - | - | - |
| `docker/local/api-keys.mjs` | List, add and remove API keys. | [API keys](/self-hosted/api-keys) |
| `docker/local/examples/refund-request.mjs` | Print a `paytm:refund` request body with fresh IDs. | [Quickstart](/self-hosted/quickstart) |
| `docker/local/offline-check/run.sh` | Prove enforcement with no internet route. Runs on the host. | [Offline verification](/self-hosted/offline-verification) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.