Installation
Prerequisites
- Docker Desktop (or Docker Engine + Compose)
- Node ≥ 18 and pnpm (for running the CLI and the Nuxt app locally)
PHP, Composer, and all API dependencies run inside Docker — nothing else to install.
1. Scaffold your project
Run the interactive CLI:
pnpm create cwa my-project
The CLI will ask four questions:
| Prompt | Options |
|---|---|
| Project name | The directory to create |
| CI/CD pipeline | GitHub Actions, GitLab CI, or none |
| Features | Navigation links, HTML content editor, image uploads, blog, nested pages, forms (multiselect) |
| Include fixtures? | Scaffolds sample content so the site works out of the box |
After answering, the CLI downloads the template, strips unused feature code from nuxt.config.ts, and generates a README.md tailored to your choices. It then offers to run docker compose up -d and pnpm install in one step — accept both.
cwa-manifest.json file hosted in the template repo. Feature options and their file exclusions are driven by this manifest, so the CLI always reflects the latest template state without needing a CLI update.create-cwa package stays on 0.x.x versioning until CWA v1. It always scaffolds from the template repo's main branch — the branch comes from the cwa-manifest.json the CLI fetches, not from the CLI version you install.2. Generate JWT keys
Authentication uses JWT tokens signed with an RSA key pair. The keys are gitignored, so every environment generates its own:
docker compose exec php bin/console lexik:jwt:generate-keypair
That writes api/config/jwt/private.pem and api/config/jwt/public.pem, encrypted with the JWT_PASSPHRASE from api/.env. The template ships a working passphrase so this step needs no configuration in local development.
docker compose exec php php -r "print bin2hex(random_bytes(26));", add it to api/.env.local as JWT_PASSPHRASE=<your passphrase>, then regenerate the pair with bin/console lexik:jwt:generate-keypair --overwrite. The passphrase and the keys must always match — change one without the other and every login fails silently.Prefer to do it by hand? Run these inside the container (docker compose exec php sh) instead. Both commands prompt for a passphrase — enter the JWT_PASSPHRASE value each time:
mkdir -p config/jwt
openssl genpkey -out config/jwt/private.pem -aes256 -algorithm rsa -pkeyopt rsa_keygen_bits:4096
openssl pkey -in config/jwt/private.pem -out config/jwt/public.pem -pubout
exit
3. Load fixtures
If you selected Include fixtures during setup, load them once the API container is healthy:
docker compose exec php bin/console doctrine:fixtures:load
This creates a default admin account (admin / admin) and seeds the site structure — layout, pages, and example components.
4. Open the site
There is no dev server to start. docker compose up -d already runs the Nuxt dev server inside the app container, and Caddy proxies it:
| URL | What's there |
|---|---|
https://localhost | Your Nuxt application |
https://localhost/_api | API (JSON-LD) |
https://localhost/_cwa | CWA admin panel |
api/frankenphp/caddy/certs/.http://localhost:3001 if you ever need to bypass the proxy. Don't run pnpm dev on your host at the same time — that starts a second Nuxt on port 3000 that https://localhost never reaches.Log in at https://localhost/login with admin / admin. Click Edit to enter edit mode and see the inline CMS.
https://localhost with the fixture content loaded — the first thing a reader should see workingWhat Docker handles automatically
When the php container starts, the entrypoint:
- Runs
composer installifvendor/is empty - Waits for PostgreSQL to be ready
- Runs pending database migrations
- Runs
ANALYZEto refresh PostgreSQL query statistics (keeps dev pages fast — see the Docker gotchas)
You never need to run these manually.
CI/CD
If you chose GitHub Actions during setup, four workflows are pre-wired in .github/workflows/:
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml | Every push | Build + test; deploy review environment on PRs |
production.yml | Manual | Deploy canary or production |
cleanup.yml | PR closed | Tear down review environment |
performance.yml | Manual | Run sitespeed tests |
Images push to GHCR. The workflows use the same bin/devops/ shell scripts as the GitLab CI option. Required secrets and variables are documented inline in each workflow file — read those.
publish-create-cwa.yml, belongs to the upstream template repo and is copied into your project too. It only fires on create-cwa/v* tags, so it never runs for you — delete it: rm .github/workflows/publish-create-cwa.yml.Manual setup (alternative)
If you prefer to create a GitHub repository first and clone from there:
With the GitHub CLI:
gh repo create my-website \
--template="components-web-app/components-web-app" \
--private --clone
Or generate from GitHub:
Then follow steps 2–4 above. You'll configure features manually rather than through the CLI.
packages/ directory used for the create-cwa CLI itself. Delete it from your project after cloning — it's not needed in your app.rm -rf packages/
What's next
- Your First Layout — create the Vue file for your layout
- Your First Page Template — define page regions with CwaComponentGroup
- Your First Component — build a PHP entity and matching Vue component