---
url: /recipes/launch-a-client-site.md
---
# Launch a new client site

From an empty folder to a site the client reviews on the server, and from there to live. The recipe follows the order the work happens in; the pages it links to hold the details and the options. Written against 2.16.0.

::: glance

* **You need** SSH access to the hosting account, a vhost per environment pointing at `<target>/current/public`, a DNS record and a MySQL database per environment, and the client's accounts for the integrations the site uses.
* **You get** a deployed site with its posture (mail, indexing, password, banner) set by its environment type, cron jobs for the scheduler and the queue, and a documented switch to live.
* **Pages** [Installation](/getting-started/installation), [Environments](/getting-started/environments), [Deployment](/getting-started/deployment), [Modules](/modules/), [Integrations](/integrations/)
  :::

## 1. Create the project

```sh
composer create-project racerfish/evolve-starter --ask --repository '{"type": "vcs", "url": "https://github.com/racerfish/evolve-starter"}' --remove-vcs
```

Composer asks for the folder name and installs the dependencies. The starter's install wizard writes the `.env`. Name the folder like the client; with Herd it becomes the local domain (`client.test`). Then:

```sh
cd client
php artisan migrate
php artisan evolve:seed
```

**Check:** `http://client.test/evolve` shows the login screen. The first user comes from the panel's registration or an invitation, see [Roles](/getting-started/roles).

## 2. Switch the modules

Decide which modules the site uses, in `config/evolve.php` under `modules` or with `EVOLVE_MODULE_<KEY>` in the `.env`. Everything is on by default except the AI chatbot and regions; switch off what the site will not use so editors do not see it. Run `php artisan migrate` again after the first change.

**Check:** the panel sidebar lists exactly the modules you kept, one entry each. The table on [Modules](/modules/) says what each adds.

## 3. Publish blueprints and the theme

Without published blueprint classes no block renders:

```sh
php artisan vendor:publish --tag=evolve.blueprints
php artisan vendor:publish --tag=blog.blueprints --tag=company.blueprints --tag=event.blueprints --tag=scout24.blueprints
php artisan vendor:publish --tag=locator.components --tag=shopify.components --tag=scout24.components
```

Publish only the tags of modules the site uses. Then the theme:

```sh
php artisan theme:publish
yarn theme:install
yarn theme:dev
```

**Check:** add a text block to the seeded home page in the panel and open `http://client.test/`. The block renders. The theme is now the project's to shape, see [Frontend](/frontend/).

## 4. Keys and settings

The `.env` carries what the site cannot run without: `APP_URL` (the page cache and the sitemap depend on it), the database, the mail transport, and the secrets of the integrations you switched on (reCAPTCHA, Google Maps and Geocoding for the locator, Vimeo, OpenAI). The starter's `.env.example`  is grouped and commented; it carries no site settings. [Environment Variables](/getting-started/environment-variables) is the reference, each row links to the page that explains the key.

The site's own values go into the panel, not the `.env`: log in at `/evolve`, open Settings and walk the tabs General (name, branding), Company, Mail (sender name and addresses), Tracking (ids, consent) and SEO (verifications). These are stored in the database and travel with the database copy in step 6; the secrets in the `.env` do not, every environment gets its own copy there.

**Check:** `/evolve/health` on the local site is green apart from checks that need the server (scheduler, queue).

## 5. Declare the environments

In `config/evolve.php` under `deployment`, name the environments and the server:

```php [config/evolve.php]
'deployment' => [
    'default' => env('DEPLOYMENT_DEFAULT', 'live'),
    'environments' => [
        // the future live site, declared as staging while the client reviews it
        'staging' => ['type' => 'staging', 'target' => 'www', 'url' => 'https://client.ch'],
    ],
    'server' => [
        'user' => env('DEPLOYMENT_USER', 'web161'),
        'host' => env('DEPLOYMENT_HOST', 'web.onlime.ch'),
        'path' => env('DEPLOYMENT_PATH', 'webhome/public_html'),
        'php-bin' => env('DEPLOYMENT_PHP_BIN', 'php84'),
    ],
],
```

The type drives the posture: a `staging` site redirects mail, carries `noindex`, shows the banner and gets a password as soon as one is set; a `live` site does none of that and asks you to type its name before a deploy. The full matrix is on [Environments](/getting-started/environments#types-and-policies).

::: tip Where the client reviews the site
The tooling never pushes a database into a live environment, not with `evolve:db:copy`, not with the Envoy tasks, not with `evolve:env:setup --db=copy:...`. Content built on a separate review site therefore cannot be moved into live later. So the declaration above names only one environment: the target that becomes the live site (`www`), declared as `staging` while the client reviews it, which gives it the mail redirect, `noindex` and the password. Step 8 turns that declaration into `live`. The permanent staging environment for the work after launch is set up at the end, seeded from live.
:::

**Check:** `php artisan evolve:env:list` prints the declared environments.

## 6. Bootstrap the server and ship the first release

With the vhost, DNS and database in place on the hosting side:

```sh
php artisan evolve:env:setup staging --db=fresh --deploy
```

The command asks you to type the name for a live environment, creates the remote directories, writes the server `.env` from the shipped template (it prompts for `APP_URL`, the database name, user and password; a prepared file passed with `--from-file` skips the prompts), runs the first deployment, and with `--db=fresh` seeds the database after the deploy. `EVOLVE_ENV_TYPE` is written from the declaration, so the posture is in place from the first request.

Then the two cron entries on the server, both pointing at `current` so they follow every deploy:

```sh
* * * * * cd ~/webhome/public_html/www/current && php84 artisan schedule:run >> /dev/null 2>&1
* * * * * cd ~/webhome/public_html/www/current && php84 artisan queue:work --stop-when-empty --max-time=55 >> /dev/null 2>&1
```

The worker line is needed once `QUEUE_CONNECTION` is not `sync`; add it in the same step as that change, see [Cron jobs](/getting-started/deployment#cron-jobs).

**Check:**

```sh
php artisan evolve:env:verify staging     # posture matches the declaration
php artisan evolve:remote:status staging  # current release, PHP, queue and scheduler
```

The site answers on its URL with the environment banner, `/evolve/health` shows the Scheduler check green within a minute, and the first user can log in.

## 7. Posture, content and integrations on the review site

In the panel under Settings, Developers tab (developer role):

* **Mail safety** stays on; set `EVOLVE_MAIL_REDIRECT` to your own address so form notifications and invitations reach you instead of the client. Switch it to real recipients only for a dress rehearsal.
* **Protect** with `EVOLVE_PROTECT_USER` and `EVOLVE_PROTECT_PASSWORD` before you send the client the URL the vhost answers on (the hosting's preview hostname, or the domain once its DNS points at the server). The password and the page cache exclude each other, so the cache stays off while the site is protected.
* **Tracking emit** stays off. The IDs can sit in the `.env` already; a staging site does not fire them. The test run is its own recipe, [Set up tracking and consent](/recipes/set-up-tracking-and-consent).

Invite the client's editors from Users; invitations are signed links. Editors build the content here. Media uploads land in `shared/storage/app` and survive every deploy.

Connect the integrations the site uses, each from its checklist on [Integrations](/integrations/): reCAPTCHA keys registered for the live domain and, if the check should run there, the staging domain; the Sentry DSN; the Oh Dear health check secret; Vimeo, Brevo and OpenAI where the site uses them.

**Check:** a form submission on the review site produces a lead in the panel and a mail in your redirect inbox. `/evolve/health` is green except Oh Dear, which only reports once the site is live and monitored.

## 8. Go live

1. Rename the declaration: the key becomes `live`, the type `live`, the target stays `www`. The name is what you type on the CLI, the target is the directory on the server, so nothing moves.

```php [config/evolve.php]
'environments' => [
    'live' => ['type' => 'live', 'target' => 'www', 'url' => 'https://client.ch'],
],
```

2. On the server side, set the environment type to live in the Developers tab, which writes `APP_ENV=production` and `EVOLVE_ENV_TYPE=live` together. Turn mail safety off and remove the preview password in the same screen. Switch the page cache on under General if the site uses it.
3. Deploy once more. A deploy to live asks you to type the environment name:

```sh
php artisan deploy live
php artisan evolve:env:verify live
```

`evolve:env:verify` fails while `APP_ENV` or `EVOLVE_ENV_TYPE` contradict the declaration or debug is on, and warns about a missing Sentry DSN, an `APP_URL` that differs from the declared one, keys missing against `.env.example` and a stale published Livewire bundle. Fix what it names and run it again.

4. Point the DNS at the server and set up the Oh Dear monitor for the live URL, see [Oh Dear](/integrations/oh-dear).

**Check:**

```sh
curl -s https://client.ch/ | grep -c gtm.js                   # 1 once tracking is configured
curl -s https://client.ch/ | grep -E '<title>|rel="canonical"' # the live domain, no noindex
php artisan evolve:remote:status live
```

`/evolve/health` is green, the Oh Dear check included. `sitemap.xml` lists the pages.

## 9. A staging environment for the work after launch

Now that live has content, add the declaration for the permanent staging site and set it up from live:

```php [config/evolve.php]
'environments' => [
    'live'    => ['type' => 'live',    'target' => 'www',     'url' => 'https://client.ch'],
    'staging' => ['type' => 'staging', 'target' => 'staging', 'url' => 'https://staging.client.ch'],
],
```

With the vhost, DNS record and database for it in place:

```sh
php artisan evolve:env:setup staging --from=live --db=copy:live --media-from=live --deploy
```

`--from=live` seeds the server `.env` from the live one (the type and posture keys come from the declaration), `--db=copy:live` copies the database on the server and runs the sanitize statements under `deployment.database.sanitize`, `--media-from=live` copies the uploads. Add the cron entries for the staging target as in step 6.

**Check:** `php artisan evolve:env:verify --all` passes for both, and the staging site shows the banner, carries `noindex` and redirects its mail.

## Done when

* `evolve:env:verify --all` passes.
* `/evolve/health` is green on live, Oh Dear included.
* The scheduler and, where the queue is not `sync`, the worker run from cron on every environment.
* The client's editors log in with their own accounts and the first lead has arrived at the client's address.
* Tracking fires on live, see the [tracking recipe](/recipes/set-up-tracking-and-consent).
