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

# Deploy the backend

> Postgres + airlockd, and how to operate it.

The backend is one daemon, `airlockd` — HTTP API, embedded
[console](/dashboard), and schema migrations in a single binary — running
from a Compose bundle in `deploy/`:

| Service    | Image                                           | Port          |
| ---------- | ----------------------------------------------- | ------------- |
| `postgres` | `postgres:16-alpine`, data in a named volume    | not published |
| `airlockd` | multi-stage build: Go binary + embedded console | `4747`        |

`airlockd` is the only thing that talks to Postgres. `airlock publish` and
push-ingestion agents ship events to it, [decision flagging](/exposure) runs
inside it, and it migrates its own database at startup — there is no SQL to
run, ever.

## Start it

```sh theme={null}
docker compose -f deploy/docker-compose.yml up -d
```

Any machine with Docker: a laptop for a demo, a VM in your VPC for a pilot.
Sign in to the console at `http://localhost:4747` with the admin account
configured at deploy time, then manage everything else from the console.

## Accounts and tokens

Auth is built in — Airlock's own user table, no external IdP to stand up:

* **Users** — created by an admin in the console (or `POST /api/v1/admin/users`):
  email, password, and a role, `admin` or `user`. Disable rather than delete;
  history stays attributed.
* **CLI identity** — [`airlock login`](/cli#airlock-login) once per machine
  exchanges email + password for an ingest-scoped token. Sessions and
  published events carry that identity from then on.
* **Push-ingestion tokens** — an admin mints a named token per external agent
  (`"market-resolver-7"`) reporting [decision events](/decision-events).
  Scoped, revocable, with a last-used time.

Passwords, tokens, and session cookies are all stored as hashes; a raw token
is shown once at mint and never again.

## Backup

```sh theme={null}
docker compose -f deploy/docker-compose.yml exec postgres pg_dump -U postgres airlock > airlock-$(date +%F).sql
```

## Upgrade

```sh theme={null}
docker compose -f deploy/docker-compose.yml pull
docker compose -f deploy/docker-compose.yml up -d
```

Migrations are forward-only. To roll back, deploy the previous image and
restore the pre-upgrade dump.

## Teardown

```sh theme={null}
docker compose -f deploy/docker-compose.yml down      # stop; keep the data volume
docker compose -f deploy/docker-compose.yml down -v   # stop and DELETE all data
```

## Security notes

* **No outbound connections.** Events come *to* the backend; it makes no
  calls out. Postgres is not published outside the compose network.
* **A dump leaks no credentials.** Tokens and sessions verify against hashes.
* **Reads are per-account.** Admins see everything, `user` accounts see only
  their own activity, and every account is revocable — there is no shared
  read-everything key.
* **Bind scope.** The bundle publishes `airlockd` on every interface so
  laptop-to-laptop demos work. On a network you do not control, bind it to
  `127.0.0.1` or front it with your reverse proxy for TLS.

This is the single-node reference deployment. Production adds TLS
termination, signed images, and your own runbooks on top.
