Documentation menu▾
Documentation
Self-hosting
MakerChecker runs entirely on your own infrastructure. This page covers what you need, how to bring it up for evaluation in one command, how to run the hardened two-role deployment that makes the audit append-only at the database level, the full environment reference, and how to operate it air-gapped with nothing leaving your network.
To get the software
packages/ are Apache-2.0.Requirements
There are two ways to run it. Docker is the simplest and the one this page leads with. Running from source gives you the same server without containers.
| Path | What you need |
|---|---|
| Docker | Docker with Compose. Both Postgres and the server come from the compose file, so nothing else is installed on the host. |
| From source | Node 22 or newer, pnpm 9.15.4, and PostgreSQL 17. Build the server package, point DATABASE_URL at your database, and start it. |
The server listens on PORT, which defaults to 3000. Postgres 17 is the only datastore. There is nothing else to run.
Quick deploy (evaluation)
One command brings up a postgres:17 container and the server, seeds the demo agents and accounts, and prints two API keys you will need.
evaluation
shell
# postgres:17 + the server, seeded with the demo. Good for evaluation.docker compose up# Watch the boot logs. With seeding on, the server prints two API keys ONCE:# [makerchecker] DEMO ADMIN API KEY (shown once - copy it now): mk_...# [makerchecker] DEMO OFFICER API KEY (for approving gated decisions): mk_...# Copy both now. They are not shown again. The API listens on :3000.
This is the single-role configuration. The server connects to Postgres as the database owner and runs migrations at boot. It is the fastest way to look at the product, and the seeded demo includes the recon-preparer agent and the txn-match@1 skill, with segregation of duties blocking recon-preparer from also approving its own work (the conflicting role is recon-approver-role).
The owner can disable the append-only audit triggers. That is fine for evaluation and wrong for production. For tamper-resistance against a compromised application credential, use the hardened deployment below.
Hardened deploy (production)
The hardened compose file splits the database work across two roles. A one-shot migrate step applies migrations as the owner. A one-shot harden step runs ops/harden-db.sql as the owner to create a restricted role, mc_app_runtime. The long-lived server then connects as that restricted role.
production
shell
# Two-role deployment. Set a strong runtime password first.MC_RUNTIME_PASSWORD=$(openssl rand -hex 24) \docker compose -f docker-compose.hardened.yml up --build# Order is enforced by the compose file:# postgres healthy# -> migrate (connects as the owner, applies migrations)# -> harden (owner runs ops/harden-db.sql, creates mc_app_runtime)# -> server (connects as mc_app_runtime, MAKERCHECKER_SKIP_MIGRATE=1)
The runtime role can read and write mutable run-state and can INSERT audit rows, but it cannot UPDATE, DELETE, or TRUNCATE the audit_events table, and it cannot disable the triggers that enforce append-only writes. It does not own the tables, so it cannot grant itself those rights back.
Two settings make this work, and the compose file sets both:
MC_RUNTIME_PASSWORDis read by the harden step and used in the server's connection string. Set it to a strong value.MAKERCHECKER_SKIP_MIGRATE=1on the server, because the runtime role lacksCREATEon the public schema and cannot run migrations itself. The owner already applied them.
Why the non-owner role matters
ALTER TABLE ... DISABLE TRIGGER and then rewrite or delete rows freely. The append-only guarantee on audit_events lives in triggers, so a server that connects as the owner could defeat it. Running as mc_app_runtime, a non-owner with UPDATE, DELETE, and TRUNCATE revoked on the tamper-evident tables, is what makes the audit append-only at the database level rather than only by convention. A compromised application credential still cannot rewrite history. See The audit trail for how the chain is built and verified.Configuration
Configuration is environment variables. Copy .env.example and adjust. Every variable is optional except DATABASE_URL.
| Variable | What it does |
|---|---|
DATABASE_URL | Postgres connection string. Required to start the server or run the CLI. In the hardened deploy this points at the mc_app_runtime role. |
PORT | HTTP port the API and web UI listen on. Default 3000. |
ALLOWED_ORIGINS | Comma-separated origins allowed for browser cross-origin requests. Empty by default, which sends no allow-origin header, so browsers block cross-origin reads. |
ANTHROPIC_API_KEY | Anthropic API key. When set, agent steps call the model and the default model is claude-opus-4-8. |
GEMINI_API_KEY | Gemini API key. When set, the default model is gemini-3-flash-preview. ANTHROPIC_API_KEY takes precedence if both are set. |
MAKERCHECKER_MODEL | Override the default model id for whichever provider is selected. |
MAKERCHECKER_EXECUTOR | Set to scripted to force the deterministic executor even when an API key is present. |
MAKERCHECKER_SEED_DEMO | Set to 1 to seed the demo agents, flows, and admin/officer accounts at boot. Unset seeds nothing. |
DEMO_DATA_DIR | Root directory the demo ingest skills are allowed to read from. |
MAKERCHECKER_DATA_DIR | Directory holding the instance Ed25519 keypair (instance_key.pem, mode 0600). Default ./data. |
MAKERCHECKER_SKIP_MIGRATE | Set to 1 to skip running migrations at boot. Required under the non-owner runtime role. Default unset runs migrations. |
MAKERCHECKER_AUTH_DISABLED | Set to 1 to disable API key auth entirely. Local demos only, never on a reachable deployment. |
MAKERCHECKER_STDIO_ALLOWED_COMMANDS | Comma-separated allowlist of exact commands MCP stdio skills may spawn. Set this in production to pin stdio skills to a known set. |
MAKERCHECKER_ALLOW_PRIVATE_SKILL_HOSTS | Set to 1 to allow HTTP and MCP skills to reach private and loopback hosts. Off by default as an egress guard. Production never sets this. |
A trimmed production file looks like this:
.env
dotenv
# Minimal production .env. Every variable is optional except DATABASE_URL.# Point the long-lived server at the NON-OWNER runtime role (hardened deploy).DATABASE_URL=postgres://mc_app_runtime:<strong-password>@db:5432/makercheckerPORT=3000# Browsers block cross-origin reads unless you list your UI origin here.ALLOWED_ORIGINS=https://makerchecker.internal.example# The runtime role cannot run migrations; the owner already applied them.MAKERCHECKER_SKIP_MIGRATE=1# No model key set -> the scripted, deterministic executor. Fully offline.# (Set ANTHROPIC_API_KEY or GEMINI_API_KEY only if you want live model steps.)# Keep auth ON. Do not set MAKERCHECKER_AUTH_DISABLED in production.
Air-gapped operation
MakerChecker is designed to run with no connection to the internet. With no ANTHROPIC_API_KEY and no GEMINI_API_KEY set, agent steps run on the scripted executor: deterministic, repeatable, and entirely local. The server never phones home.
That means you can run the whole control plane inside a network with no egress. Nothing about authorization, approvals, or the audit depends on a remote service. If you do want live model steps later, setting one provider key is the only change, and you can pin it off again with MAKERCHECKER_EXECUTOR=scripted.
The audit is verifiable offline as well. The records are hash-chained and the export is signed, so an inspector can check the chain and the signature without reaching any server. See The audit trail for the verification procedure.
Authentication and API keys
Every /api route requires a Bearer token. Keys are issued in the form mk_... and are presented as Authorization: Bearer mk_.... The seeded demo prints an admin key and an officer key once at boot; in a real deployment you create keys through the admin API or CLI.
A request that authenticates looks like this:
shell
curl https://makerchecker.internal.example/api/agents \-H "Authorization: Bearer mk_0123456789abcdef0123456789abcdef"
Never disable auth in production
MAKERCHECKER_AUTH_DISABLED=1 turns off API key checks entirely. It exists for local development and the bundled demo only. Do not set it on any deployment that is reachable from another machine. A server with auth disabled accepts unauthenticated calls to every route.With the hardened database, a strong MC_RUNTIME_PASSWORD, a restrictive ALLOWED_ORIGINS, and auth left on, the deployment enforces its guarantees against both a network attacker and a compromised application credential. From here, see The audit trail to verify what the server recorded, or the Quickstart to wrap your first tool against the running server.