Quick start
This runs InterLock and a small sample shop database with Docker Compose, then walks one agent from nothing to a governed, redacted, audited query. Everything after the first build happens in the admin console and one terminal.
You need:
- Docker with Compose 2.24 or later (
docker compose version), running. gitandcurl.
You do not need psql (a copy inside the stack is used below), and you do not
need an API key for any model provider: the optional Anthropic and Mistral keys
only enable document summaries and OCR during ingestion.
1. Start it
Section titled “1. Start it”git clone https://github.com/ContextData/interlockcd interlockdocker compose --profile quickstart up -d --waitThe first run builds the images, which takes a few minutes. It starts the
gateway, the admin console, two workers, PostgreSQL and Redis for InterLock
itself, and sample-postgres, a database named shop with customers and
orders tables. Every port binds to 127.0.0.1 only:
| Service | Address |
|---|---|
| Admin console | http://127.0.0.1:9090 |
| Gateway, HTTP and MCP | http://127.0.0.1:3001 |
| Gateway, PostgreSQL wire | 127.0.0.1:5434 |
If one of those ports is already in use, create docker-compose.override.yml
next to docker-compose.yml with other host ports, run the same up command
again, and use your ports wherever this guide says 9090, 3001 or 5434:
services: admin: ports: !override - "127.0.0.1:9190:9090" gateway: ports: !override - "127.0.0.1:3101:3000" - "127.0.0.1:5534:5432"2. Sign in and set a password
Section titled “2. Sign in and set a password”Open http://127.0.0.1:9090 and sign in as admin with the password admin.
The console asks for a new password straight away (at least 12 characters) and
opens nothing else until you set one.
3. Register the sample database
Section titled “3. Register the sample database”Open Data Sources and choose + New Source. The wizard has five steps:
-
Display name
Sample shop, Connector PostgreSQL. Leave Create default source role templates ticked. Choose Next. -
Connection details:
- Host
sample-postgres, Port5432, Databaseshop. - User
shop_reader, Passwordshop-reader-dev-only. - TLS mode
disable: the sample database has no TLS. Production refuses anything butverify-fullorverify-ca. - Tick Allow a private network address.
sample-postgresis a Compose service on a private address, and InterLock refuses private addresses unless a source opts in.
Choose Test Connection; it reports
Connected ina few milliseconds. Then choose Next. - Host
-
The schema preview reports
Found 2 table(s) in shopand pre-ticks the columns that look like personal data, which records them as PII in the source catalog. Choose Next. -
Keep the cache settings. Choose Next.
-
Review and choose Save Source.
The console opens the new source’s page. It was created as sample_shop: the
ID is generated from the name, and agents use it to name the source.


4. Look at the roles it created
Section titled “4. Look at the roles it created”A source role lists what an agent granted it may do on this source. The wizard created five from the PostgreSQL templates, listed under Source Roles on the Sample shop page:
| Role | Allows |
|---|---|
Read (read) |
list schemas, describe tables, read rows in any table, search discovery |
Analyst (analyst) |
the same as Read, for analysis agents |
Write (write) |
Read, plus inserting and updating rows |
Owner (owner) |
everything |
Blocked (blocked) |
nothing: an explicit deny |
The agent in this guide gets Read. It is what an agent needs to explore
and query a SQL source: interlock_describe_source needs db.table.describe
and interlock_query needs db.table.select for a read. The
MCP tools reference lists the action each tool needs.
To narrow a role to certain tables or columns, open it and choose Edit.
5. Allow reads by policy
Section titled “5. Allow reads by policy”Policies are checked after roles, in priority order (higher first); the first rule that matches decides, and a request no rule matches is denied. Open Policies, choose + New Policy, and create:
- Name
allow-sample-shop-reads, Priority10. - Source ID
sample_shop, Operationread. - Effect
allow.
Choose Save.
6. Create an agent and its key
Section titled “6. Create an agent and its key”Open Identities, choose + New Identity:
- Name
quickstart-agent, Agent typeclaude_code. - Under Source access, choose Sample shop and the Read role.
- Leave Auto-generate API key ticked and choose Save.
The key is shown once. Copy it into a shell variable:
API_KEY=paste-the-key-here7. Send a governed query
Section titled “7. Send a governed query”The same statement over both protocols. First the PostgreSQL wire protocol,
using the psql inside the stack’s own PostgreSQL container: the database name
picks the source and the API key is the password.
docker compose exec -e PGPASSWORD="$API_KEY" postgres \ psql "host=gateway port=5432 user=agent dbname=sample_shop sslmode=disable" \ -c "SELECT name, email, plan FROM customers ORDER BY id LIMIT 3" name | email | plan-------------+------------------+------------ Ada Park | [REDACTED:EMAIL] | team Ben Okafor | [REDACTED:EMAIL] | free Chloe Varga | [REDACTED:EMAIL] | enterprise(3 rows)The email addresses left the database and were redacted on the way out. If
you have psql on your machine, the same query works from there with
host=127.0.0.1 port=5434.
Now over MCP:
curl -sS http://127.0.0.1:3001/mcp \ -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' -H 'MCP-Protocol-Version: 2025-11-25' \ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "interlock_query", "arguments": {"source_id": "sample_shop", "sql": "SELECT name, email, plan FROM customers ORDER BY id LIMIT 3"}}}'The result’s text is the same three rows as JSON, with the same
[REDACTED:EMAIL] values.
Now a write the Read role does not allow, over MCP:
curl -sS http://127.0.0.1:3001/mcp \ -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' -H 'MCP-Protocol-Version: 2025-11-25' \ -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "interlock_query", "arguments": {"source_id": "sample_shop", "sql": "DELETE FROM orders WHERE id = 1"}}}'The result has "isError": true and the text Source role denied: ....
Nothing reached the database.
8. See what happened
Section titled “8. See what happened”Open Audit & Costs. Each request is a row: the identity, the source, the protocol, whether it was allowed, and whether PII was redacted. The refused delete is there too, marked denied. Rows are written in batches, so a request can take a few seconds to appear; refresh if it is not there yet. Filter by Protocol to compare the PostgreSQL and MCP requests.


- First governed query from Claude Code: connect a real agent to the same source.
- Concepts: how the pieces fit together.
- To stop everything:
docker compose --profile quickstart down. Add-vto delete the data as well.