> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/xprem/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/xprem/references/environment-variables.md).

# Environment variables

The server reads its full configuration from environment variables. Set them in a `.env` file for local development, or in your deployment environment.

### Reading the requirements

* **✅**: always required.
* **with `VARIABLE=value`**: required only for that configuration.
* **Choose exactly one**: set one variable from the listed group. Do not set both.

### Core

| Name         | Required | Description                                                                                                                                      | Example                   |
| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
| `BASE_URL`   | ❌        | Public root URL of the server. Manifest and asset URLs are built from it. Defaults to `http://localhost:3000`, so set it in any real deployment. | `https://ota.example.com` |
| `JWT_SECRET` | ✅        | Secret used to sign dashboard sessions and upload tokens.                                                                                        | Random string             |
| `PORT`       | ❌        | Listening port. Defaults to `3000`.                                                                                                              | `3000`                    |

### Database

Apps, branches, channels and update metadata live in PostgreSQL.

<table><thead><tr><th>Configuration</th><th width="208.10546875">Value</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>Database</td><td><code>DB_URL</code></td><td>✅</td><td>PostgreSQL connection string. Schema migrations run on boot.</td></tr><tr><td>Master key source</td><td><strong>Set exactly one:</strong><br><code>DB_KEYS_MASTER_KEY_B64</code><br><code>AWSSM_DB_KEYS_MASTER_KEY_SECRET_ID</code></td><td>✅</td><td>Use a base64-encoded 32-byte master key, or an AWS Secrets Manager secret containing it. Generate a value with <code>openssl rand -base64 32</code>.</td></tr><tr><td>Connection pool</td><td><code>DB_MAX_CONNS</code>, <code>DB_MIN_CONNS</code></td><td>❌</td><td>Connection pool bounds. Default to <code>DB_MAX_CONNS=25</code> and <code>DB_MIN_CONNS=5</code>.</td></tr><tr><td>Connection lifetime</td><td><code>DB_MAX_CONN_LIFETIME</code>, <code>DB_MAX_CONN_IDLE_TIME</code></td><td>❌</td><td>Connection lifetime limits. Default to <code>DB_MAX_CONN_LIFETIME=30m</code> and <code>DB_MAX_CONN_IDLE_TIME=5m</code>.</td></tr></tbody></table>

{% hint style="warning" %}
Setting both master-key variables, or neither, prevents the server from starting.

The master key is not recoverable. Back it up before the first deployment.
{% endhint %}

Each app's signing keys are managed per app at creation time, see [Keys](/xprem/key-store/keys.md). `KEYS_STORAGE_TYPE` only selects the CloudFront private key source, see the [CDN](#cdn) section.

### Cache

See [Caching](/xprem/caching/overview.md) for details.

| Name                         | Required                         | Description                                                                   |
| ---------------------------- | -------------------------------- | ----------------------------------------------------------------------------- |
| `CACHE_MODE`                 | ❌                                | `redis`, `redis-sentinel`, or any other value for in-memory caching.          |
| `REDIS_HOST`, `REDIS_PORT`   | with `CACHE_MODE=redis`          | Redis endpoint.                                                               |
| `REDIS_PASSWORD`             | ❌                                | Redis password.                                                               |
| `REDIS_USERNAME`             | ❌                                | Redis username for ACL authentication.                                        |
| `REDIS_USE_TLS`              | ❌                                | `true` enables TLS.                                                           |
| `REDIS_CA_CERT_B64`          | ❌                                | Base64-encoded CA certificate for TLS with a custom CA.                       |
| `REDIS_SENTINEL_ADDRS`       | with `CACHE_MODE=redis-sentinel` | Comma-separated Sentinel addresses, e.g. `sentinel-0:26379,sentinel-1:26379`. |
| `REDIS_SENTINEL_MASTER_NAME` | ❌ (defaults to `mymaster`)       | Sentinel master name.                                                         |
| `CACHE_KEY_PREFIX`           | ❌ (defaults to `expoopenota`)    | Key namespace when several deployments share one Redis.                       |

### Storage

See [Storage](/xprem/storage/overview.md) for details.

| Name                                 | Required                  | Description                                                                                    |
| ------------------------------------ | ------------------------- | ---------------------------------------------------------------------------------------------- |
| `STORAGE_MODE`                       | ❌ (defaults to `local`)   | `local`, `s3`, `gcs`, or `azure`.                                                              |
| `S3_BUCKET_NAME`                     | with `STORAGE_MODE=s3`    | S3 bucket name.                                                                                |
| `GCS_BUCKET_NAME`                    | with `STORAGE_MODE=gcs`   | GCS bucket name.                                                                               |
| `GOOGLE_APPLICATION_CREDENTIALS_B64` | with `STORAGE_MODE=gcs`   | Base64-encoded service account JSON, used for signed URLs.                                     |
| `AZURE_BLOB_CONTAINER_NAME`          | with `STORAGE_MODE=azure` | Blob container name.                                                                           |
| `AZURE_STORAGE_ACCOUNT_NAME`         | with `STORAGE_MODE=azure` | Storage account name.                                                                          |
| `AZURE_STORAGE_ACCOUNT_KEY`          | with `STORAGE_MODE=azure` | Account access key. Also signs the SAS URLs used for uploads and asset delivery.               |
| `AZURE_BLOB_ENDPOINT`                | ❌                         | Custom blob service URL (Azurite, private endpoints).                                          |
| `LOCAL_BUCKET_BASE_PATH`             | with `STORAGE_MODE=local` | Directory where updates are stored. Defaults to `./updates`.                                   |
| `BUCKET_KEY_PREFIX`                  | ❌                         | Key prefix namespacing all objects inside a shared bucket. Applies to `s3`, `gcs` and `azure`. |
| `S3_KEY_PREFIX`                      | ❌                         | **Deprecated.** Use `BUCKET_KEY_PREFIX` instead. Kept for backward compatibility.              |
| `AWS_BASE_ENDPOINT`                  | ❌                         | Custom S3-compatible endpoint (Cloudflare R2, MinIO, Railway…).                                |
| `AWS_S3_FORCE_PATH_STYLE`            | ❌                         | `true` forces path-style S3 addressing for providers that require it.                          |

### AWS credentials

Static credentials are optional. When absent, the AWS SDK uses the runtime credential chain. This supports IAM roles attached to EC2 instances, ECS tasks, or EKS service accounts.

| Name                                         | Required                                                          | Description                                                                                 |
| -------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `AWS_REGION`                                 | with `STORAGE_MODE=s3` or `KEYS_STORAGE_TYPE=aws-secrets-manager` | AWS region. Defaults to `eu-west-3` when unset, a surprising default, so set it explicitly. |
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | ❌                                                                 | Static credentials. Omit both to use the runtime IAM role.                                  |

### CDN

See [CDN](/xprem/cdn/overview.md) for details.

<table><thead><tr><th>Configuration</th><th width="230.85546875">Value</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td>CloudFront distribution</td><td><code>CLOUDFRONT_DOMAIN</code></td><td>❌</td><td>Distribution domain. Enables CloudFront delivery.</td></tr><tr><td>Public key ID</td><td><code>CLOUDFRONT_KEY_PAIR_ID</code></td><td>with <code>CLOUDFRONT_DOMAIN</code> set</td><td>CloudFront public key ID.</td></tr><tr><td>Private key source</td><td><strong>Set exactly one:</strong><br><code>AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID</code><br><code>PRIVATE_CLOUDFRONT_KEY_B64</code><br><code>PRIVATE_CLOUDFRONT_KEY_PATH</code></td><td>with <code>CLOUDFRONT_DOMAIN</code> set</td><td>Provide the private key through AWS Secrets Manager, as base64, or from a local PEM file. When <code>KEYS_STORAGE_TYPE</code> is set, only the source matching it is used; when unset, sources are tried in the listed order (Secrets Manager → base64 → file path).</td></tr><tr><td>Generic CDN</td><td><code>CDN_BASE_URL</code></td><td>❌</td><td><a href="/pages/n9xmpNPWPqtNSVHHApC2">Generic CDN</a> base URL in front of a public S3, GCS or Azure bucket. Takes precedence over GCS and Azure signed URL delivery.</td></tr><tr><td>Generic CDN (legacy)</td><td><code>S3_CDN_PREFIX</code></td><td>❌</td><td><strong>Deprecated.</strong> Use <code>CDN_BASE_URL</code> instead. Kept for backward compatibility.</td></tr></tbody></table>

### Dashboard

| Name             | Required          | Description                                                                                                                                                                                            |
| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `USE_DASHBOARD`  | ❌                 | `true` enables the dashboard.                                                                                                                                                                          |
| `ADMIN_EMAIL`    | on the first boot | Read once, at the first boot, to seed the first admin account (see [Users](/xprem/dashboard/users.md)).                                                                                                |
| `ADMIN_PASSWORD` | on the first boot | The first admin's password. It must meet the dashboard password policy (at least 8 characters, with an uppercase letter, a lowercase letter, a digit and a special character) or the first boot fails. |

Both variables are required on the very first boot, whatever `USE_DASHBOARD` is set to. The migration that seeds the first admin account runs with the rest of the schema migrations, and it aborts the boot when either variable is missing or when the password does not satisfy the policy. Once that first boot has succeeded the pair is never read again, so you can drop both from your deployment.

### Metrics

| Name                 | Required | Description                                      |
| -------------------- | -------- | ------------------------------------------------ |
| `PROMETHEUS_ENABLED` | ❌        | `true` exposes Prometheus metrics on `/metrics`. |

### Migration

| Name                          | Required | Description                                                                                                                                                                                                                                         |
| ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SKIP_LEGACY_APP_ID_FALLBACK` | ❌        | `true` stops resolving manifest/asset requests without an `expo-app-id` header to the legacy `EXPO_APP_ID` app imported from a v2 deployment. Those requests then fail with `400`. Leave unset while clients built before v2 are still in the wild. |

### Enterprise features

| Name                                   | Required                                                | Description                                                                                                                                                                                                                                     |
| -------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TRUST_PROXY_HEADERS`                  | ❌                                                       | Used for [Token access](/xprem/security/token-access.md) IP allowlists. Only enable this when the server is reachable exclusively through a trusted proxy, since a client that can reach the server directly could otherwise forge that header. |
| `TRUST_PROXY_DEPTH`                    |                                                         | Used for [Token access](/xprem/security/token-access.md) IP allowlists. Tells the server how many trusted proxies sit in front of it.                                                                                                           |
| `AUDIT_LOG_RETENTION_DAYS`             | ❌ (defaults to `550`)                                   | How many days [audit log](/xprem/security/audit-log.md) entries stay in the database before the daily purge removes them.                                                                                                                       |
| `ARCHIVE_AUDIT_LOGS`                   | ❌                                                       | `true` enables the periodic NDJSON export of the [audit log](/xprem/security/audit-log.md) to a dedicated bucket.                                                                                                                               |
| `AUDIT_LOGS_EXPORT_INTERVAL_SECONDS`   | ❌ (defaults to `300`)                                   | How often the audit archive exporter runs, in seconds.                                                                                                                                                                                          |
| `S3_BUCKET_AUDIT_LOGS_NAME`            | with `ARCHIVE_AUDIT_LOGS=true` and `STORAGE_MODE=s3`    | Dedicated S3 bucket receiving the audit archive. Must differ from `S3_BUCKET_NAME`.                                                                                                                                                             |
| `GCS_BUCKET_AUDIT_LOGS_NAME`           | with `ARCHIVE_AUDIT_LOGS=true` and `STORAGE_MODE=gcs`   | Dedicated GCS bucket receiving the audit archive. Must differ from `GCS_BUCKET_NAME`.                                                                                                                                                           |
| `AZURE_BLOB_AUDIT_LOGS_CONTAINER_NAME` | with `ARCHIVE_AUDIT_LOGS=true` and `STORAGE_MODE=azure` | Dedicated blob container receiving the audit archive. Must differ from `AZURE_BLOB_CONTAINER_NAME`.                                                                                                                                             |
| `LOCAL_AUDIT_LOGS_BASE_PATH`           | ❌ (defaults to `./audit-logs`)                          | Directory receiving the audit archive with `STORAGE_MODE=local`. Must differ from `LOCAL_BUCKET_BASE_PATH`.                                                                                                                                     |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/xprem/references/environment-variables.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
