fastsite is self-hosted. You run the engine on your own box, it builds static output, and it pushes that output to a git repository your CDN watches. Nothing about your content or your credentials leaves your infrastructure.

What you need

  • A Linux host with Docker and the Compose plugin. Two cores and 2 GB of RAM is enough to run several sites.
  • A domain for the engine itself, pointed at that host, with TLS in front of it.
  • A git repository per site for the built output, plus a token or deploy key that can push to it.
  • A static host that watches the repo — Cloudflare Pages, Netlify, or anything else that builds on push.

What you do not need

No Node toolchain and no package manager on the host, no external database service, no separate cache server to provision, and no account with us. The image carries PHP, Node, and the image pipeline; the Compose stack brings its own Redis. It is one container plus a data volume.

Get the code

git clone https://github.com/sitepointsystems/getfastsite_gnu.git
cd getfastsite_gnu
cp .env.example .env

Configure

Open .env and set the handful of values the engine needs to know about itself. Everything else has a working default.

VariableWhat it does
APP_URLThe engine's public URL. It is the OAuth issuer and the base advertised to MCP clients during discovery, so it must be the real external URL. Leaving it at the localhost default is the single most common reason a remote agent cannot connect.
APP_SECRETA 64-character random hex string. Derives the encryption key for every stored credential — webhook secrets, GitHub tokens, two-factor seeds. Generate it once and keep it: rotating it makes existing secrets undecryptable.
DATA_PATHWhere site databases, media, builds, and per-site git clones live. Back this up.
REDIS_URLQueue, sessions, and rate limiting. The Compose stack sets this for you.
GIT_AUTHOR_NAME · GIT_AUTHOR_EMAILThe identity on publish commits. Both default.

Generate a secret with:

php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'

Optional, and only if you want the features: R2_* for off-box backups to Cloudflare R2, SHORTPIXEL_API_KEY for extra image compression before the variant pipeline runs, and ENDPOINTR_* if you are routing translation through your own AI gateway rather than a per-site key.

Start the engine

docker compose up -d
docker compose logs -f

Migrations run automatically on every boot, for the engine database and each site database. The queue worker and the scheduler start alongside the web server under supervisor inside the container — there is nothing extra to run or keep alive.

Check it came up:

curl https://your-host/healthz

Create the first admin user

Booting the container migrates the database but does not create an account. Do that once, explicitly:

docker compose exec app php bin/fastsite install \
  --email=you@example.com \
  --password='a-long-passphrase'

The password needs at least 12 characters. The command is safe to re-run — once an admin exists it does nothing, so it cannot be used to quietly add a second one. Every further account is created from the dashboard.

Three things are now listening on your host:

Dashboard

https://your-host/

Sites, content, media, the shell editor, build logs, backups.

MCP server

https://your-host/mcp

What you point Claude, ChatGPT, or Codex at. See the MCP reference.

REST API

https://your-host/api/v1

For pipelines and scripts that are not agents. See the API reference.

Create your first site

A site is identified by an immutable handle. It gets its own database, its own media store, its own shell, and its own build output directory.

docker compose exec app php bin/fastsite site:create blog \
  --name="My blog" \
  --url=https://example.com \
  --locale=en

Then point it at the repository your CDN watches and add a GitHub credential that can push there. Both are settings on the site — github_repo and a stored credential — editable from the dashboard, over the API, or by an agent.

Or skip all of that

Connect an agent first and say create a site called blog on example.com. Everything above is a tool call it can make on your behalf. See connecting your agent.

Talking to it from code

Create a scoped API key, then use it as the X-API-Key header:

docker compose exec app php bin/fastsite key:create you@example.com \
  --name="Deploy pipeline" \
  --scopes=content:write,media:write,publish \
  --sites=blog

The secret is printed once and hashed at rest, so copy it when it appears. The full surface is on the REST API page.

Design the shell

A new site is scaffolded with a default shell: Twig templates, a CSS token file, and UI strings. That is where all design lives — content arrives as body HTML only, so the chrome is designed once and never repeated.

Read how it works before you edit the shell. The build audit enforces inlined CSS, zero shipped JavaScript, and complete head tags, and it will reject a template that would cost a PageSpeed point rather than letting it ship quietly.

Updating

git pull
docker compose build --pull
docker compose up -d

Migrations run on boot. Site databases are versioned independently, so an update never touches published output until you publish again.

Backups

Everything that matters lives under DATA_PATH. The engine can also snapshot a site on demand — full, database only, media only, or just the shell — and restore any snapshot back over a running site.

docker compose exec app php bin/fastsite backup:site blog
docker compose exec app php bin/fastsite backup:list blog

With R2_* configured those snapshots go to Cloudflare R2 and a nightly set runs on its own; without it they are written to the data volume. Restoring a site database always triggers a rebuild and republish, so the database and the published output can never drift apart.

Built output is already in git, so your publish history is a second backup you can roll back to with a revert.