---
name: treehouse-expo
description: Scaffold and deploy Expo/React Native mobile apps on Treehouse. Generates a complete project (SDK 54), Dockerfile, app.yml, QR code for Expo Go, and explains the live hot-reload dev loop. Use when the user wants to build or host a React Native / Expo mobile app.
---

# Expo / React Native (SDK 54) on Treehouse

Host Metro bundler inside a Treehouse container and connect Expo Go over HTTPS (`exps://`).

## Key idea (why this works)

- Metro embeds `hostname:port` into manifests and bundle URLs.
- Setting `EXPO_PACKAGER_PROXY_URL=https://{subdomain}.{tenant}.devitltd.uk` (no port) forces Metro to emit the public HTTPS URL everywhere.
- With that set, no response-rewriting proxy is needed.

## Prereqs

- User has Expo Go installed (iOS/Android) and it supports Expo SDK 54.
- Use `tc whoami` for the tenant id.

## Step 1 — Create the app directory

```bash
APP_NAME="my-expo-app"
mkdir -p "/workspace/apps/$APP_NAME"
cd "/workspace/apps/$APP_NAME"
```

## Step 2 — Scaffold the project files

Create these files in `/workspace/apps/$APP_NAME/`.

- `package.json`
```json
{
  "name": "my-expo-app",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start --port 8081"
  },
  "dependencies": {
    "expo": "~54.0.0",
    "expo-status-bar": "~2.0.1",
    "react": "18.3.2",
    "react-native": "0.76.7"
  },
  "devDependencies": {
    "@babel/core": "^7.25.0",
    "babel-preset-expo": "~12.0.0",
    "qrcode-terminal": "^0.12.0"
  }
}
```

- `app.json`
```json
{
  "expo": {
    "name": "My App",
    "slug": "my-expo-app",
    "version": "1.0.0",
    "orientation": "portrait",
    "newArchEnabled": true,
    "platforms": ["ios", "android"]
  }
}
```

- `App.js`
```javascript
import { StatusBar } from expo-status-bar;
import { StyleSheet, Text, View } from react-native;

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Hello from Treehouse!</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: center,
    justifyContent: center,
  },
});
```

- `babel.config.js`
```javascript
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [babel-preset-expo],
  };
};
```

## Step 3 — Add `.dockerignore` (CRITICAL)

Without this, `node_modules` (often hundreds of MB) gets included in the Docker build context and builds can stall badly.

Create `.dockerignore`:
```
node_modules
.expo
```

## Step 4 — Install dependencies on the host (CRITICAL)

`node_modules` must exist on the host before deploying so it can be bind-mounted into the container.

```bash
cd "/workspace/apps/$APP_NAME"
npm install --legacy-peer-deps
```

## Step 5 — Dockerfile

Create `Dockerfile`:

```dockerfile
FROM node:20-alpine
WORKDIR /app
RUN apk add --no-cache python3 make g++
EXPOSE 8081
CMD ["npx", "expo", "start", "--port", "8081"]
```

Notes:
- Do not set `CI=1` (it disables Metro’s watcher; hot reload breaks).
- Bind mount the whole directory; file-level bind mounts are unreliable under rootless nerdctl.

## Step 6 — app.yml

Create `app.yml` (substitute your tenant id and subdomain):

```yaml
name: my-expo-app
services:
  - name: my-expo-app
    subdomain: my-expo-app
    port: 8081
    type: proxy
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./:/app
    environment:
      REACT_NATIVE_PACKAGER_HOSTNAME: my-expo-app.TENANT.devitltd.uk
      EXPO_PACKAGER_PROXY_URL: https://my-expo-app.TENANT.devitltd.uk
      EXPO_DEVTOOLS_LISTEN_ADDRESS: "0.0.0.0"
```

## Step 7 — Deploy

From the app directory:

```bash
tc app apply
tc app wait my-expo-app
tc app logs my-expo-app web --tail 50
```

## Step 8 — Send a scannable QR code for Expo Go

Expo Go must use `exps://` (HTTPS). Treehouse serves HTTPS only, so do not use `exp://`.

**IMPORTANT:** Do NOT use ASCII/terminal QR codes — they render as unreadable monospace text in Discord. Always generate a PNG image and send it with `tc send file`.

```bash
# Generate QR code as PNG image
python3 -c "
import qrcode
img = qrcode.make('exps://my-expo-app.TENANT.devitltd.uk')
img.save('/workspace/group/expo-qr.png')
"
tc send file /workspace/group/expo-qr.png
rm /workspace/group/expo-qr.png
```

If `qrcode` module is not installed: `pip3 install --break-system-packages qrcode[pil]`

Tell the user to scan the QR code with Expo Go. Edits to JS/TS files in `/workspace/apps/my-expo-app/` should hot reload instantly.

## Dev loop

- Edit JS/TS/UI code: just edit files on the host → phone hot reloads (no rebuild/redeploy).
- Add npm packages: run `npm install --legacy-peer-deps` on the host, then `tc app apply` to rebuild.

## Troubleshooting

- Build stalls forever: `.dockerignore` missing `node_modules`.
- Container errors like “Unable to find expo”: `node_modules` missing on host (run `npm install --legacy-peer-deps`).
- Expo Go can’t connect: confirm `EXPO_PACKAGER_PROXY_URL` is `https://...` (no port) and `REACT_NATIVE_PACKAGER_HOSTNAME` matches the exact public hostname.
- Hot reload not working: ensure `CI=1` isn’t set; keep `EXPO_DEVTOOLS_LISTEN_ADDRESS="0.0.0.0"`.
