# Configuring Build Targets for Node.js and Cloudflare Workers Deployment in Flue

> Configure build targets for Node.js and Cloudflare Workers with Flue using the --target flag. Deploy identical agent code to both runtimes effortlessly. Learn how now.

- Repository: [Astro/flue](https://github.com/withastro/flue)
- Tags: how-to-guide
- Published: 2026-05-11

---

**Flue compiles agent projects to either Node.js (generating `dist/server.mjs`) or Cloudflare Workers (generating `dist/worker.mjs` plus `wrangler.jsonc`) using the `--target` flag, while sharing identical source code and project layout between both runtimes.**

When deploying applications built with the withastro/flue framework, selecting the appropriate build target determines your runtime environment and scaling characteristics. Configuring build targets for Node.js and Cloudflare Workers deployment requires understanding how Flue adapts the same agent source code into distinct artifacts optimized for traditional servers versus serverless edge functions.

## Build Target Overview

Flue supports two primary compilation targets controlled via the `--target` CLI flag:

| Target | Output Artifact | Runtime | Best For |
|--------|----------------|---------|----------|
| **Node.js** | `dist/server.mjs` | Hono server on Node.js | Self-hosted services, Docker containers, VPS, Railway |
| **Cloudflare Workers** | `dist/worker.mjs` + `dist/wrangler.jsonc` | Cloudflare Workers with Durable Objects | Edge functions, ultra-low latency APIs, per-session isolation |

Both targets consume the same project layout—agents live under `./agents`, `./roles` or `./.flue/agents`, `./.flue/roles`—and accept identical environment variable configurations via `--env <path>`.

## Configuring the Node.js Build Target

The Node.js target compiles your Flue application into a standalone Hono server that listens on `process.env.PORT` (defaulting to 3000).

### Project Initialization

Initialize a Node project and install the SDK and CLI:

```bash
mkdir my-flue-server && cd my-flue-server
npm init -y
npm install @flue/sdk valibot
npm install -D @flue/cli

```

Create an agent under `.flue/agents/`:

```typescript
// .flue/agents/translate.ts
import type { FlueContext } from '@flue/sdk/client';
import * as v from 'valibot';

export const triggers = { webhook: true };

export default async function ({ init, payload }: FlueContext) {
  const harness = await init({ model: 'openai/gpt-5.5' });
  const session = await harness.session();
  
  const { data } = await session.prompt(
    `Translate this to ${payload.language}: "${payload.text}"`,
    { schema: v.object({ 
      translation: v.string(), 
      confidence: v.picklist(['low','medium','high']) 
    }) }
  );
  
  return data;
}

```

*(Reference: [`docs/deploy-node.md`](https://github.com/withastro/flue/blob/main/docs/deploy-node.md) lines 31-52)*

### Local Development

Run the development server with hot reloading on port 3583:

```bash
npx flue dev --target node --env .env

```

The dev server implements the same Hono-based routing found in [`packages/sdk/src/runtime/flue-app.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/runtime/flue-app.ts), exposing health checks at `GET /health`, manifest at `GET /agents`, and invocation endpoints at `POST /agents/:name/:id`.

### Production Builds

Compile for production:

```bash
npx flue build --target node

```

Load environment variables before starting the compiled server:

```bash
set -a; source .env; set +a
node dist/server.mjs

```

The server automatically binds to the port specified in `process.env.PORT`.

### Containerization

Deploy using Docker by wrapping the built artifact:

```dockerfile
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
ENV PORT=8080
EXPOSE 8080
CMD ["node", "dist/server.mjs"]

```

*(Reference: [`docs/deploy-node.md`](https://github.com/withastro/flue/blob/main/docs/deploy-node.md) lines 90-103)*

## Configuring the Cloudflare Workers Build Target

The Cloudflare target generates a Worker script compatible with Durable Objects, R2 buckets, and optional sandbox containers.

### Project Setup

Install the Workers SDK alongside Flue packages:

```bash
mkdir my-flue-worker && cd my-flue-worker
npm init -y
npm install @flue/sdk valibot agents
npm install -D @flue/cli wrangler

```

Agent code remains identical to the Node.js target, though you may reference Cloudflare-specific bindings:

```typescript
import type { FlueContext } from '@flue/sdk/client';

export default async function ({ init, env }: FlueContext) {
  // Access Cloudflare bindings through env
  const harness = await init({ 
    model: 'anthropic/claude-sonnet-4-6',
    // Additional Cloudflare-specific configuration
  });
  // ...
}

```

### Development with Wrangler

The dev server proxies to a local Wrangler runtime:

```bash
npx flue dev --target cloudflare --env .env

```

As implemented in the CLI, this command launches the Wrangler dev environment on port 3583 with automatic reloading on source changes.

### Building and Deploying

Generate the Worker artifact:

```bash
npx flue build --target cloudflare

```

This creates `dist/worker.mjs` and merges your root `wrangler.jsonc` with Flue's internal Durable Object bindings into `dist/wrangler.jsonc`.

Deploy via Wrangler:

```bash
npx wrangler deploy --secrets-file .env

```

*(Reference: [`docs/deploy-cloudflare.md`](https://github.com/withastro/flue/blob/main/docs/deploy-cloudflare.md) lines 91-96)*

### Advanced Bindings

For knowledge bases using R2 storage, mount a virtual sandbox:

```typescript
import { getVirtualSandbox } from '@flue/sdk/cloudflare';
import type { FlueContext } from '@flue/sdk/client';

export default async function ({ init, payload, env }: FlueContext) {
  const sandbox = await getVirtualSandbox(env.KNOWLEDGE_BASE);
  const harness = await init({ 
    sandbox, 
    model: 'openrouter/moonshotai/kimi-k2.6' 
  });
  const session = await harness.session();
  
  return await session.prompt(
    `Search the knowledge base for "${payload.query}" and summarise.`
  );
}

```

Declare R2 buckets and Durable Objects in your root `wrangler.jsonc`; Flue automatically merges these declarations during the build process.

*(Reference: [`docs/deploy-cloudflare.md`](https://github.com/withastro/flue/blob/main/docs/deploy-cloudflare.md) lines 86-97)*

## Shared Configuration Interface

Both targets respect the same configuration surface in [`flue.config.ts`](https://github.com/withastro/flue/blob/main/flue.config.ts) or via CLI flags:

- **Target specification**: `--target node|cloudflare` or `export default defineConfig({ target: 'node' })` as shown in [`examples/hello-world/flue.config.ts`](https://github.com/withastro/flue/blob/main/examples/hello-world/flue.config.ts)
- **Environment loading**: `--env .env` injects variables before build or runtime
- **Output directory**: `--output path/to/out` or the `output` config field
- **Project root**: Automatically detects `.flue/` subdirectories or bare `agents/` folders

## Universal Agent Patterns

The following agent works identically under both targets, with model selection abstracted via environment variables:

```typescript
import type { FlueContext } from '@flue/sdk/client';
import * as v from 'valibot';

export const triggers = { webhook: true };

export default async function ({ init, payload }: FlueContext) {
  const harness = await init({
    model: process.env.DEFAULT_MODEL ?? 'openai/gpt-5.5',
  });
  const session = await harness.session();

  const { data } = await session.prompt(
    `Translate this to ${payload.language}: "${payload.text}"`,
    { schema: v.object({ 
      translation: v.string(), 
      confidence: v.picklist(['low','medium','high']) 
    }) }
  );

  return data;
}

```

Run this unchanged with `flue dev --target node` or `flue dev --target cloudflare`.

## Summary

- **Choose Node.js** (`--target node`) for self-hosted deployments requiring traditional process-based execution via Hono server ([`packages/sdk/src/runtime/flue-app.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/runtime/flue-app.ts))
- **Choose Cloudflare Workers** (`--target cloudflare`) for edge deployment with automatic Durable Object support and optional R2 bindings
- Both targets read from identical source layouts in `./agents` or `./.flue/agents`
- Always pass sensitive credentials via `--env .env` rather than committing them to version control
- The Cloudflare target automatically merges user-defined bindings from `wrangler.jsonc` into the generated `dist/wrangler.jsonc`

## Frequently Asked Questions

### How do I switch between Node.js and Cloudflare Workers in the same project?

Change the `--target` flag when running `flue dev` or `flue build`. The same agent source code compiles to both targets without modification, though Cloudflare-specific features like `env.KNOWLEDGE_BASE` will only function in the Workers environment.

### Where does Flue store the compiled output for each target?

Node.js builds generate `dist/server.mjs` (a Hono application), while Cloudflare builds create `dist/worker.mjs` alongside a merged `dist/wrangler.jsonc` configuration. Control the output directory using the `--output` flag or `output` field in [`flue.config.ts`](https://github.com/withastro/flue/blob/main/flue.config.ts).

### Can I use Durable Objects when developing locally?

Yes. When running `flue dev --target cloudflare`, the CLI proxies to Wrangler's local dev environment, which simulates Durable Objects and other Cloudflare platform features. The Node.js target does not support Durable Objects, as it runs a standard Hono server without Cloudflare's infrastructure.

### How are environment variables handled differently between targets?

Both targets load variables from the file specified by `--env`. In Node.js, these become `process.env` variables. In Cloudflare Workers, Wrangler injects them as bindings accessible via the `env` object passed to `FlueContext`, matching standard Workers runtime behavior.