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.
When to write one
Section titled “When to write one”Most apps need no forculus.json. Write one when:
- detection picks the wrong kind, and you’d rather not pass
--kindevery time, - a site’s build writes somewhere other than
dist,build,out,_site,.output/public,.vitepress/distordocs/.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 static site
Section titled “A static site”A VitePress site whose build script isn’t called build:
{ "version": 1, "build": { "command": "npm run docs:build", "output": "docs/.vitepress/dist" }}forculus detecta 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 Worker
Section titled “A Worker”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 container
Section titled “A container”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.13start 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.
Order of settings
Section titled “Order of settings”When the same setting comes from more than one place, the first of these wins:
- A flag on
forculus deploy:--kind,--main,--compatibility-date,--port,--instance-type,--instances. forculus.json.- The project’s wrangler config, for a Worker built with wrangler: its
main,compatibility_dateandcompatibility_flags. - 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.Check it before you deploy
Section titled “Check it before you deploy”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.The old flat format
Section titled “The old flat format”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" }}Every field
Section titled “Every field”The forculus.json schema lists each field with its type, its default and what it overrides.
Next steps
Section titled “Next steps”- forculus.json schema: every field.
- How deploy decides what to build.