CWA 2 is a prerelease (2.0.0-alpha), heading for a beta. It runs production sites; configuration can still change between releases, always with upgrade notes. View versions and support
Getting Started

Installation

Scaffold a new CWA project with the interactive CLI — it picks your features, wires CI/CD, and starts a site you can sign in to in one go.

Prerequisites

  • Docker Desktop (or Docker Engine + Compose), running, with about 4 GiB of memory for Docker. Docker Desktop sets this in Settings › Resources.
  • Ports 80, 443, 3001, 5432 and 8025 free on your machine. The stack publishes Caddy on 80 and 443, the Nuxt dev server on 3001, PostgreSQL on 5432 and Mailpit, which catches the emails the API sends, on 8025. Another CWA project's running stack, a local web server, a local PostgreSQL or a local Mailpit commonly holds one.
  • Node 22.13 or later and pnpm 12 (for running the CLI and the Nuxt app locally)

PHP, Composer, and all API dependencies run inside Docker — nothing else to install. The Nuxt app's own requirements, including Pinia 4, come in through pnpm install.

1. Scaffold your project

Run the interactive CLI:

pnpm create cwa my-project

It starts with a notice: CWA is a prerelease, so expect breaking changes between versions, and create-cwa collects no telemetry, it only downloads the template from GitHub. Then it asks, in this order:

PromptWhat it means
Project name?Lower-case letters, digits and single dashes, such as my-cwa-app. It becomes the directory and the Docker Compose project name. The directory must not exist yet, or be empty.
CI/CD pipeline? (Kubernetes deploy pipelines; choose None to decide later)GitHub Actions, GitLab CI or None
Which features to include?Navigation links, HTML content editor, image uploads (stored on the server, or in a bucket on Kubernetes), blog, nested pages, example form. A feature another one needs is added for you.
Include database fixtures for selected features? (sample pages; the admin login is created either way)Yes by default
Start the Docker stack when the project is ready? (bin/up: starts it, loads fixtures, prints the login)Yes by default. Asked only when docker is installed.
Install app/ dependencies on this machine? (only for editor types; the app container installs its own on first start)No by default. Asked only when you don't start the stack now, and pnpm is installed.

A name that breaks the rule is refused at the prompt (Use lower-case letters, digits and single dashes (for example my-cwa-app)), and so is a directory with files in it ("my-cwa-app" already exists here and is not an empty directory), before anything is downloaded.

If you start the stack, the CLI checks Docker first, before it downloads anything:

  • docker info must answer. If it doesn't, the CLI says: "Docker is installed but docker info failed: start Docker Desktop (or the Docker daemon)."
  • Ports 80, 443, 3001, 5432 and 8025 must be free. For a port in use, it names what holds it: a container and its Compose project, with the command to stop it, or the process. For 3001, 5432 and 8025 it also names the line in compose.override.yaml to change instead.
  • Under about 3.5 GiB of Docker memory, it warns, for example: "Docker has 2.0 GiB of memory; the stack needs about 4 GiB. Raise it in Docker Desktop → Settings → Resources." This is only a warning: the stack still starts.

If Docker isn't running or a port is taken, the CLI still creates the project but doesn't start the stack, and says so. Fix the problem (see Troubleshooting › Local development), then run bin/up in the project.

The CLI then downloads the template, strips the code for features you didn't pick, generates a README.md for your choices, and, if you said yes, runs bin/up in the new project (step 2).

The host install is offered only when the stack isn't started now, because app/node_modules is shared with the app container: a pnpm install on your machine under a running dev server crashes it (see Troubleshooting › Every page returns 502 or 503 after you run pnpm install on the host).

create-cwa has the same version as the template release it installs. It downloads the template, and its cwa-manifest.json of feature options, from the template tag of its own version, so the same CLI version always installs the same template. Releases lists the bundle and module versions each template release ships with. Template changes reach new projects only in a new release.
To install from a different branch or tag, for example to try unreleased changes, pass --ref:
pnpm create cwa my-project -- --ref main

Answering the questions with options

Each question has an option that answers it, so it isn't asked:

OptionAnswers
--pipeline <github|gitlab|none>CI/CD pipeline?
--features <list>Which features to include? Comma-separated ids (navigation, html-content, image, blog, nested-pages, forms), all or none. A feature another one needs is still added.
--fixtures / --no-fixturesInclude database fixtures…?
--start / --no-startStart the Docker stack…? With --start, a failed Docker check (Docker not running, a port taken) stops the CLI before anything is created.
--install-deps / --no-install-depsInstall app/ dependencies on this machine? Only when the stack isn't started.

For a run with no one at the keyboard:

  • -y or --yes takes the default for every question no option answers: GitHub Actions, no features, fixtures, start the stack (when Docker is there and its checks pass), no host install. The project name has no default, so it's still asked for when you don't give one.
  • --ci never asks anything, and implies --yes. A missing or invalid project name is an error, and a failed bin/up makes the CLI exit with 1.
pnpm create cwa my-project -- --ci --pipeline none --features all --no-start

--template-dir <path> copies the template from a local checkout instead of downloading it: the files git tracks, as a download would have them, with the features you chose. It can't be combined with --ref.

2. Let bin/up start the site

bin/up is the one command that takes the project from nothing running to a site you can sign in to. create-cwa runs it for you; run it yourself from the project root any other time:

bin/up

It works through four steps, printing how long each took:

  1. Starts the stack with docker compose up -d --wait, which returns once every container answers its health check (see Docker › Waiting for the stack). The first run builds the images, installs the Composer and pnpm dependencies and migrates the database, so it takes several minutes.
  2. Loads the fixtures with doctrine:fixtures:load --append: the admin account, plus the sample pages if you chose them.
  3. Pre-compiles / and /login, so your first visit isn't a slow first compile.
  4. Prints where to sign in, for example:
Ready in 412s.

  Site:      https://localhost
  Sign in:   https://localhost/login
  Admin:     admin / admin
  Emails:    http://localhost:8025 (Mailpit catches every email the API sends)

--append keeps what's already in the database and only creates what's missing, so running bin/up again is safe and changes nothing you've built. It does reset the admin's password and email to ADMIN_PASSWORD and ADMIN_EMAIL (admin and admin@example.com when unset) on every load, so if you changed them in the admin, run bin/up --skip-fixtures instead. The whole run has 15 minutes, which CWA_UP_TIMEOUT changes, in seconds. If a step fails, bin/up names it and prints the last 40 log lines of the services involved: see Troubleshooting › Local development.

admin / admin is for your machine only. Anywhere public, set ADMIN_PASSWORD to a long random value before the first fixture load: every load resets the admin's password to it. A deploy with ADMIN_PASSWORD unset or admin still runs, but warns, and compose.prod.yaml refuses to start without it. See CI/CD Variables › Application.
Without fixtures, create-cwa removes the sample content, api/src/DataFixtures/AppScaffold.php and api/src/DataFixtures/Parts/, and keeps UsersFixture.php, so bin/up still creates the admin account, but no layout and no pages. The account comes from ADMIN_USERNAME, ADMIN_PASSWORD and ADMIN_EMAIL in api/.env.local, and is admin / admin / admin@example.com when they're unset. To choose the details at a prompt instead, create the account by hand:
docker compose exec php bin/console silverback:api-components:user:create --admin
The --admin flag gives the account ROLE_ADMIN; without it you can't open the admin panel. Until a page has the route /, https://localhost shows a not-found error page. That's expected: sign in and build the site from the admin, starting with Your First Layout.

Without a POSIX shell

Windows without a POSIX shell (WSL or Git Bash) can't run bin/up: there create-cwa runs docker compose up -d --wait instead and prints the fixtures command. Start the stack, wait for it, and load the fixtures yourself:

docker compose up -d --wait
docker compose exec php bin/console doctrine:fixtures:load --append

3. Open the site

There is no dev server to start: the Nuxt dev server already runs inside the app container, and Caddy proxies it:

URLWhat's there
https://localhostYour Nuxt application
https://localhost/_apiAPI (JSON-LD)
https://localhost/loginSign in
https://localhost/_cwaCWA admin panel
http://localhost:8025Mailpit: every email the API sends in development, such as password resets
The browser warns about https://localhost because Caddy signs it with its own local certificate. That's expected: proceed past the warning. The certificate lives in the caddy_data volume, so docker compose down -v creates a new one and the browser asks again (see Docker › Volumes).
The containerised dev server is also published directly on 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.

Sign in at https://localhost/login with the admin login bin/up printed (admin / admin unless you set ADMIN_*; for development only). Click Edit to enter edit mode and see the inline CMS.


Where things live

The project root holds the two applications side by side. Every path in these docs starts at the project root.

api/
src/
Entity/
PHPTitle.phpa component's PHP entity
DataFixtures/
PHPUsersFixture.phpcreates the admin account from ADMIN_*
PHPAppScaffold.phpthe sample content's entry point (only with fixtures)
Parts/
PHPCoreScaffoldPart.phpseeds the layout, home page and Title (only with fixtures)
app/
TSnuxt.config.tsregisters layouts, pages and components
app/
cwa/
layouts/
Vprimary.vue
pages/
VPrimaryPageTemplate.vue
components/
Title/
VTitle.vue
compose.yaml
The Nuxt app is app/, and its source directory is app/ too, so CWA's Vue files live in app/app/cwa/, not app/cwa/. Nuxt only looks in app/app/cwa/, so a file created at app/cwa/ from the project root is never found.

Commands run from the project root unless a page says otherwise. PHP and Composer run only inside Docker, so PHP commands go through the php container: docker compose exec php bin/console ….


What Docker handles automatically

When the php container starts, the entrypoint:

  • Runs composer install if vendor/ is empty
  • Generates the JWT key pair for authentication with lexik:jwt:generate-keypair --skip-if-exists, outside production. The keys go in api/config/jwt/ (gitignored), encrypted with the JWT_PASSPHRASE in api/.env. To deploy, see Docker › JWT keys
  • Waits for PostgreSQL to be ready
  • Runs pending database migrations
  • Runs ANALYZE to refresh PostgreSQL query statistics (keeps dev pages fast — see Troubleshooting)

The app container runs pnpm install and then the Nuxt dev server each time it starts. You never need to run these manually. Loading the fixtures is the one step the containers don't do, which is why bin/up does it after they're healthy.


CI/CD

If you chose GitHub Actions during setup, three workflows are pre-wired in .github/workflows/:

WorkflowTriggerWhat it does
ci.ymlEvery pushBuild + test; deploy a review environment for every branch except main
production.ymlManualDeploy canary or production
cleanup.ymlPR closedTear down review environment

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.

The template repo also has a publish-create-cwa.yml workflow, which publishes the CLI. create-cwa leaves it out of new projects.

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:

Generate a new repository from the template on GitHub

Then, from the cloned project's root, run bin/up (step 2):

bin/up

Installing the Nuxt dependencies on your machine (pnpm install in app/) is optional, as in step 1: the app container installs them. If you want them, install before bin/up, never while the stack runs. A clone keeps every feature and all the fixtures, because nothing is stripped, so bin/up seeds the full demo site. Turning features off is up to you.

The template repo includes a 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

Read Key Ideas first: it explains the terms the guides use, in a few minutes. Then Core Concepts gives the full picture of how a CWA site works, before the guides.

The template already ships a working layout (primary.vue), page template (PrimaryPageTemplate.vue) and component (Title). The guides build new ones beside them, ending with a /landing page: