
Deploy Camofox Browser | Open Source Browserless Alternative
Stealth Firefox browser API for AI agents, locked down by default
Just deployed
/data
Deploy and Host Camofox Browser on Railway
Camofox Browser is a stealth headless browser served over HTTP: your agent or scraper drives a real Firefox through a small REST API instead of shipping Playwright, a browser binary and 400 MB of Chromium dependencies inside its own container. It is built on Camoufox, a Firefox fork whose anti-detection work happens in the C++ layer rather than in injected JavaScript, so the usual navigator.webdriver and WebGL tells are simply not there to find.
About Hosting Camofox Browser
This template runs the project's own published image — ghcr.io/jo-inc/camofox-browser:1.14.0, pinned, with Camoufox 135.0.1 already baked in — as a single service with a volume. There is no database and nothing to build: the deploy is one container pull, and the browser is warm within about a minute.
Two decisions here are worth knowing about before you deploy, because they are the ones that differ from the obvious setup.
The API is closed. The server has three separate keys, and only one of them is the master gate: CAMOFOX_ACCESS_KEY. It is generated for you and required on every route except /health. This matters more than it sounds — the API includes /tabs/{id}/evaluate, which runs arbitrary JavaScript in a browser that sits inside your private Railway network. A deployment of this server without that key set is a public remote-code-execution surface with a side of SSRF, not merely an open scraper. CAMOFOX_API_KEY (cookie import) and CAMOFOX_ADMIN_KEY (POST /stop) are generated separately so you can hand one out without handing out everything.
Everything durable is on the volume. Browser profiles, imported cookies, upload staging and Playwright traces all live under /data. Upstream defaults them to ~/.camofox, which on Railway is the ephemeral layer — the practical effect of leaving that alone is that a logged-in session you spent effort establishing quietly disappears on your next redeploy.
Crash telemetry is off. Upstream ships anonymized crash and hang reporting turned on, and it files those reports as public GitHub issues against jo-inc/camofox-browser. That is a reasonable default for a project collecting its own field data and a surprising one for someone who clicked Deploy, so this template sets CAMOFOX_CRASH_REPORT_ENABLED=false. Set it to true if you would like to help, or point CAMOFOX_CRASH_REPORT_URL at an endpoint of your own.
Common Use Cases
- Browser tool for an AI agent: eleven MCP-shaped operations — create tab, navigate, snapshot, click, type, scroll, screenshot, evaluate — with accessibility snapshots that give the model element refs instead of raw HTML.
- Scraping sites that fingerprint the browser: real Firefox with a consistent fingerprint (spoofed WebGL renderer, plausible screen and hardware, no
webdriverflag) rather than a patched Chromium that announces itself. - Logged-in automation: import a Netscape cookie file once, and the per-user profile — cookies, localStorage, optionally IndexedDB — is checkpointed to the volume and restored on the next session, across redeploys.
- Shared browser for a fleet of small workers: session isolation is per
userId, with per-user and global tab caps, so several jobs can share one deployment without seeing each other's state. - YouTube transcripts without a browser:
yt-dlpships in the image and the bundled plugin uses it directly, which is far cheaper than driving a page.
Dependencies for Camofox Browser Hosting
Deployment Dependencies
- The upstream image,
ghcr.io/jo-inc/camofox-browser— no build step, no fork. - Camoufox 135.0.1-beta.24, pre-baked into the image.
- A 1 GB volume at
/data. Nothing else: no database, no queue, no cache.
Implementation Details
- Port. The image bakes
CAMOFOX_PORT=9377and the app prefers it over Railway's injectedPORT, soPORTis pinned to 9377 to keep the platform routing to the port that is actually listening. Leave it alone. - No healthcheck.
/healthreturns 503 while the server is relaunching a browser that died — a state to recover from, not one to fail a deploy on. Verified across three consecutive deploys. - Memory. Measured on this template: 172 MB once the browser has shut down for idleness, 300–500 MB with it warm, and up to 930 MB with several tabs open across concurrent sessions. Firefox is genuinely heavy, and upstream's own telemetry shows native memory growing by several hundred MB over long runs; the server watches for that itself and recycles the browser (
BROWSER_RSS_RESTART_THRESHOLD_MB, default 1500). Budget roughly $3–10/month depending on how much you actually browse — an instance that sits idle most of the day lands near the bottom of that.BROWSER_IDLE_TIMEOUT_MScloses the browser after five minutes with no sessions and is the single biggest lever on the number; verified here, and/healthkeeps answering 200 while the browser is stopped, so nothing else notices. - Concurrency. Defaults are 3 concurrent tabs per user, 10 tabs per session, 50 tabs and 50 sessions globally. Raise them alongside the memory budget, not before it.
- Interactive mode is off. The noVNC plugin can put a live view of the browser on a web page; on a public domain that is a second door, so it stays disabled.
Verifying your deployment
URL=https://your-service.up.railway.app
KEY=
curl -s $URL/health # public, no key needed
curl -s -o /dev/null -w '%{http_code}\n' $URL/tabs # expect 401
TAB=$(curl -s -X POST $URL/tabs -H "Authorization: Bearer $KEY" \
-H 'Content-Type: application/json' \
-d '{"userId":"me","sessionKey":"s1","url":"https://example.com"}')
echo "$TAB"
Then check the stealth is real, which is the whole point of the product:
curl -s -X POST $URL/tabs//evaluate -H "Authorization: Bearer $KEY" \
-H 'Content-Type: application/json' \
-d '{"userId":"me","expression":"navigator.webdriver"}'
# -> {"ok":true,"result":false}
GET / (authenticated) returns the server's own status. The full route list is in the project's OpenAPI spec.
Why Deploy Camofox Browser on Railway?
A headless browser is the awkward part of a scraping or agent stack: too heavy to bundle into every worker, too stateful to run as a function, and too memory-hungry to ignore. Running it as its own Railway service gives it a private hostname your other services can call, a volume that keeps logged-in profiles across deploys, and per-second billing that actually rewards the idle shutdown built into the server. Because the image is upstream and pinned, upgrading is a one-line change you choose to make rather than something that happens to you on a redeploy.
Template Content