> 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/quickstart.md).

# Quickstart

This page gets a working xprem server in front of you in a few minutes, so you can publish an update and see the whole loop before committing to anything.

When you're ready to run xprem for an app your users actually have installed, start over from the [Installation guide](/xprem/installation-guide/overview.md): it covers storage, CDN, caching, signing keys, and the topology choices that don't matter yet at this stage.

## **Railway 2-click deployment**

<a href="https://railway.com/deploy/expo-open-ota?referralCode=OEHlEK&#x26;utm_medium=integration&#x26;utm_source=template&#x26;utm_campaign=generic" class="button primary">Deploy on railway</a>

The template provisions a PostgreSQL database and an S3-compatible bucket for you. The only values you need to provide are `ADMIN_EMAIL` and `ADMIN_PASSWORD`, which secure the xprem dashboard.

Once the deployment finishes, copy the URL Railway assigned to your service and open `/dashboard` on it:

​ `https://your-app.up.railway.app/dashboard ​`

Sign in with the email and password you set above, then continue from [step 7 of the local setup.](#create-your-app)

## **Run a minimal local server**

#### Prerequisites

* Docker
* An Expo project using `expo-updates`

{% stepper %}
{% step %}

### Generate JWT Secret & DB Master Key

<pre class="language-shellscript"><code class="lang-shellscript"><strong>openssl rand -base64 32   # JWT_SECRET
</strong>openssl rand -base64 32   # DB_KEYS_MASTER_KEY_B64
</code></pre>

{% endstep %}

{% step %}

### Start Postgres

```shellscript
docker network create eoo;
docker run -d --name eoo-db --network eoo \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=expo_open_ota \
  postgres:16;
```

\
The shared `eoo` network lets the server reach the database by name (`eoo-db`). No port is published, so this won't collide with a Postgres you already run on 5432.
{% endstep %}

{% step %}

### Start the server

```shellscript
docker run --rm -it --network eoo \
  -p 3000:3000 \
  -e BASE_URL=http://localhost:3000 \
  -e DB_URL="postgres://postgres:secret@eoo-db:5432/expo_open_ota?sslmode=disable" \
  -e DB_KEYS_MASTER_KEY_B64=your-master-key-from-step-1 \
  -e JWT_SECRET=your-jwt-secret-from-step-1 \
  -e STORAGE_MODE=local \
  -e LOCAL_BUCKET_BASE_PATH=/updates \
  -e CACHE_MODE=local \
  -e USE_DASHBOARD=true \
  -e ADMIN_EMAIL=admin@example.com \
  -e ADMIN_PASSWORD='Admin123!' \
  -v "$(pwd)/updates:/updates" \
  ghcr.io/mercuretechnologies/xprem:latest
```

`DB_URL` is what selects the control plane: the server boots into DB mode and runs its migrations automatically. Look for this line in the logs:

```
⚙️  [CONTROL] Initializing Control Plane (DB Mode)..
```

Verify the server is up:

```shellscript
curl http://localhost:3000/hc
```

{% endstep %}

{% step %}

### Open the dashboard

Go to [**http://localhost:3000/dashboard**](http://localhost:3000/dashboard) and log in with the `ADMIN_EMAIL` and `ADMIN_PASSWORD` you set above (`admin@example.com` / `Admin123!`).

During the boot, that pair seeded the **first admin account** into Postgres: from now on it lives in the database, and the two variables are never read again.
{% endstep %}

{% step %}

### Create your app

<figure><img src="https://2210815540-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3lDfFt997XARABknq9E7%2Fuploads%2Fp29bEmr3DmAOmluEEfXX%2Fimage.png?alt=media&#x26;token=75e1d4da-9480-4f16-beda-397f71c88de6" alt="" width="253"><figcaption></figcaption></figure>

<figure><img src="https://2210815540-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3lDfFt997XARABknq9E7%2Fuploads%2FBIsQjU5GwOVzo9kBrtQd%2Fimage.png?alt=media&#x26;token=d03e5c5d-6767-4795-83c3-da3b4c6a14ad" alt="" width="375"><figcaption></figcaption></figure>

Then go to **App Info** and copy your App ID.

<figure><img src="https://2210815540-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3lDfFt997XARABknq9E7%2Fuploads%2FwbcjF2Ftk1dTEO6YWXFa%2Fimage.png?alt=media&#x26;token=b9530b36-c937-4d24-8de4-dafe41928af3" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Download the signing certificate

Go to **App Info** and click **Download certificate** in the top-right corner.

Save it into your Expo project as `certs/certificate.pem`:

```bash
mkdir -p certs
mv ~/Downloads/app-<your-app-id>-certificate.txt certs/certificate.pem
```

Your app uses this certificate to verify that updates really came from your server. Commit it to your Expo project.
{% endstep %}

{% step %}

### Create an API Key

Go to **API tokens** and create a new token:

{% hint style="info" %}
Make sure to copy your token now. You won't be able to see it again.
{% endhint %}

<figure><img src="https://2210815540-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3lDfFt997XARABknq9E7%2Fuploads%2F3soaHyrNNrSgmGhcKruT%2Fimage.png?alt=media&#x26;token=046c72a1-94bc-4d54-b8f0-d1d8e39052ce" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Configure your Expo app

```shellscript
npx eoas init
```

| Prompt                                             | Answer                                    |
| -------------------------------------------------- | ----------------------------------------- |
| Project id (sent as expo-app-id the in the header) | **Your dashboard app UUID** - from step 5 |
| URL of your update server                          | <http://localhost:3000>                   |
| Do you have already generated your certificates    | **Yes** - You downloaded it in step 6     |
| {% endstep %}                                      |                                           |

{% step %}

### Publish your first update

```shellscript
export RELEASE_CHANNEL=production #you can use other value
export EOO_TOKEN=eoo_your_api_key
npx eoas publish --branch production
```

{% endstep %}

{% step %}

### Point a channel at your branch

Each Expo build is bound to a release channel at build time, and each update is published to a branch. A channel points to one branch, and that mapping determines which updates the build receives.\
Go to Channels -> Create Channel and attach it to the "production" branch.

<figure><img src="https://2210815540-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F3lDfFt997XARABknq9E7%2Fuploads%2FodQIcKeOH76I977DArsg%2Fimage.png?alt=media&#x26;token=4ac2ca9c-8245-4c10-aef0-8987fd68f83c" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
The "production" channel is used in this demo but you should use the one attached to your expo build
{% endhint %}
{% endstep %}
{% endstepper %}

To see exactly what a client would receive, ask for a manifest yourself:

```bash
curl -sD - "http://localhost:3000/manifest" \
  -H "expo-app-id: your-app-id" \
  -H "expo-channel-name: production" \
  -H "expo-runtime-version: 1.0.0" \
  -H "expo-platform: ios" \
  -H "expo-protocol-version: 1"
```

**Your server works.** The `expo-manifest-filters: branch="production"` header confirms it resolved your channel from the database and found your update.

### Tearing down

```bash
docker rm -f eoo-db && docker network rm eoo
```


---

# 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/quickstart.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.
