Skip to content

forculus.json schema

forculus.json lives in the directory you deploy. Every field is optional except version. An unknown field in any section is an error, and that includes $schema. forculus.json shows it in use.

{
"version": 1,
"kind": "worker",
"build": {
"install": "pnpm install --frozen-lockfile",
"command": "pnpm build",
"output": "dist"
},
"worker": {
"main": "src/worker.ts",
"compatibility_date": "2026-09-01",
"compatibility_flags": ["nodejs_compat"],
"include": ["dist"],
"wrangler_config": "wrangler.production.jsonc"
},
"container": {
"runtime": "python",
"start": "uvicorn api.main:app --host 0.0.0.0 --port $PORT",
"port": 8080,
"instance_type": "lite",
"instances": 2
},
"kv": true,
"queue": { "path": "/jobs/queue" }
}

A real file has only the sections its kind uses. A flag on forculus deploy overrides the file, and the file overrides the project’s wrangler config and detection.

You can’t open a public path or set a webhook verifier here. Both are set in the dashboard only, so a deploy, including one an agent runs, can’t make any part of an app public.

  • Type: integer. Required.
  • Value: 1.

A file with main, compatibility_date, include, port or instance_type at the top level is in the old flat format, and is refused with the rewrite printed. Any other file without version is refused with forculus.json needs "version": 1. Any other number is refused with forculus.json says version 2, but this CLI only reads version 1.

  • Type: string: static, worker or container.
  • Default: what detection decides.
  • Overridden by: --kind.

static and worker run on Workers, and container on Containers. An app can’t move between the two after it’s created.

How to build the source. The build runs on your machine before anything is uploaded.

  • Type: string, a shell command.
  • Default: the frozen install for your lockfile: npm ci, pnpm install --frozen-lockfile, yarn install --immutable or bun install --frozen-lockfile. With no lockfile in the app, the one at the root of its monorepo. Otherwise none.

Runs only when there’s a package.json and no node_modules where it runs: the app’s directory, or the workspace’s root when the install comes from the root’s lockfile.

  • Type: string, a shell command, run in the app’s directory.
  • Default: npm run build when package.json has a build script. For Next.js with @opennextjs/cloudflare, ./node_modules/.bin/opennextjs-cloudflare build. For a container, none.

A container builds inside its image, so for a container nothing runs on your machine unless this is set. The command runs through sh -c, or cmd on Windows, with the app’s environment variables and without Cloudflare credentials.

  • Type: string, a directory relative to the app.
  • Default: the first of dist, build, out, _site, .output/public, .vitepress/dist and docs/.vitepress/dist that exists after the build. public only when nothing is built. The app’s directory itself when there’s nothing to build.

For static sites only. The directory whose files are served. If the build ran and none of the default directories exists, the deploy stops and asks you to set this.

For a Worker. On a container or a static site, the deploy stops at it rather than ignoring it.

  • Type: string, a path relative to the app.
  • Default: the wrangler config’s main when the project has a wrangler config. Otherwise package.json’s module or main, when it names a file with a default export. Otherwise the first of src/index.ts, index.ts, src/worker.ts and worker.ts, built with wrangler. Otherwise the first of index.js, index.mjs, src/index.js, src/index.mjs, dist/index.js, dist/index.mjs, worker.js, worker.mjs, src/worker.js and src/worker.mjs.
  • Overridden by: --main.

The entry module. A .ts entry in a project with no wrangler config is built with wrangler. Without a build, it must be a .js or .mjs ES module.

  • Type: string, YYYY-MM-DD.
  • Default: the wrangler config’s, else 2026-09-01.
  • Overridden by: --compatibility-date.

A real date, and not one in the future.

  • Type: array of strings.
  • Default: the wrangler config’s flags, else none.
  • Allowed: nodejs_compat, nodejs_compat_v2, nodejs_als, global_fetch_strictly_public, no_nodejs_compat_v2.

When set, the list replaces the wrangler config’s. Any other flag is refused before the build.

  • Type: array of strings, directories relative to the app.
  • Default: the whole app.

The directories to take modules from, for a Worker uploaded without a build. Each must be a directory inside the app. A symlink, a missing directory, or a path that leads outside the app is refused.

  • Type: string, a file name.
  • Default: whichever config wrangler finds itself, including one a build tool generated.

The wrangler config to build with, when the project has more than one or one with an unusual name. It must be a file beside the app ending in .json, .jsonc or .toml. forculus passes it to wrangler with --config, so wrangler builds from that file and not from a config a build tool generated.

For a container. On a Worker or a static site, the deploy stops at it rather than ignoring it.

  • Type: string: node, bun, deno, python, go or rust.
  • Default: from the project’s files: node with a package.json, bun for a Bun project, deno with deno.json, python with requirements.txt, pyproject.toml or Pipfile, go with go.mod, rust with Cargo.toml.

Which Dockerfile to write when the project has none. A project with its own Dockerfile builds from it, and this setting is ignored.

  • Type: string, one line.
  • Default: depends on the runtime. For Node, Bun and Deno, it’s the project’s start script or task, or what its framework builds. For Go and Rust, it’s the binary. For Python, it’s a command for the framework. With none, the deploy stops and asks for this.

The command the written Dockerfile runs. A plain program and its arguments run directly. A command using shell syntax, such as $PORT or &&, runs through sh -c. Go’s image has no shell, so a Go start command can’t use shell syntax. A project with its own Dockerfile ignores this.

  • Type: integer, 1 to 65535.
  • Default: the live version’s port, else 8080.
  • Overridden by: --port.

The port the container listens on. The container’s PORT variable is set to it.

  • Type: string: lite, basic, standard-1, standard-2, standard-3 or standard-4.
  • Default: the live version’s size, else basic.
  • Overridden by: --instance-type.

The size the container runs at. These are Cloudflare Containers’ instance types, smallest first. The workspace’s plan decides which sizes it can use. Team runs lite and basic, Business up to standard-1, and a size the plan doesn’t run is refused before anything is built. See Limits.

  • Type: integer, 1 to 20.
  • Default: the number the app runs now, which is 1 for a new app.
  • Overridden by: --instances.

How many containers the app runs. Each request goes to one of them. Team runs 1 an app and Business up to 4, within the plan’s container units, and more than the plan runs is refused before anything is built. The number stays with the app until something sets it again, as forculus scale does. See More than one container.

It needs CLI 0.1.12 or later. An older CLI stops at a forculus.json that has it, with an error that starts forculus.json isn't valid: unknown field `instances` , so upgrade every CLI that deploys the app first, the GitHub deploy action’s included (its cli-version input).

  • Type: boolean.
  • Default: false.

true gives the app a key-value store of its own, bound as KV, on the next deploy, for a Worker or a container. On Team and Business. false or leaving it out deletes nothing an app already has. See Key-value storage and queues.

A queue of the app’s own, bound as QUEUE, on the next deploy. On Team and Business. Leaving it out deletes nothing an app already has. See Key-value storage and queues.

  • Type: string, a path on the app. Required in queue.

Where we send the queue’s messages, in batches, as a POST. It starts with /, has no query string, and isn’t under /.forculus. A deploy with another path moves them there.