# Installation

> Every way to run Contextator — Docker Compose, plain docker run, building the image yourself, or from source — plus the first account and a reverse proxy.

- Updated: 2026-09-21
- Source: https://contextator.com/en/docs/installation/
- Language: en-US
- Author: Muhammet Şafak

---
Contextator ships as **one Docker image** containing PostgreSQL 16 with pgvector and the Node.js
application. There is no separate database to install and no migration step: the schema is created and
kept up to date on every start.

- [Requirements](#requirements)
- [Docker Compose with the published image (recommended)](#docker-compose-with-the-published-image-recommended)
- [Plain `docker run`](#plain-docker-run)
- [Build the image yourself](#build-the-image-yourself)
- [The first account](#the-first-account)
- [Behind a reverse proxy](#behind-a-reverse-proxy)
- [From source, without Docker](#from-source-without-docker)
- [Upgrading](#upgrading)
- [Uninstalling](#uninstalling)

---

## Requirements

| | |
|---|---|
| Docker | Any recent version with Compose v2 (`docker compose`, not `docker-compose`) |
| Disk | ~1 GB for the image, ~470 MB for the default embedding model (~120 MB with `EMBEDDING_DTYPE=q8`), plus your documents and their vectors |
| RAM | 2 GB is comfortable; embedding is CPU-bound, not memory-hungry |
| CPU | Any x86-64 or ARM64 CPU. No GPU is needed or used. The published image is built for `linux/amd64` and `linux/arm64`, so it runs on Apple Silicon without emulation |
| Network | Only for the one-time model download and for remote sources (git, Notion). Air-gapped installs are possible — see [Embedding Models](/en/docs/embedding-models/) |

Runs on Linux, macOS and Docker Desktop for Windows (WSL 2 backend).

---

## Docker Compose with the published image (recommended)

No clone needed — just the compose file and the annotated environment template:

```bash
mkdir contextator && cd contextator
curl -fsSLO https://raw.githubusercontent.com/Contextator/Contextator/main/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/Contextator/Contextator/main/.env.example
cp .env.example .env
```

Edit `.env` — at minimum, point it at your documentation:

```bash
DOCS_HOST_PATH=/path/to/your/docs     # mounted read-only at /docs in the container
PORT=3444                             # host port
SETUP_CODE=                           # optional: pick the first-run code yourself
```

Then:

```bash
docker compose up -d
docker compose logs -f                # wait for "embedding model ready"
```

Open **http://localhost:3444/** and create the first account — see
[The first account](#the-first-account).

### What the compose file does

- Pulls and runs one container named `contextator`, publishing `${PORT}:3444`. The published image is
  `contextator/contextator`.
- Creates three volumes that survive `docker compose down`, upgrades and container removal:
  `contextator-pgdata` (the database), `contextator-models` (downloaded models) and
  `contextator-data` (uploaded files, git checkouts, Notion pulls).
- Mounts `DOCS_HOST_PATH` read-only at `/docs`.
- Restarts the container unless you stopped it (`restart: unless-stopped`).

Inside the container, PostgreSQL listens only on `127.0.0.1` and is not published — see
[Backup and Data](/en/docs/backup-and-data/) for how to reach it when you need to.

---

## Plain `docker run`

Without Compose, mount the same four paths yourself:

```bash
docker run -d --name contextator -p 3444:3444 \
  -e SETUP_CODE=whatever-you-like \
  -v contextator-pgdata:/var/lib/postgresql/data \
  -v contextator-models:/app/.cache/models \
  -v contextator-data:/data \
  -v /path/to/your/docs:/docs:ro \
  contextator/contextator
```

Add any other settings with `-e` — see [Configuration](/en/docs/configuration/).

---

## Build the image yourself

The compose file above pulls `contextator/contextator`. To build it from source instead — to try a
change to the `Dockerfile`, or on an architecture the published image does not cover — clone the
repository and layer the build override on top:

```bash
git clone https://github.com/Contextator/Contextator.git contextator && cd contextator
cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build
```

`docker-compose.build.yml` builds the image from the checked-out source and tags it
`contextator/contextator:source` — deliberately not `latest`. Compose's default `pull_policy` is
`missing`, so a shared tag would make the two workflows clobber each other silently: build once, and a
later plain `docker compose up -d` would keep running your old local build under the published name
instead of pulling the real one. Everything else — volumes, environment, ports — is exactly what
`docker-compose.yml` defines.

---

## The first account

The dashboard requires an account, and a fresh installation has none. Until the first one exists, every
start prints a one-time **setup code** on stdout:

```
┌─ Contextator first-run setup ───────────────────────────────────────────┐
│ No user accounts exist yet; the dashboard is waiting for its first one. │
│                                                                         │
│   Open   http://localhost:3444/setup                                    │
│   Code   KRTW-9MHD-2PQF                                                 │
│                                                                         │
│ A new code is printed on every start until that first account exists.   │
└─────────────────────────────────────────────────────────────────────────┘
```

`docker compose logs -f` shows it. Open `/setup` — `/` redirects there by itself — and give the code, a
username and a password of at least 12 characters. That account is `root`, and creating it closes setup
for good; there is nothing left to rotate.

Set `SETUP_CODE` in `.env` to choose the code instead, the same way you would choose
`POSTGRES_PASSWORD`. A code you chose is not echoed into the log. If a generated one is lost, restart
the server and read the new one.

Everything after that — more accounts, roles, per-project members — is on
[Accounts and Permissions](/en/docs/accounts-and-permissions/).

> **Serving the dashboard over plain HTTP** on a LAN address? Set `AUTH_COOKIE_SECURE=0`, or browsers
> drop the session cookie and sign-in loops back to `/login`.

---

## Behind a reverse proxy

A new project's MCP endpoint is **token required** by default, but any project can be switched to
**open** or to **account required** on its own page ([Security](/en/docs/security/)). If the server is
reachable by anyone you do not trust, check no project that should be private was switched to open, and
put the proxy's own authentication in front of any project you deliberately leave open.

Three things to get right:

1. **Do not buffer responses.** The legacy SSE transport is a long-lived stream.
2. **Tell the dashboard its public URL** so the snippets it prints are correct.
3. **Pin the cookie flag.** The proxy terminates TLS, so the application only learns the scheme from
   `X-Forwarded-Proto`. Say it outright instead:

```bash
PUBLIC_BASE_URL=https://docs.example.com
AUTH_COOKIE_SECURE=1
```

An nginx location that works:

```nginx
location / {
    proxy_pass http://127.0.0.1:3444;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_read_timeout 1h;
}
```

If browsers on another origin must reach `/mcp/*`, list that origin in `ALLOWED_ORIGINS`. Command-line
clients send no `Origin` header and are always allowed.

---

## From source, without Docker

Useful for development or when you already run PostgreSQL.

```bash
docker compose -f docker-compose.dev.yml up -d   # PostgreSQL 16 + pgvector on localhost:5432
cp .env.example .env
```

In `.env`:

```bash
DATABASE_URL=postgres://contextator:contextator@localhost:5432/contextator
ALLOWED_DOC_ROOTS=/home/me/docs          # or C:/Users/me/docs on Windows
DATA_DIR=.data                           # git checkouts, uploads and Notion pulls
SECRET_KEY=                              # only needed for private repositories / Notion
```

Then:

```bash
npm install
npm run dev        # tsx watch → http://localhost:3444
npm test           # unit tests
npm run typecheck
```

Requires Node.js 22 or newer — the floor in `package.json`'s `engines`, and the version the container
ships — and a PostgreSQL 16 with the `vector` extension.

---

## Upgrading

```bash
docker compose pull
docker compose up -d
```

Building from source instead: `git pull`, then
`docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build`.

The schema is brought up to date automatically at startup. Your data is in volumes and is untouched by
an upgrade. The only upgrade that needs a decision from you is a change of embedding dimensions — see
[Embedding Models](/en/docs/embedding-models/).

**Tags.** `latest` always points at the newest stable release; `0.2` tracks the latest patch inside the
`0.2.x` line; `0.2.0` is one exact, immutable release. Pin a versioned tag for anything you upgrade
deliberately by setting `CONTEXTATOR_TAG` in `.env` (e.g. `CONTEXTATOR_TAG=0.2.0`) — this is read at
every start, not only the first.

---

## Uninstalling

```bash
docker compose down            # stops and removes the container — DATA IS KEPT
docker compose down -v         # also deletes the volumes — DATA IS GONE
```

Your original documentation is never modified: `/docs` is mounted read-only, and local directory
sources are scanned in place rather than copied.
