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.
Anatomy of a site
Section titled “Anatomy of a site”| 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 |
Framework presets
Section titled “Framework presets”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.
Runtimes
Section titled “Runtimes”| 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) |
Static sites and single-page apps
Section titled “Static sites and single-page apps”Caddy serves a static site in this order:
- The requested file or directory, if it exists.
/404.htmlwith status 404, if the site has one./index.htmlfor paths without a file extension (so client-side routes like/aboutsurvive a reload), while a missing/app.jsstays a 404.
This means single-page apps work without configuration. .env and .git are never served.
Directory layout on a server
Section titled “Directory layout on a server”/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 mode0750(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,
currentpoints at a placeholder release that answers 503, so a never-deployed site cannot break other sites’ routes.
Site user
Section titled “Site user”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.
Leader
Section titled “Leader”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).