> 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.

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 by default that mapping determines which updates the build receives. A channel can later serve other branches through [progressive rollouts](/xprem/eoas/progressive-rollouts.md) or [branch surfing](/xprem/features/branch-surfing.md), but a fresh channel behaves exactly like this.\
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
```
