---
id: self-hosted-install
title: Install Vantage (self-hosted)
sidebar_label: Self-hosted install
---

This installs the control plane on a host you own. Budget about fifteen minutes.

## Before you start

You need:

- A Linux host with **Docker** and the **Compose plugin**.
- A DNS name pointing at it. You will use it for both the web UI and, with a
  port, for agents.
- A reverse proxy terminating TLS in front of the web UI. gRPC on `:9090` is
  reached directly by agents.
- Two ports reachable from every machine you intend to manage: the web port for
  people, and **9090** for agents.
- Outbound access from the control plane, and from every managed machine, to
  `gitea.hostxtra.co.uk`, which serves the agent releases.

The stack itself brings MongoDB, Redis and guacd with it. You do not need to
provide a database.

## 1. Get the compose file

Put `deploy/docker-compose.yml` from the repository in a working directory, for
example `/opt/vantage`.

```bash
mkdir -p /opt/vantage/data && cd /opt/vantage
# copy docker-compose.yml here
```

The `server` service bind-mounts `./data`, which is where workflow run logs are
written. Create it before first boot so it is not owned by root-in-container in
a way you did not intend.

## 2. Write the environment file

Create `/opt/vantage/.env`:

```bash
# The host:port agents dial. NOT the web URL this port speaks gRPC.
GRPC_HOST=vantage.example.com:9090


# 32 bytes as 64 hex characters. Generate with the command below.
KEY_ENCRYPTION_KEY=

# Optional: where workflow run logs are written inside the container.
VANTAGE_WORKFLOW_LOG_DIR=/data/workflow-logs
```

Generate the encryption key:

```bash
openssl rand -hex 32
```

:::danger Keep the encryption key
`KEY_ENCRYPTION_KEY` encrypts SSH private keys, vault secrets, OIDC client
secrets and console credentials with AES-256-GCM. Lose it and every one of those
becomes unreadable there is no recovery path. Back it up somewhere other than
the server it protects, and never rotate it without a planned re-encryption.
:::

:::warning `GRPC_HOST` has no default
The server refuses to boot without it. There is deliberately no fallback to the
web host: that would hand every agent a port that does not speak gRPC, and the
failure would only surface later, on each agent, as a connection error.
:::

## 3. Start the stack

```bash
docker compose up -d
docker compose ps
```

Five services come up: `mongo`, `redis`, `guacd`, `server` and `web`.

Check the server got through boot:

```bash
docker compose logs -f server
```

Boot runs database migrations, builds indexes and seeds the default workflow
step library. Index builders for auth and settings are **fatal on failure** —
they enforce tenant isolation, so the server would rather not start than start
without them.

## 4. Put a proxy in front

Point your reverse proxy at `web` on port `3000` and terminate TLS there. The
web app calls the REST API through a Next rewrite, so you do not need to expose
`8080` publicly.

Do **not** proxy `9090`. Agents connect to it directly over TLS.

## 5. First sign-in

Open your hostname in a browser. With no users in the database, you are sent to
`/setup`.

Continue with [First login](./first-login.md).

## Verifying the install

| Check                                          | Expected                        |
| ---------------------------------------------- | ------------------------------- |
| `docker compose ps`                            | five services `running`         |
| `curl -s localhost:8080/auth/bootstrap-status` | JSON saying bootstrap is needed |
| `nc -z your-host 9090`                         | open                            |
| `docker compose logs server \| grep -i fatal`  | nothing                         |

## Common install problems

**Server exits immediately.** Almost always a missing `GRPC_HOST`. The log line
names it.

**Agents register but never go active.** They reached `:9090` for `Register` but
cannot sustain the poll, or `GRPC_HOST` names a host they resolve differently.
Check from the managed machine, not from the control plane host.

**Secrets pages error.** `KEY_ENCRYPTION_KEY` is empty or not 64 hex characters.

More in [Troubleshooting](../reference/troubleshooting.md).

## What this install does not include

The website, the HQ portal and this documentation site are hosted by us and are
not part of a self-hosted install. It deliberately runs none of them, and in
particular never holds the licence signing key.
