> For the complete documentation index, see [llms.txt](https://docs.veilio.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.veilio.xyz/documentation/on-premise/installation-guide.md).

# Installation guide

This guide covers the full path from purchasing an on-premise license on the Veilio SaaS to running a self-hosted instance with Docker and a license.jwt file.

## Installation guide

> **Two separate planes**
>
> * **Control plane** = the Veilio SaaS ([app.veilio.fr](https://app.veilio.xyz)). You create an account, sign a **quote-based** on-premise contract, then download **one JWT per instance** and the deployment kit from **Dashboard → Integration**. Veilio never sees your tokenized data.
> * **Your on-premise instance** = what you deploy on your own servers. It contains only the public key and validates the license offline. No business data leaves your infrastructure.
>
> **Billing model:** contract + instances (quote only). Entry pack = license + 1st site. Extra sites as add-ons. **1 buying org → N instances**, each with its own vault and `license.jwt`.

***

**1. Prerequisites (your server)**

Before deploying on your infrastructure, make sure you have:

* Docker ≥ 24 and Docker Compose ≥ 2.20
* 2 CPUs, 2 GB RAM minimum
* A Linux host (or VM) with outbound HTTPS access only if you want automatic license renewal or the in-app update banner (optional)
* No Docker registry login — the application image is public on [Docker Hub (`veilio/app:latest`)](https://hub.docker.com/r/veilio/app)

You will obtain `license.jwt` (one per site) and the deployment kit from the Veilio SaaS — see section 2.

***

**2. Contract workflow (Veilio SaaS)**

Everything in this section happens on the **control plane** (the Veilio web app), not on your on-premise server.

**2.1 Create your account**

1. Open [app.veilio.fr](https://app.veilio.xyz) and click **Sign up**.
2. Register with your work email and set a password.
3. Confirm your email if prompted, then sign in.

You do **not** need a cloud (SaaS credits) subscription for an on-premise contract. On-premise is a separate product, **sold via quote**.

**2.2 Get an on-premise contract (quote)**

1. Contact Veilio for a **quote**: entry pack + additional instances if needed.
2. After acceptance and invoicing, Veilio activates your **contract** on the control plane (Standard / Pro instance seats) and issues **one JWT per seat**.
3. Sign in at [app.veilio.fr](https://app.veilio.xyz) → **Dashboard → Integration**.

**Catalog (excl. tax / year)**

| Offer                                       | Price      | Quotas **per instance / year**    |
| ------------------------------------------- | ---------- | --------------------------------- |
| **Standard entry** (license + 1st instance) | **€5,990** | 50,000 tokens · 150,000 requests  |
| **Pro entry** (license + 1st instance)      | **€8,990** | 200,000 tokens · 500,000 requests |
| Additional Standard instance                | **€2,990** | same as Standard                  |
| Additional Pro instance                     | **€4,990** | same as Pro                       |

* **Features:** all (bulk, multi-format, datasets, compliance export, etc.)
* **Unlimited:** users, API keys, datasets (they consume tokens + requests)
* Quotas are **annual** over the license period (`iat` → `exp`), not monthly
* **Mix** Standard + Pro on the same contract: yes
* Bulk / format / dataset = **N request units** (same as SaaS)
* Do **not** share one JWT across deployments

**2.3 What you receive after activation**

| Source               | Content                                                                  |
| -------------------- | ------------------------------------------------------------------------ |
| Email                | `license-<site>.jwt` (**one license per instance**)                      |
| Email (optional)     | `vlr_...` refresh token for automatic renewal (shown **once** — save it) |
| **Integration** page | Per-instance JWT download + shared deployment kit                        |

Renewals are handled via the **annual contract invoice**; JWTs are re-issued for the new period.

**2.4 Download the deployment kit and licenses**

On **Dashboard → Integration**, in **Your on-premise instances**:

1. **Deployment kit** → `veilio-onpremise-kit.zip` (`docker-compose.yml`, `.env.onpremise.example`, `README.md`)
2. **Download JWT** per instance → one `license-….jwt` file per site

> `license.jwt` is **not** inside the zip. Place **one file per site** in that site’s `veilio/` folder next to `docker-compose.yml`.
>
> The **Docker image** is not in the zip either — it is pulled from Docker Hub on `docker compose up`.

**2.5 Prepare files on your server**

```bash
unzip veilio-onpremise-kit.zip && cd veilio-onpremise
mkdir -p veilio
cp ~/Downloads/license-mysite.jwt veilio/license.jwt
```

Repeat with a **separate kit copy + JWT** for each additional instance.

***

**3. Configure the environment**

```bash
cp .env.onpremise.example .env
```

Edit `.env` and set at least:

| Variable                                 | Description                                                                                                                |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `DB_PASSWORD`                            | Strong PostgreSQL password                                                                                                 |
| `ENCRYPTION_KEY`                         | `openssl rand -base64 32` — **back it up: without it, tokens cannot be recovered**                                         |
| `NEXTAUTH_SECRET`                        | `openssl rand -base64 32`                                                                                                  |
| `APP_URL`                                | Public URL (e.g. `https://veilio.acme.internal`)                                                                           |
| `VEILIO_IMAGE`                           | Docker image (default: `veilio/app:latest`)                                                                                |
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_FROM`  | Outbound email                                                                                                             |
| `BOOTSTRAP_EMAIL` / `BOOTSTRAP_PASSWORD` | First admin on empty database                                                                                              |
| `VEILIO_LICENSE_FILE`                    | Default: `/veilio/license.jwt` (file mode)                                                                                 |
| `VEILIO_LICENSE_KEY`                     | Full JWT inline (Coolify / secret managers) — overrides file                                                               |
| `VEILIO_CRON_SECRET`                     | Bearer secret for internal jobs (auto-shred). Required if you use retention / TTL. Generate with `openssl rand -base64 32` |

**Automatic renewal (optional):**

| Variable                                | Description                   |
| --------------------------------------- | ----------------------------- |
| `VEILIO_LICENSE_AUTO_REFRESH`           | `true` to enable              |
| `VEILIO_LICENSE_CONTROL_PLANE_URL`      | `https://app.veilio.fr`       |
| `VEILIO_LICENSE_REFRESH_TOKEN`          | `vlr_...` from delivery email |
| `VEILIO_LICENSE_REFRESH_THRESHOLD_DAYS` | Default `45`                  |

***

**4. Configure the license**

**Option A — File (default)**

```bash
VEILIO_LICENSE_FILE=/veilio/license.jwt
mkdir -p veilio && cp ~/Downloads/license-mysite.jwt veilio/license.jwt
```

For auto-refresh persistence, mount `./veilio` read-write (remove `:ro` in `docker-compose.yml`).

**Option B — Environment variable (Coolify, PaaS)**

```bash
VEILIO_LICENSE_KEY=eyJ...full_jwt_content...
# do not set VEILIO_LICENSE_FILE
```

***

**5. Start**

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

On startup the instance: applies migrations, verifies the license, creates bootstrap admin if configured, and optionally refreshes the license.

***

**6. Check status**

```bash
curl -s http://localhost:3000/api/health?db=1 | jq
```

For license details, send header `X-Metrics-Secret` with your `METRICS_SECRET` value.

**SDK / API base URL for your instance:** `https://your-domain.com/api`

***

**7. Retention & auto-shred (required if you use TTL)**

If your apps pass `retention.ttlDays` or `retention.retentionUntil` when tokenizing, you must schedule the auto-shred job yourself. Setting retention alone does **not** destroy data.

1. Set `VEILIO_CRON_SECRET` in `.env` and restart the app if needed.
2. Schedule an hourly call (crontab, systemd timer, or your orchestrator):

```bash
curl -fsS -X POST "https://your-domain.com/api/internal/tokens/auto-shred?limit=500" \
  -H "Authorization: Bearer $VEILIO_CRON_SECRET"
```

3. Confirm a `200` JSON response with a `processed` field.

Full details: [Data Lifecycle: Retention and Crypto Shredding](https://docs.veilio.xyz/documentation/basics/data-lifecycle).

***

**8. Update the application**

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

PostgreSQL volumes and `license.jwt` are preserved.

***

**9. Create an admin manually (alternative)**

```bash
docker compose exec veilio-app pnpm onpremise:seed-admin -- \
  --email admin@acme.internal --password 'ChangeMe123!' --company "Acme"
```

***

**10. Coolify deployment**

1. **New Resource** → **Docker Compose** (image on Docker Hub, do not build from Git).
2. Deploy the kit's `docker-compose.yml`.
3. Set environment variables (section 3), including `VEILIO_CRON_SECRET` if you use retention.
4. Set `VEILIO_LICENSE_KEY` as a secret (option B, section 4).
5. Assign domain to `veilio-app`, port **3000**.
6. `APP_URL` = Coolify HTTPS URL.
7. Configure an external cron / Coolify scheduled job for auto-shred (section 7).

Verify: `curl -s https://your-domain/api/health?db=1`

***

**License lifecycle**

| State                 | Behavior                                 |
| --------------------- | ---------------------------------------- |
| Valid                 | Everything works                         |
| 30 days before expiry | Warning banner + `health: expiring_soon` |
| Expired (0–30 days)   | Grace period: read-only, no new tokens   |
| Expired > 30 days     | Instance refuses to start                |

**Annual quotas:** each instance hard-caps **token creations** and **request units** for the license period. Email alerts fire at **80% / 95% / 100%**. Beyond the cap, new usage is blocked until a quote amendment (upgrade Pro, extra instance, or custom quotas).

**Automatic renewal** — add to `.env` (if outbound HTTPS is allowed and you received a refresh token):

```bash
VEILIO_LICENSE_AUTO_REFRESH=true
VEILIO_LICENSE_CONTROL_PLANE_URL=https://app.veilio.fr
VEILIO_LICENSE_REFRESH_TOKEN=vlr_...
```

**Manual swap (air-gap):**

```bash
cp ~/Downloads/license-mysite.jwt veilio/license.jwt
docker compose restart veilio-app
```

Re-download anytime from **Dashboard → Integration → Download JWT**.

***

**Backups**

You must back up:

* Both PostgreSQL volumes (`veilio-data`, `clients-data`)
* The `ENCRYPTION_KEY` variable (off-server, in a secrets vault)
* `VEILIO_CRON_SECRET` (same vault — rotating it without updating the cron job breaks auto-shred)

***

**TLS / reverse proxy (recommended)**

Terminate HTTPS in front of the container:

```nginx
# nginx example
location / {
  proxy_pass http://127.0.0.1:3000;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Proto https;
}
```

Set `APP_URL` to your public HTTPS URL.
