# Sites and runtimes

> What a Falak site is, the framework presets and runtimes it can use, how each runtime is served on the server, and the directory layout of a site.

Source: https://falak.sh/docs/concepts/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

| 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](/docs/guides/deploy-scripts/) | |

## 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

| 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

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.

## Directory layout on a server

```text title="/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.

## 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](/docs/databases/redis-and-valkey/#isolation-and-security)). Sites isolated before v0.7.0 keep their existing `falak…` user.

## 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).

## Next steps
