Documentation
Install
One command, no database to set up, no setup wizard.
What you need
- Node 22 or newer. That is the entire list.
- A few minutes. There is no account to create and nothing to license.
Start a project
pnpm dlx @forinda/fcms-cli init my-site
cd my-site && pnpm install
pnpm startnpx @forinda/fcms-cli init my-site
cd my-site && npm install
npm run startyarn dlx @forinda/fcms-cli init my-site
cd my-site && yarn
yarn startbunx @forinda/fcms-cli init my-site
cd my-site && bun install
bun run startThat writes a site you can already run — a spec that validates, a
package.json, and a .gitignore that keeps the database
out of version control. The next steps it prints come back in whichever
manager you used, too.
Postgres runs inside the process — the real thing, compiled to
WebAssembly — and keeps its data in .fcms. Nothing to install,
nothing listening but the site itself, and a backup is cp -r .fcms.
Prefer to run Postgres as a service, or already have one? Docker Compose and a connection string both work, and the schema is the same either way.
With Docker
Docker brings the database and a version to pin along with it, which is what you want once one process is not enough. It needs Docker Compose and about 1–2 GB of memory.
mkdir my-site && cd my-site
curl -O https://forinda-cms.netlify.app/install/compose.yaml
curl -o .env https://forinda-cms.netlify.app/install/env.example
Open .env and set three things:
POSTGRES_PASSWORD | Anything long and random. There is no default on purpose. |
OWNER_EMAIL | Your sign-in. |
OWNER_PASSWORD | At least 12 characters. |
docker compose up -d
Open http://localhost:8080. There is a working site, and your dashboard is at /admin. Sign in and it asks what kind of site this is — bookings, enquiries, or nothing yet — and gives you a real one to start from.
There is no migration step and no setup command. The app migrates its own database and creates the first owner on boot, so up reaches a working site rather than a page telling you to run something else. Booting again changes nothing — migrations keep their ledger in the database, and the owner is created once.
Against a Postgres you already have
The same server, pointed at a database somebody else runs — a VPS, a managed one, the one your host already gave you. Same schema and same migrations as the embedded one, so moving between them is pg_dump and one variable:
DATABASE_URL=postgres://user:pass@localhost:5432/forinda \
OWNER_EMAIL=you@example.com OWNER_PASSWORD=a-long-enough-password \
npx @forinda/fcms-core
A postgres:// URL is a server; anything else is a directory to keep files in. npx @forinda/fcms-core —help lists everything it reads.
This is also the way past the embedded database’s ceiling: it serves one query at a time, which is invisible for a business taking bookings and a wall under real traffic.
What this path does not bring. No database, no TLS, no backup.sh — those came from compose. Put a reverse proxy in front of it for HTTPS, set SECURE_COOKIES=true and TRUST_PROXY=true behind one, point MEDIA_DIR at a directory that survives a redeploy, and take your own pg_dump backups.
Putting it on the internet
Compose publishes the app on HTTP_PORT and deliberately does not publish Postgres. Put a TLS terminator in front — Caddy, nginx, a load balancer — and then set two more variables:
SECURE_COOKIES=true |
Marks the session cookie Secure. Turn this on only once HTTPS works, or the cookie never arrives and nobody can sign in. |
PUBLIC_URL |
Your address, e.g. https://example.com. Used for canonical URLs and
the sitemap. |
TRUST_PROXY=true |
Only with a proxy you control. It makes the app believe forwarded headers, which is wrong for anything else. |
Upgrading
docker compose pull && docker compose up -d
Migrations are idempotent and resumable, so an interrupted upgrade is re-runnable rather than a state to reason about. Pin a version in .env if you would rather upgrade deliberately.