Skip to content

forculus.json

forculus.json sits in the directory you deploy and tells forculus deploy what the files don’t. Every setting is optional except version, and each one overrides only what detection would have decided.

Most apps need no forculus.json. Write one when:

  • detection picks the wrong kind, and you’d rather not pass --kind every time,
  • a site’s build writes somewhere other than dist, build, out, _site, .output/public, .vitepress/dist or docs/.vitepress/dist,
  • a Worker’s entry isn’t where detection looks, or it needs a compatibility date or flag,
  • a container listens on a port other than 8080, runs at a size other than basic, or starts with a command detection can’t work out.

Every deploy from the directory reads the file, including one an agent runs. A flag applies to one deploy only.

A VitePress site whose build script isn’t called build:

{
"version": 1,
"build": {
"command": "npm run docs:build",
"output": "docs/.vitepress/dist"
}
}
Terminal window
forculus detect
a static site, served by Workers (VitePress)
because package.json depends on vitepress, builds with `npm run docs:build`, deploys docs/.vitepress/dist/
install: npm ci
build: npm run docs:build
deploys: docs/.vitepress/dist/

build.output is the directory whose files are served. Static sites covers what’s left out of it.

A TypeScript Worker whose entry is src/worker.ts, with Node’s APIs turned on:

{
"version": 1,
"worker": {
"main": "src/worker.ts",
"compatibility_date": "2026-09-01",
"compatibility_flags": ["nodejs_compat"]
}
}

A worker section is enough to make the app a Worker, even beside an index.html, so "kind": "worker" is optional here. A container section makes it a container the same way. A file with both sections has to say which with kind, and a section for another kind than the one deployed stops the deploy rather than being ignored:

error: forculus.json has a worker section, but this deploys as a static site, which would ignore it. Remove the section, or deploy it as a worker with "kind": "worker".

If the project has no wrangler config, forculus writes a temporary one to build the .ts entry with wrangler, and deletes it after the build.

If the project has more than one wrangler config, name the one to build with:

{ "version": 1, "worker": { "wrangler_config": "wrangler.production.jsonc" } }

The file has to sit beside the app and end in .json, .jsonc or .toml. Workers and frameworks covers what a wrangler config may ask for.

A FastAPI app whose module isn’t main.py at the top of the project, run at the smaller lite size:

{
"version": 1,
"container": {
"start": "uvicorn api.main:app --host 0.0.0.0 --port $PORT",
"port": 8080,
"instance_type": "lite"
}
}
a container
because forculus.json has a container section, runs as a python container
port: 8080
size: lite
start: uvicorn api.main:app --host 0.0.0.0 --port $PORT
python: 3.13

start and runtime only apply when the project has no Dockerfile of its own. port, instance_type and instances apply either way. Containers has the defaults for each runtime.

When the same setting comes from more than one place, the first of these wins:

  1. A flag on forculus deploy: --kind, --main, --compatibility-date, --port, --instance-type, --instances.
  2. forculus.json.
  3. The project’s wrangler config, for a Worker built with wrangler: its main, compatibility_date and compatibility_flags.
  4. Detection and the defaults.

The next forculus deploy without the flag uses forculus.json again, whoever runs it.

A flag for another kind of app, such as --instances on a Worker or --main on a container, stops the deploy before anything is built or uploaded.

A container’s port and size work differently when forculus.json doesn’t set them. A deploy then keeps the live version’s value, so after one deploy with --port 3000 the app stays on port 3000 until something else sets the port. The deploy says where each value came from:

It listens on port 3000, kept from the previous version. It runs at size basic.

forculus detect reads forculus.json, so you see a mistake before anything builds. An unknown field in any section is an error, so a typo can’t pass as a setting:

error: forculus.json isn't valid: unknown field `compatibility_dat`, expected one of `main`, `compatibility_date`, `compatibility_flags`, `include`, `wrangler_config` at line 1 column 47.

Other errors it reports before building:

error: forculus.json needs "version": 1.
error: forculus.json says version 2, but this CLI only reads version 1.
error: forculus.json asks for the compatibility flag streams_enable_constructors, which we don't allow. You can use nodejs_compat, nodejs_compat_v2, nodejs_als, global_fetch_strictly_public, no_nodejs_compat_v2.
error: forculus.json's container.instance_type is "standard", which isn't a size we run. Use one of lite, basic, standard-1, standard-2, standard-3, standard-4.
error: forculus.json's container.instances is 30. An app runs from 1 to 20 containers.
error: forculus.json's wrangler_config is config/wrangler.jsonc. It must name a file next to the app, like wrangler.jsonc.

An earlier forculus.json had no sections: kind, main, compatibility_date, include, port and instance_type were at the top level. forculus refuses that file and prints the same settings in the current format for you to paste, kind included:

error: forculus.json is in the old format. Rewrite it as:
{
"version": 1,
"kind": "worker",
"worker": {
"main": "dist/index.js"
}
}

The forculus.json schema lists each field with its type, its default and what it overrides.