Skip to content

Sites and runtimes

A site is a deployable app placed on one or more servers. Its framework preset decides the defaults, and its runtime decides how it runs on the server. This page explains both.

Property Meaning Example
Name Display name, unique in the organization Shop
Slug URL-safe id used in paths, domains and the CLI (^[a-z0-9][a-z0-9-]{0,62}$) shop
Framework Preset for defaults laravel
Runtime How it runs frankenphp
Build mode How it is built: native or docker native
Source Repository + branch, Docker image, or Compose file acme/shop@main
Servers Targets. The first (or the chosen one) is the leader. app-1 (leader), app-2
Web directory Document root relative to the release (PHP, static) public
App port Port the app listens on (Node, Bun, Deno, Docker), allocated from 3000–3999 per server 3004
Variables Encrypted environment, written to each release’s .env APP_ENV=production
Shared paths Files and directories kept across releases storage, .env
Deploy script Bash with macros; see Deploy scripts

A preset fills in the runtime, web directory, shared paths, deploy script, initial variables, Laravel toggles and health check path. Everything stays editable.

Preset API value Runtimes (first = default) Web dir Shared paths Health path
Laravel laravel frankenphp, php-fpm public storage/, .env /up
Statamic statamic frankenphp, php-fpm public storage/, .env, content/, users/, public/assets/ none
Symfony symfony frankenphp, php-fpm public var/log/, .env.local none
WordPress wordpress frankenphp, php-fpm (root) wp-content/uploads/, wp-config.php none
Plain PHP php frankenphp, php-fpm public none none
Next.js next node, bun, deno, docker none /
Nuxt nuxt node, bun, deno, docker none /
Generic Node node node, bun, deno, docker none /
Static site static static none none
Docker docker docker, compose none /

Initial variables per preset:

  • Laravel: APP_NAME, APP_ENV=production, APP_KEY (generated), APP_DEBUG=false, APP_URL, LOG_CHANNEL=daily. Scheduler on, Horizon and Octane off.
  • Statamic: the same without LOG_CHANNEL; scheduler on.
  • Symfony: APP_ENV=prod, APP_SECRET.
  • Next.js: NODE_ENV=production, NEXT_TELEMETRY_DISABLED=1.
  • Nuxt: NODE_ENV=production, NITRO_PRESET=node-server.
  • Node: NODE_ENV=production.
Runtime API value How it runs on the server
FrankenPHP frankenphp The server’s FrankenPHP (which embeds Caddy) serves the site’s web directory and runs PHP in-process. Default for PHP.
PHP-FPM php-fpm A standalone Caddy serves files and passes PHP to a per-site PHP-FPM pool.
Node.js node The agent supervises npm run start in the current release with PORT=<app port>, HOST=127.0.0.1; Caddy reverse-proxies to it.
Bun bun Same, with bun run start. Bun is installed on the server when the site first targets it.
Deno deno Same, with deno task start. Deno is installed on demand.
Static static Caddy serves the built output directory.
Docker docker A container runs the image with PORT=<app port>; Caddy proxies to it. Deploys are blue/green.
Docker Compose compose A Compose project per site; public services are published on loopback ports and routed by Caddy.

Runtime versions:

Runtime Versions
PHP Servers install 8.1–8.5 (default 8.4). A site can pin 7.4–8.5 when that version is installed on its servers.
FrankenPHP 1.9.1 (FALAK_FRANKENPHP_VERSION)
Node.js Servers: 20 (20.19.5), 22 (22.20.0, default), 24 (24.9.0). Sites can select 18, 20, 22 or 24.
Bun 1.4.2 (FALAK_BUN_VERSION)
Deno 2.9.7 (FALAK_DENO_VERSION)

Caddy serves a static site in this order:

  1. The requested file or directory, if it exists.
  2. /404.html with status 404, if the site has one.
  3. /index.html for paths without a file extension (so client-side routes like /about survive a reload), while a missing /app.js stays a 404.

This means single-page apps work without configuration. .env and .git are never served.

/srv/falak/sites/shop
/srv/falak/sites/shop/
├── releases/
│ ├── 01K3H4HNKW…/ one directory per release (release id, upper-case ULID)
│ └── 01K3H9ZQ2A…/
├── shared/ .env, storage/, other shared paths (linked into every release)
└── current -> releases/01K3H9ZQ2A…
  • Release and shared/ directories have mode 0750 (owner: the site user) plus an ACL that lets the edge read them. Other local users cannot enter them.
  • Writable directories are group-writable with default ACLs, so PHP under FrankenPHP (the edge user) and workers (the site user) can share log files.
  • Before the first deploy, current points at a placeholder release that answers 503, so a never-deployed site cannot break other sites’ routes.

By default, sites run as the Linux user falak. A site created with isolated: true (a field of POST /api/v1/sites and the site form) gets its own Linux user derived from its slug, so sites on the same server cannot read each other’s files. Isolation is chosen at creation.

Since v0.7.0, a new isolated site whose slug starts with falak gets an s- prefix on its Unix user (s-falak… instead of falak…), so it can’t be mistaken for one of Falak’s own service accounts (its database and cache engines use falak-<engine>-<name> system users — see Redis and Valkey). Sites isolated before v0.7.0 keep their existing falak… user.

The leader is the site’s primary server. Falak runs migrations (the $FALAK_IS_LEADER branch of the deploy script), the Laravel scheduler, and Compose leader commands only on the leader. Generated domains point at the leader (or at the load balancer).