Cloud sandboxes
environment.json — Automatic dev servers
Last updated: 2026-09-21
Start dev servers, watchers, and workers automatically when your environment boots or wakes. Each service in environment.json gets a named terminal tab where you can watch its output, stop it, and restart it.
What it looks like#
Create environment.json at the root of your repository and commit it:
{
"services": [{ "name": "dev", "cmd": "bun install && bun dev" }]
}Next wake, RepoGo runs the command and opens a terminal tab named dev with the live output. That's the whole setup — nothing to configure in the app.
Two habits worth forming from the first file:
- Chain the install into
cmdwith&&. A fresh clone or cold boot may not havenode_modulesyet; install is a fast no-op when it does. - Start with one service. Add more as you need them.
No file yet? The web dashboard's Environment tab has a Create environment.json button that writes a starter file at the repo root and opens it in the editor. Once a file exists, the pencil in the tab header reopens it.
Which repos run it#
- Cloud Sandbox: every repo's
environment.jsonruns. - Paired Mac: only repos that belong to a GitHub account you have connected in RepoGo — cloned through RepoGo, or a folder you picked whose
originis under one of your accounts. Anything else (a repo cloned from someone else's account, a folder with no GitHub remote) is skipped: its services show as stopped and you can start them from the terminal yourself. This is deliberate —environment.jsonruns commands on your machine, so it only runs for code that is yours. Requires RepoGo Host 1.0.1 (16) or later; earlier versions run every repo's file.
The file#
services is the only required field. A full example:
{
"version": 1,
"services": [
{
"name": "api",
"cmd": "bun install && bun run start",
"target": "8080",
"expose": true,
"envFrom": ["api-secrets"]
},
{
"name": "web",
"cmd": "bun install && bun dev",
"cwd": "apps/frontend",
"target": "3000",
"expose": true,
"dependsOn": ["api"],
"env": { "NEXT_PUBLIC_API_URL": "${ENVIRONMENT_URL_API}" }
}
]
}| Field | Required | What it does |
|---|---|---|
name | Yes | Terminal tab name and how other services refer to this one. Lowercase letters, numbers, dashes: web, api, worker. |
cmd | Yes | The command, run in a shell. Chain installs with &&; pipes, $VARS, and scripts work. |
type | No | "service" (default) for long-running processes. "setup" for run-once steps that exit — installs, migrations, codegen. |
cwd | No | Folder to run in, relative to the repo root. Use it for monorepos instead of cd in cmd. |
target | No | Where the service serves locally: a port as a string ("3000") or a hostname ("web.localhost"). Lets other services find it. Does not make it public on its own. (port is accepted as an alias.) |
expose | No | true opens target to the internet with a public URL. Defaults to false. Requires target. |
readyWhen | No | Readiness check so dependents wait until this service is serving: { "port": 8080 } or { "http": "http://localhost:8080/health" }. Add "timeoutMs" to cap the wait. |
dependsOn | No | Services to start first. A setup is waited on until it exits; a service until it spawns, or until it is ready if it has readyWhen. |
env | No | Extra environment variables as name/value pairs. Can reference other services' ${ENVIRONMENT_*} variables. |
envFrom | No | Named secret sources to load before starting, e.g. ["api-secrets"]. Names only — the file lives on your Mac. |
autoStart | No | Defaults to true. false defines the service as a stopped tab you start by hand. |
autoRestart | No | true restarts the service when its process exits. Off by default. |
idleStop | No | On a Mac, what happens when nobody is viewing the workspace: omitted = stopped after 3 minutes, "now", or "never". See When services stop on their own. |
version | No | Format version. 1, or leave it out. |
Examples#
Single app — install, run, expose:
{
"services": [{ "name": "web", "cmd": "npm install && npm run dev", "target": "3000", "expose": true }]
}Monorepo with a shared install — one setup step installs at the root; the apps depend on it and on each other:
{
"services": [
{ "name": "install", "type": "setup", "cmd": "pnpm install" },
{
"name": "api",
"cmd": "pnpm --filter api dev",
"target": "8080",
"expose": true,
"dependsOn": ["install"],
"readyWhen": { "port": 8080 }
},
{
"name": "web",
"cmd": "pnpm --filter web dev",
"target": "3000",
"expose": true,
"dependsOn": ["install", "api"],
"env": { "NEXT_PUBLIC_API_URL": "${ENVIRONMENT_URL_API}" }
}
]
}Public URLs#
Nothing is exposed unless you ask. To make a service public, give it a target and expose: true. On boot, RepoGo assigns it a stable public address like https://yourmac-3000-x7k2.repogo.dev — no port in the URL, generated for you so names never collide, and the same across sleeps and restarts.
expose: true is re-applied on every wake, so you never have to turn it back on. You can also open or close targets from the environment's screen in the app at any time.
Portless: friendly hostnames#
If your dev setup serves on hostnames instead of ports — web.localhost, api.localhost — set target to the hostname and RepoGo proxies straight to it. Two apps that both want 3000 can't clash when each owns a name, and expose, dependsOn, readyWhen, and the ${ENVIRONMENT_*} variables all work the same.
{
"services": [
{ "name": "web", "cmd": "bun install && bun dev", "target": "web.localhost", "expose": true },
{ "name": "api", "cmd": "bun install && bun start", "target": "api.localhost", "expose": true }
]
}A bare label is shorthand: "target": "web" means web.localhost. Anything with a dot, or the literal localhost, is used as written.
RepoGo forwards requests to http://<hostname> inside the environment with the right Host header; your project or a small local proxy has to actually answer on that name. If a hostname target reports "local server unreachable," nothing inside the box is serving it yet. A port target always works with no extra setup.
Connecting services to each other#
Don't hardcode another service's address. Every service gets variables describing the other services, usable in cmd or env:
| Variable | Example | Available when |
|---|---|---|
ENVIRONMENT_TARGET_<NAME> | 8080 | the other service has a target |
ENVIRONMENT_URL_<NAME> | https://yourmac-8080-x7k2.repogo.dev | the other service is exposed |
ENVIRONMENT_DOMAIN_<NAME> | yourmac-8080-x7k2.repogo.dev | the other service is exposed |
<NAME> is the other service's name uppercased, dashes to underscores: my-api → ENVIRONMENT_URL_MY_API.
URL— fullhttps://…. For anything a browser hits:NEXT_PUBLIC_API_URL, OAuth callbacks, webhooks.DOMAIN— host only. For CORS allowlists and cookie domains.TARGET— the local port or hostname. For server-to-server calls inside the environment:http://localhost:$ENVIRONMENT_TARGET_API(port) orhttp://$ENVIRONMENT_TARGET_API(hostname).
A private service (target, no expose) provides only TARGET. These values are read at start, so if you change a URL while things are running, restart the services that use it.
Secrets stay on your Mac#
The repo says what a service needs; the app says where it lives.
- In
environment.json, list named sources:"envFrom": ["api-secrets"]. Names only, nothing sensitive. - In the app's Env Sources, point each name at a file on your Mac, once.
Save or refresh the source in Env Sources to pull a protected snapshot to your iPhone. When a service starts, approve sharing that snapshot; RepoGo loads the variables into the service in memory without writing them to the environment's disk or a snapshot. A teammate points the same name at their own file, so one environment.json works for everyone. Full flow: Env Sources.
When services stop on their own#
On a Mac, every project shares one machine, so RepoGo stops managed services shortly after you stop using a workspace:
- While any tab or device has the workspace open, everything stays up.
- When the last one closes, a 3-minute countdown appears on the workspace tab, next to a Kill app button.
- Come back before it runs out and nothing stops.
- When it expires, RepoGo stops the services it started. Terminals you opened by hand are left alone.
idleStop changes this per service:
idleStop | When you stop viewing the workspace |
|---|---|
| (omitted) | Stopped after the 3-minute countdown. Right for almost everything. |
"now" | Stopped immediately. For a heavy dev server or a port you want back at once. |
"never" | Never stopped by the countdown. For a local database or a tunnel. |
Kill app always stops everything, including "never" services. If every service is "never", no countdown is shown.
Cloud Sandboxes ignore idleStop: a sandbox is scoped to one project and already stops everything when it sleeps, so its services stay up the whole time it is awake.
Multiple repositories#
An environment can hold several repos, each with its own environment.json. On wake, RepoGo starts the services from every repo. A repo without the file starts nothing. Service names are scoped per repo, so two projects can both have web. dependsOn only orders services within the same repo.
Next steps#
- Cloud Sandboxes on Vercel
- Env Sources — secrets for
envFrom - Actions — one-off commands, as opposed to services
- File types — all four RepoGo magic files