How to Deploy Camofox-Browser to Fly.io with Multi-Machine Horizontal Scaling
Camofox-browser automatically encodes the Fly.io machine ID into every tab identifier and uses an Express middleware to issue fly-replay headers, enabling stateful horizontal scaling across multiple Fly machines without client-side awareness.
Camofox-browser is an open-source browser automation framework built on Camoufox. When deployed to Fly.io with multi-machine horizontal scaling, it leverages the platform's machine-centric architecture by embedding FLY_MACHINE_ID directly into tab IDs. This design allows the built-in replay middleware to route cross-machine requests transparently, ensuring each browser tab remains anchored to its originating Fly machine regardless of which instance receives the HTTP request.
Architecture and Key Code Paths
Fly.io runs each service instance in its own lightweight VM called a "machine." When you scale to multiple machines, they share a global DNS name but each receives a unique FLY_MACHINE_ID. The camofox-browser repository contains specialized logic to handle this distributed state.
Tab ID Encoding and Machine Ownership
In lib/fly.js, the makeTabId() function generates tab identifiers that encode the machine ID:
// lib/fly.js L16-L19
function makeTabId(machineId) {
return machineId ? `${machineId}_${uuidv4()}` : uuidv4();
}
When FLY_MACHINE_ID is present, tabs are created with IDs like 01a2b3c4d5e6f7_9d2f1e8c-.... The parseTabOwner() function (L21-L30) extracts the machine prefix before the underscore, and isLocalTab() (L32-L35) validates whether the current machine owns the requested tab.
The Fly Replay Middleware
For requests targeting tabs owned by other machines, the replay middleware in lib/fly.js intercepts the request and returns a 307 response with a fly-replay header:
// lib/fly.js L41-L50
function replayMiddleware(req, res, next) {
const owner = parseTabOwner(req.params.tabId);
if (owner && !isLocalTab(owner)) {
res.setHeader('fly-replay', `instance=${owner}`);
return res.status(307).send('Replaying to owner machine');
}
next();
}
This header instructs Fly's edge load balancer to forward the request to the specified machine instance. The middleware is mounted in server.js on the /tabs/:tabId route, ensuring all tab operations route correctly.
Configuration Management
Required environment variables are centralized in lib/config.js:
- FLY_MACHINE_ID: Auto-generated by Fly.io per machine (read-only)
- FLY_APP_NAME: Your Fly application name
- FLY_API_TOKEN: For internal Fly API calls (auto-injected)
- CAMOFOX_API_KEY: Your secret API key for client authentication
Prerequisites
Before deploying, ensure you have the following:
- Fly CLI installed (
curl -L https://fly.io/install.sh | sh) - Docker available for local builds (optional but recommended)
- Git to clone
https://github.com/jo-inc/camofox-browser - OpenSSL to generate the API secret (
openssl rand -hex 32)
Step-by-Step Deployment Guide
1. Create the Fly.io Application
Clone the repository and launch your app:
git clone https://github.com/jo-inc/camofox-browser
cd camofox-browser
fly launch
When prompted:
- App name: Choose a unique name (e.g.,
my-camofox) - Deploy now: Select "No" (you need to set secrets first)
The repository already includes a production-ready fly.toml that configures the Docker build, exposes port 9377, and sets up health checks.
2. Configure Environment Secrets
Set the required secrets before your first deployment:
# Generate and set your API key
fly secrets set CAMOFOX_API_KEY=$(openssl rand -hex 32)
# Set your app name
fly secrets set FLY_APP_NAME=my-camofox
The Fly platform automatically injects FLY_MACHINE_ID and FLY_API_TOKEN into each machine at runtime via loadConfig() in lib/config.js. You never set FLY_MACHINE_ID manually.
3. Deploy the Initial Instance
Deploy your application:
fly deploy
This builds the Docker image (which includes pre-downloaded Camoufox and yt-dlp binaries) and starts a single machine. Verify the deployment:
fly status
You should see one machine entry with a unique FLY_MACHINE_ID.
4. Enable Horizontal Scaling
To run multiple instances and enable multi-machine horizontal scaling, scale your app:
fly scale count 3
Fly.io spins up two additional machines, each receiving its own FLY_MACHINE_ID. Because camofox-browser encodes this ID into every new tab ID, requests that hit the wrong machine are automatically replayed to the owning instance via the fly-replay header.
Monitor the scaling process:
fly logs -a my-camofox
Optional: Configure Autoscaling
For automatic horizontal scaling based on CPU utilization, add an autoscaler to your fly.toml:
[[services]]
internal_port = 9377
protocol = "tcp"
[[services.autoscaling]]
target_cpu = 70
min_instances = 2
max_instances = 10
Run fly deploy to apply changes. The platform automatically adds or removes machines, and the tab-routing logic continues to function because each new machine receives its own unique FLY_MACHINE_ID.
Verifying Multi-Machine Operation
Creating a Tab
Create a tab using the REST API:
curl -X POST http://my-camofox.fly.dev/tabs/agent1 \
-H "Authorization: Bearer YOUR_CAMOFOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.google.com"}'
The response contains a tabId like 01a2b3c4d5e6f7_9d2f1e8c-.... The prefix (01a2b3c4d5e6f7) identifies the specific Fly machine that owns this browser instance.
Testing Cross-Machine Routing
Access the tab from any machine:
curl http://my-camofox.fly.dev/tabs/agent1/01a2b3c4d5e6f7_9d2f1e8c-.../snapshot
If the request lands on a different Fly machine, the replay middleware returns a 307 response with fly-replay: instance=01a2b3c4d5e6f7. Fly's edge then retries the request on the correct machine transparently.
Programmatically detect replays:
const res = await fetch('https://my-camofox.fly.dev/tabs/agent1/<tabId>/snapshot');
if (res.status === 307) {
console.log('Request was replayed to the owning machine');
}
Summary
- Machine-aware tab IDs: Camofox-browser encodes
FLY_MACHINE_IDinto every tab viamakeTabId()inlib/fly.js, creating stateful affinity between tabs and Fly machines. - Automatic request routing: The replay middleware in
lib/fly.jsintercepts cross-machine requests and issuesfly-replayheaders, ensuring transparent routing without client-side logic. - Simple scaling: Use
fly scale count Nto add machines; the existingfly.tomland configuration inlib/config.jshandle the rest automatically. - Autoscaling support: Configure CPU-based autoscalers in
fly.tomlto dynamically adjust machine count while maintaining tab ownership consistency.
Frequently Asked Questions
How does camofox-browser route requests to the correct machine when scaling horizontally?
Camofox-browser embeds the Fly.io machine ID into every tab identifier using the format {machineId}_{uuid}. When a request arrives at the wrong machine, the replayMiddleware in lib/fly.js detects the ownership mismatch via parseTabOwner() and returns a 307 response with a fly-replay: instance=<owner> header. Fly's internal load balancer then automatically reroutes the request to the owning machine before the client receives the final response.
What environment variables are required for Fly.io deployment?
You must set CAMOFOX_API_KEY (for API authentication) and FLY_APP_NAME (your application identifier). The variables FLY_MACHINE_ID and FLY_API_TOKEN are automatically injected by the Fly platform into each machine's environment and read by loadConfig() in lib/config.js at startup. Never manually set FLY_MACHINE_ID as it is unique per machine instance.
Can I use autoscaling with camofox-browser without breaking tab sessions?
Yes. Because each machine generates tab IDs that include its own FLY_MACHINE_ID, new tabs created on autoscaled machines automatically carry the correct ownership metadata. Existing tabs remain bound to their original machines via the fly-replay routing mechanism. Configure autoscaling in fly.toml using [[services.autoscaling]] blocks with target_cpu thresholds, then deploy with fly deploy.
Is the fly-replay header handling transparent to API clients?
Yes. The replay process is handled entirely within Fly.io's edge network and the camofox-browser middleware. Clients may observe a brief 307 redirect response if they inspect headers, but the final response comes from the correct machine with the requested tab data. No client-side changes are required to support horizontal scaling.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →