# How to Deploy Camofox-Browser to Fly.io with Multi-Machine Horizontal Scaling

> Deploy Camofox-Browser to Fly.io with multi-machine horizontal scaling. Automatically encode Fly.io machine IDs for stateful scaling across multiple machines without client-side awareness.

- Repository: [jo/camofox-browser](https://github.com/jo-inc/camofox-browser)
- Tags: how-to-guide
- Published: 2026-04-15

---

**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`](https://github.com/jo-inc/camofox-browser/blob/main/lib/fly.js), the `makeTabId()` function generates tab identifiers that encode the machine ID:

```javascript
// 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`](https://github.com/jo-inc/camofox-browser/blob/main/lib/fly.js#L41-L50) intercepts the request and returns a `307` response with a `fly-replay` header:

```javascript
// 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`](https://github.com/jo-inc/camofox-browser/blob/main/server.js#L87-L93) on the `/tabs/:tabId` route, ensuring all tab operations route correctly.

### Configuration Management

Required environment variables are centralized in [`lib/config.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js#L43-L45):

- **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:

```bash
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`](https://github.com/jo-inc/camofox-browser/blob/main/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:

```bash

# 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`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js). You never set `FLY_MACHINE_ID` manually.

### 3. Deploy the Initial Instance

Deploy your application:

```bash
fly deploy

```

This builds the Docker image (which includes pre-downloaded Camoufox and `yt-dlp` binaries) and starts a single machine. Verify the deployment:

```bash
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:

```bash
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:

```bash
fly logs -a my-camofox

```

### Optional: Configure Autoscaling

For automatic horizontal scaling based on CPU utilization, add an autoscaler to your [`fly.toml`](https://github.com/jo-inc/camofox-browser/blob/main/fly.toml):

```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:

```bash
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:

```bash
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:

```javascript
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_ID` into every tab via `makeTabId()` in [`lib/fly.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/fly.js), creating stateful affinity between tabs and Fly machines.
- **Automatic request routing**: The replay middleware in [`lib/fly.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/fly.js) intercepts cross-machine requests and issues `fly-replay` headers, ensuring transparent routing without client-side logic.
- **Simple scaling**: Use `fly scale count N` to add machines; the existing [`fly.toml`](https://github.com/jo-inc/camofox-browser/blob/main/fly.toml) and configuration in [`lib/config.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js) handle the rest automatically.
- **Autoscaling support**: Configure CPU-based autoscalers in [`fly.toml`](https://github.com/jo-inc/camofox-browser/blob/main/fly.toml) to 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`](https://github.com/jo-inc/camofox-browser/blob/main/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`](https://github.com/jo-inc/camofox-browser/blob/main/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`](https://github.com/jo-inc/camofox-browser/blob/main/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.