Dev Environments
Portless named .localhost URLs, local vs remote VPC development, and Expo.
Strategy for supporting all IDEs, local PC development, and remote VPC development across the basilic monorepo.
Local HTTP apps use Vercel Labs Portless: stable https://*.localhost names, HTTPS with a local CA, ephemeral bind ports. Humans and agents should use the names, not ports.
Quick Reference
| Context | Web + API | Mobile (Expo) |
|---|---|---|
| Local | pnpm setup then pnpm dev | pnpm --filter @repo/mobile start |
| Remote + Cursor | Forward 443; pnpm dev | pnpm --filter @repo/mobile start:localhost |
| Remote + other IDE | Forward 443; run pnpm dev | Forward 8081 + start:localhost, or use start:tunnel |
| Physical device (remote Metro) | — | Use start:tunnel (port forwarding does not help devices) |
| Bypass Portless | pnpm --filter <pkg> dev:app or PORTLESS=0 | Metro is not wrapped |
Overview
The monorepo must work consistently whether you develop locally (macOS, Linux, Windows) or on a remote VPC. This document defines port requirements, app-specific behavior, and environment-agnostic patterns so any IDE (Cursor, VS Code, Neovim, JetBrains, etc.) can be used.
Core Principles
- IDE-agnostic – No assumptions about Cursor, VS Code Remote, or other tools. Docs and scripts work for any setup.
- Named local URLs – Application traffic uses Portless
.localhosthosts. Bind ports are assigned in 4000–4999 and are not part of the public contract. - Environment detection – Commands and env vars support both local and remote contexts; docs explain both paths.
Canonical local URLs
| URL | App | Notes |
|---|---|---|
https://basilic.localhost | Next.js web | Login test@test.ai |
https://api.basilic.localhost | Fastify API | GET /health, /reference |
https://docu.basilic.localhost | Fumadocs | This repo only |
https://email.basilic.localhost | React Email preview | |
https://agents.basilic.localhost | eve workspace | /eve/command and /eve/chat (GET …/v1/health) |
Linked git worktrees prefix the branch: https://fix-ui.api.basilic.localhost. scripts/dev.mjs exports sibling URLs from portless get so worktrees do not call the main checkout.
pnpm dev starts the Portless HTTPS proxy on 443 before Turbo when it is down, so sudo is prompted once in your terminal. It does not touch the Keychain (--skip-trust). Parallel app tasks no longer each try to bind 443 inside the TUI. pnpm setup / pnpm setup:portless trusts the local CA and starts that proxy (may prompt for sudo and a Keychain confirmation; re-run is safe; CI skips it). After reboot, the next pnpm dev, pnpm exec portless proxy start, or pnpm exec portless service install brings :443 back. portless get may print :1355 if the HTTPS proxy was never started; Basilic env still uses https://<name>.localhost (port 443).
Safari: .localhost may need pnpm exec portless hosts sync. Diagnose with pnpm exec portless doctor and pnpm exec portless list.
Still numeric (not Portless)
| Port | Service |
|---|---|
| 54322 | Local Supabase Postgres |
| 8081 / 19000 / 19001 | Expo Metro / DevTools |
| 3000 / 3001 | Playwright and CI spawned web/API (no Portless, no eve) |
| 3104 / 3105 | Local eve command/chat loopback behind the agents gateway |
| 11434 | Optional Ollama |
Direct-run debugging: pnpm --filter @repo/web dev:app (and sibling dev:app scripts). PORTLESS=0 pnpm --filter @repo/web dev bypasses the proxy. Node processes outside Portless need NODE_EXTRA_CA_CERTS=~/.portless/ca.pem to fetch the named HTTPS URLs.
Google/Apple OAuth may reject .localhost redirect URIs. Local callbacks are still documented as https://basilic.localhost/auth/callback/oauth/.... Use the provider's localhost exception or a custom TLD if they refuse.
Local PC Development
Standard workflow: run everything on the same machine where the IDE and simulators live.
Web + API + agents
pnpm devEnsures local Postgres is up, then starts Turborepo's TUI (same left-pane logs as pnpm lint:eslint / pnpm test): Fastify, Next.js, eve workspace (/eve/command and /eve/chat) in this repo, and dependent package watchers. Prints the named .localhost URLs, then the TUI. Mobile stays a separate command. Generated projects omit agents.
Mobile (Expo)
pnpm --filter @repo/mobile start
# or: cd apps/mobile && pnpm start- Default – Expo uses LAN; simulator/device connects via your machine's IP.
- iOS Simulator – Requires macOS + Xcode; runs locally.
- Android Emulator – Runs locally (Linux/macOS/Windows).
Remote VPC Development
When the repo and dev servers run on a remote VPC, the simulator and browser run on your local machine and must reach Metro/API/Next over the network. Note: If the VPC runs Linux, you cannot run the iOS Simulator there (Xcode is macOS-only). Use a local Mac for iOS or rely on Expo Go on a physical device + tunnel.
Port Forwarding (Cursor, VS Code Remote-SSH)
Cursor automatically tunnels ports from remote to local when you connect to a VPC. No setup required—run Expo and the simulator will reach Metro via localhost once forwarded.
If your IDE forwards ports from remote to local:
-
Forward 443 so
https://*.localhoston your laptop hits the remote Portless proxy. Also forward8081(and optionally19000,19001) for Expo. Trust the remote Portless CA on the client (NODE_EXTRA_CA_CERTS/portless trust), or usePORTLESS=0/dev:appwith raw loopback ports. -
Run Expo with
--localhostso it advertiseslocalhost:8081instead of the VPC's LAN IP:pnpm --filter @repo/mobile start:localhost -
The simulator on your local machine connects to
localhost:8081, which is tunneled to the remote Metro server.
Tunnel Mode (No Port Forwarding)
When port forwarding is unavailable or unreliable:
pnpm --filter @repo/mobile start:tunnelExpo creates a public URL (via ngrok). Simulators and physical devices can connect regardless of network. Expect higher latency. Physical devices (real phones/tablets) cannot use localhost—they run on a different machine. Port forwarding only helps simulators on your dev machine. For physical devices with remote Metro, always use --tunnel.
Network / VPN
If the VPC is reachable from your machine (VPN or shared network):
- Use
--lan(or default). Expo will advertise the VPC's IP. - Ensure your local machine can reach the VPC Portless proxy (443) and Metro (
8081). Portless is loopback-only unless LAN mode is enabled.
App-Specific Notes
Expo (apps/mobile)
| Scenario | Command | Simulator connects via |
|---|---|---|
| Local dev | pnpm --filter @repo/mobile start | LAN IP (default) |
| Remote + port forwarding | pnpm --filter @repo/mobile start:localhost | localhost:8081 (tunneled) |
| Remote + no forwarding | pnpm --filter @repo/mobile start:tunnel | Public ngrok URL |
| Remote + VPN / same LAN | pnpm --filter @repo/mobile start | VPC IP:8081 |
Scripts start:localhost and start:tunnel are available in apps/mobile for convenience.
Expo Setup Details
Prerequisites
- iOS Simulator: macOS with Xcode. Run
xcode-select --installif needed. Simulator is in Xcode → Open Developer Tool → Simulator. - Android Emulator: Android Studio with an AVD. Create a virtual device in AVD Manager.
- Expo Go (physical device): Install from App Store / Play Store. Connects via QR code or LAN/tunnel URL.
Expo Go vs development builds
- Expo Go – Default. No native build needed.
pnpm start→ scan QR or pressi/ato open simulator. Use when the app has no custom native code. - Development builds – Required for custom native modules (e.g.
expo-dev-client). Build locally witheas buildornpx expo run:ios/run:android. Simulator builds run on the machine where the simulator lives (e.g. your Mac for iOS).
Workflow: remote VPC + Cursor
- Connect Cursor to the remote VPC (SSH).
- Open terminal in Cursor, run:
pnpm --filter @repo/mobile start:localhost. - Cursor auto-tunnels port 8081. Press
i(iOS) ora(Android) in the Expo CLI. - Simulator launches locally and loads the bundle from
localhost:8081(forwarded to remote Metro).
Hot reload / Fast Refresh works over tunnels and port forwarding, but may feel slightly slower than local due to network latency.
API / env for mobile
- The app runs in the simulator on your local machine. API requests must reach wherever your backend is.
- Simulator on the same machine as Portless:
EXPO_PUBLIC_API_URL=https://api.basilic.localhost. - Physical devices do not resolve
.localhostto your computer; use Expo LAN/tunnel and a reachable API URL. - If using tunnel for Expo, the API must be reachable from the device—use a public URL or ensure the device is on the same network.
Troubleshooting Expo
| Issue | Fix |
|---|---|
| "Unable to resolve module" / red box | Run pnpm install at repo root; restart Metro with pnpm start (clear cache: expo start -c). |
| Simulator can't connect to Metro (remote) | Use --localhost when Cursor forwards ports; or --tunnel if not. Ensure 8081 is in the Ports panel (Cursor/VS Code). |
| "Network response timed out" | API unreachable. Check EXPO_PUBLIC_API_URL and that the API host is reachable from the simulator. |
| iOS Simulator not found | Open Xcode Simulator first, or run open -a Simulator before pressing i. |
| Android emulator not found | Start an AVD from Android Studio; ensure adb devices lists it. |
Next.js / Fastify
These run where you execute pnpm dev. Browsers use the named .localhost URLs (forward 443 on remote). Local Postgres is Docker via pnpm db:start (Supabase CLI in packages/db).
Scripts (apps/mobile)
| Script | Command | Use case |
|---|---|---|
start | expo start | Local dev, default LAN |
start:localhost | expo start --localhost | Remote + port forwarding (Cursor, VS Code) |
start:tunnel | expo start --tunnel | Remote without forwarding, or physical device |
IDE Support Matrix
| IDE | Port forwarding | Remote (VPC) workflow |
|---|---|---|
| Cursor | Automatic – tunnels ports | Use --localhost; 8081 auto-fwd |
| VS Code | Remote-SSH (Ports view) | Same as Cursor |
| Neovim | Manual / ssh -L | Use --tunnel or SSH port fwd |
| JetBrains | Remote Dev | Forward ports or use --tunnel |
| Other | Varies | Prefer --tunnel for mobile |
Common Gotchas
- Remote + simulator won't connect – Forgot
--localhost? Without it, Expo advertises the VPC IP, which your laptop may not reach. Usestart:localhostwhen using port forwarding. - Physical device + remote Metro – Port forwarding does not help. The device runs the app; its
localhostis the device itself. Usestart:tunnel. - iOS Simulator on Linux VPC – Impossible. Xcode is macOS-only. Use a local Mac or a physical iPhone + tunnel.
- API unreachable from app – Simulator:
https://api.basilic.localhost. Device: LAN/tunnel URL, not.localhost.
Environment Variables
No special env vars are required to distinguish local vs remote. The same .env files work in both contexts. Ensure:
NEXT_PUBLIC_API_URL/NEXT_PUBLIC_APP_URL/EXPO_PUBLIC_API_URLdefault tohttps://api.basilic.localhostandhttps://basilic.localhost. Worktreepnpm devoverrides viaportless get.
Checklist: New Developer Setup
- Clone repo, run
pnpm setup. - Decide: local only, or remote VPC?
- If remote with Cursor: ports are auto-tunneled; use
pnpm --filter @repo/mobile start:localhostfor Expo. With other IDEs: configure port forwarding or usestart:tunnelfor mobile. - Run
pnpm dev(web, API, agents in this repo) andpnpm --filter @repo/mobile start(orstart:localhost/start:tunnelfor remote) from repo root. - Verify simulator/device can load the app and reach the API.
Related
- Dev Tooling – pnpm, Biome, ESLint, Turborepo
- Onboard new developer — playbook
/w-onboardwhen installed under.agents/skills/workflow/w-onboard/(see Skills)