> 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/stateless-mode/overview.md).

# Overview

## Stateless mode

Stateless mode is xprem's original operating model. It powered v1 and v2.

It keeps the server lightweight. No database or separate control plane is required.

### How it works

xprem reuses Expo tokens and sessions to authenticate these operations:

* Publish an update.
* Roll back an update.
* Republish an update.

The server also reads branches, release channels, and their mappings from Expo. It does not manage this state locally.

```mermaid
flowchart LR
    CLI["eoas CLI"] -->|publish| SRV["xprem"]
    APP["expo-updates client"] -->|manifest| SRV
    SRV --> ST[("Your storage (bucket)")]
    SRV -->|"auth on every publish<br/>channel → branch lookup"| EXPO["Expo API"]
    style EXPO stroke-dasharray: 5 5
```

### Update storage

Updates live in an object-storage bucket. Each app uses this hierarchy:

```
appId/
└── branch/
    └── runtimeVersion/
        ├── update-1
        ├── update-2
        └── update-n
```

```
Dashboard or client manifest request
                │
                ▼
        Resolve branch and channel in Expo
                │
                ▼
 Search the bucket under appId/branch/runtimeVersion
                │
                ▼
      Return the matching update or manifest
```

The dashboard searches this hierarchy to list updates. Manifest generation performs the same lookup.

Bucket searches can become slow as the update history grows. Stateless mode adds caching to reduce lookup time.

### When to use it

Choose stateless mode when you want a simple OTA server without operating a database. It still scales well for a single application.

You need one app, one storage bucket, and optionally Redis for caching. In return, you forgo control-plane features and remain dependent on Expo for release metadata and update operations.

{% hint style="warning" %}
Stateless mode supports **one application per xprem server**. Use the control plane when one server must manage multiple applications.
{% endhint %}

## Differences from control plane

The rest of the documentation describes a control-plane server. This section is the reference of everything that works differently on a stateless one; for a stateless deployment, this list wins over any other page.

### Mode selection

The server runs stateless when `DB_URL` is not set. Setting it switches the server to control-plane mode; your keys and existing updates are migrated automatically, as described in [Keys](/xprem/key-store/keys.md).

### App configuration

One server hosts exactly one app, configured entirely through environment variables:

| Name                                                                  | Required                                     | Description                                                                                                |
| --------------------------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `EXPO_APP_ID`                                                         | ✅                                            | Expo project ID (EAS `projectId`) that clients send as the `expo-app-id` header.                           |
| `EXPO_ACCESS_TOKEN`                                                   | ✅                                            | Expo access token scoped to that project.                                                                  |
| `KEYS_STORAGE_TYPE`                                                   | ❌ (defaults to `local`)                      | Signing key store: `local`, `environment`, or `aws-secrets-manager`. See [Keys](/xprem/key-store/keys.md). |
| `PUBLIC_LOCAL_EXPO_KEY_PATH`, `PRIVATE_LOCAL_EXPO_KEY_PATH`           | with `KEYS_STORAGE_TYPE=local`               | Paths to the signing key PEM files.                                                                        |
| `PUBLIC_EXPO_KEY_B64`, `PRIVATE_EXPO_KEY_B64`                         | with `KEYS_STORAGE_TYPE=environment`         | Base64-encoded signing keys (PEM).                                                                         |
| `AWSSM_EXPO_PUBLIC_KEY_SECRET_ID`, `AWSSM_EXPO_PRIVATE_KEY_SECRET_ID` | with `KEYS_STORAGE_TYPE=aws-secrets-manager` | AWS Secrets Manager secret IDs.                                                                            |

The database variables (`DB_URL`, the master key) are not used, and there is no per-app key store: the variables above are the whole key configuration.

### Signing keys

You generate and store the app's signing pair yourself. From the root of your Expo project:

```bash
npx eoas generate-certs
```

The command asks you for a certificate output directory, a key-pair output directory, a certificate common name and a validity duration, then creates three files:

* `public-key.pem` and `private-key.pem` — the server uses them to sign your updates.
* `certificate.pem` — your Expo client uses it to verify updates. Commit this file to your Expo project, and never commit the private key.

Then point the server at the pair through `KEYS_STORAGE_TYPE` and the matching variables from the table above.

### Publishing and authentication

EOAS uses Expo authentication instead of `EOO_TOKEN`:

* Set `EXPO_TOKEN` to provide an Expo access token explicitly. In CI this is mandatory: no session exists as a fallback.
* Without `EXPO_TOKEN`, EOAS reuses the active EAS session from `eas login`.
* Leave `EOO_TOKEN` unset: its presence switches EOAS to control-plane authentication and the publish is rejected.

Channels are managed on Expo's side: create them with `eas channel:create`, and keep branch and channel names identical.

### Storage is the source of truth

With no database, the bucket carries the update metadata itself. Next to the files EOAS uploads, the server maintains its own marker files in each update directory:

| File                   | Written                         | Purpose                                                                                                           |
| ---------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `update-metadata.json` | At publish time                 | Holds the platform, the git commit hash, the optional update message, and the update UUID served in the manifest. |
| `.check`               | Last, after upload verification | Marks the update as complete. An update without it is invisible to clients and the dashboard.                     |
| `rollback`             | By `eoas rollback`              | Marks the directory as a rollback, served as a `rollBackToEmbedded` directive instead of a manifest.              |

These files are internal to the server. Do not edit or delete them: without `update-metadata.json` the update loses its identity, and deleting `.check` hides a published update.

### Dashboard

The dashboard is read-mostly: it shows your updates, your runtime versions, and the channel-to-branch mappings it reads back from Expo. `ADMIN_EMAIL` and `ADMIN_PASSWORD` are its only account and stay required for as long as the server runs; they are the sign-in credentials, not a first-boot seed.

### Not available in stateless mode

* **Progressive rollouts**: `--rollout-percentage` is rejected with an explicit error, and the dashboard shows no rollout controls.
* **API tokens**: publishing authenticates with Expo credentials, so [Token access](/xprem/security/token-access.md) does not apply.
* **Every Enterprise feature**: audit log, SSO, user roles, Observe and device identity all live in the control-plane database.
* **Multiple applications** on one server.


---

# 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/stateless-mode/overview.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.
