The idea: same server, your machine
Self-hosted Parmana is not a cut-down edition. It is the same production image as the hosted API, packaged with its own Postgres and started with one command:docker compose up -d --build --wait
That starts five services in order:
- setup makes two Ed25519 signing key pairs and a first API key on the first start, and keeps them on every later start.
- postgres is the deployment’s own database, not reachable from outside Docker.
- migrate creates or upgrades the schema, applying each migration once.
- seed copies the policies shipped with Parmana into the database. It does not approve them.
- api is the server, in production mode.
No rule is relaxed
This was the part I was most careful about. It would have been easy to make a “local mode” that skips the annoying parts. The self-hosted server keeps every production rule:- Every request needs an API key, and a key acts only for its own caller unless it lists others.
- A policy authorizes nothing until two different people approve it, one with a signed step-up authorization. Seeding is not approval: that decision belongs to your people, not to me.
- No agent action is authorized without a signed human approval, reads included, whatever the amount.
- Before an action is released, the server signs and stores an Execution Intent. If it cannot, nothing is released.
- A connector must be reached over HTTPS.
Proving it with no internet
One script,docker/local/offline-check/run.sh, starts an isolated copy of the deployment on a Docker network created with no route to the internet, runs fourteen checks, and cleans up. Some of them:
- the network really cannot reach the internet;
- a policy proposed by one person, refused when the same person tries to approve it, then approved by a second person with a signed step-up;
- a refund manager’s key added the same way, through maker-checker;
- a ₹500 refund with that manager’s signed approval authorized and delivered to the downstream system exactly once, which checked the gateway’s signature with only the public key;
- a ₹50,000 refund with no approval refused, and never delivered;
- the Trust Record verified with only the public keys, and a copy with the amount changed failing.
Running it day to day
Upgrading isgit pull and the same start command. Migrations already applied are skipped, new policy versions are added but not approved, and your keys are kept. A backup is two things taken together: a Postgres dump and the directory that holds the keys and API keys. I tested a full wipe and restore on 2026-09-25; a Refusal Record and a policy approval made before the backup were both there afterwards. After every upgrade, the docs say to run the offline check again.
What it does not do
- No customer runs it yet. The deployment, its one-command start and its enforcement without internet are proven on my machines and in CI. Any bank, fintech or other organization running it on its own infrastructure, with its own people approving its policies, is still a future claim.
- Installing needs the internet. The image is built and Postgres is pulled while online. The claim is about running offline, not installing offline.
- The offline check used a stand-in. The downstream system was a small stand-in that verifies the gateway’s signature and calls nothing. Only the Paytm connector path has been run self-hosted.
- Other connectors break the isolation. HubSpot, Slack and GitHub call services on the internet, so a deployment that uses them is not isolated.
- One server, one database. No replicas, no failover.
- Signing keys are files on the host. Protect and back up that directory. There is no key rotation procedure for the self-hosted deployment yet; if you replace a key, keep every old public key so old records still verify.
- KMS is untested here. AWS KMS signing works on the hosted deployment, but has not been tested with the self-hosted one.
- Migrations run through Compose. The container on its own does not migrate.