Run a real Android emulator in the cloud, for free, inside your own GitHub Codespace.
Emuly starts an Android emulator in a codespace on your GitHub account. It uses the Codespaces hours that your GitHub plan already includes (about 30 h/month on Free and 45 h/month on Pro, at 4 cores). Emuly bills nothing.
The main way in is through your coding agent. Copy one prompt into Claude Code, Codex, Cursor, T3 Code or OpenCode. The agent does the setup and asks you only for approvals, such as the GitHub sign-in. You can also watch and touch the phone in the browser.
- You sign in with GitHub. Emuly asks only for the
codespacescope. - Emuly creates a codespace from this repository with
.devcontainer/phone/devcontainer.json. - Emuly installs the emulator and the runner from
phone/over the codespace tunnel. - Your agent or your browser controls the phone through the Emuly API.
- GitHub stops an idle phone after 30 minutes or 4 hours. Stopping keeps the phone.
Only deleting the codespace erases the phone. Each phone uses one codespace, so more phones share the same monthly hours.
You need Node 26 and pnpm.
pnpm install
cp .env.example .env.local # set the OAuth client and EMULY_TOKEN_KEY
pnpm db:migrate # apply the database schema
pnpm dev # http://localhost:4321- Open GitHub Settings → Developer settings → OAuth Apps → New OAuth App.
- Set the callback URL to
http://localhost:4321/auth/callback. - Turn on expiring user tokens. Keep the device flow off.
- Copy the client ID and a new client secret into
.env.local.
pnpm lint # tsc --noEmit
pnpm test # node:test unit tests in lib/**/*.test.ts
pnpm buildThe session-authenticated API includes:
GET /v1/usage: remaining phone hours and storage.POST /v1/phones: create a phone. The body{ "machine": "<name>", "idle_timeout_min": 30 }is optional.POST /v1/webhooks: register a webhook. See webhook setup and signature verification.
GitHub refusals return HTTP 409 with { "error": "github_refused", "message": "<GitHub message>" }.
Each account can create one phone per minute and send six changes per minute.
- The OAuth App asks for
codespaceonly. It never asks forrepo. - GitHub tokens, webhook secrets and runner secrets use AES-256-GCM. Each value is bound to its row.
- Agent sign-in endpoints have a rate limit per client IP.
- Every response sends security headers, such as
frame-ancestors 'none'and HSTS.
To report a security problem, open a private security advisory. Do not open a public issue.
- Set
EMULY_TOKEN_KEYto a new key andEMULY_TOKEN_KEY_OLDto the previous key. Deploy. - Run
pnpm db:rotate-key. It encrypts each old value again with the new key. - When it reports 0 failures, remove
EMULY_TOKEN_KEY_OLDand deploy again.
Users do not see a change. They stay signed in.
- Bring your own Codespace. Emuly never resells GitHub access.
- Use one GitHub account per person. Do not pool free accounts. See the GitHub Terms of Service.
⌘K / Ctrl K command menu · ? shortcuts · g p phones · g 1…g 9 open a phone ·
1–4 phone tabs · c copy stream URL · t toggle dark mode. Delete has no shortcut.
Issues and pull requests are welcome. See CONTRIBUTING.md.