Deploying Laravel applications on Vercel
Tags: laravel, tutorial, webdev, programming
Published: 2023-11-03 13:13:44

> Read the original post [here](https://www.jxd.dev/blog/laravel-vercel-example)

Laravel is one of my preferred tools for speedy application development. We utilise it extensively at [DocLabs](https://www.doclabs.dev), deploying our software to AWS using Vapor. However, for smaller projects, I've been seeking a more cost-effective yet equally straightforward deployment solution.

Here is my step-by-step guide on how to deploy Laravel applications on Vercel with a PlanetScale database:

1. Create a new Laravel application.
1. Configure the application for Vercel.
1. Set up a PlanetScale database.
1. Automate the deployment process using Github Actions.

Let’s crack on!

# Create a new Laravel application

We require an application for deployment. Let's swiftly set up a new Laravel + Jetstream project.

```bash
# Create a new Laravel project
composer create-project laravel/laravel laravel-vercel-example

# Install Jetstream
composer require laravel/jetstream
php artisan jetstream:install livewire
```

Next, we'll set up a local database using Docker Compose, update our environment variables, and run our migrations.

```yaml
# docker-compose.yml
version: '3.8'

services:
  db:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_DATABASE: db
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
```

```
# .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db
DB_USERNAME=root
DB_PASSWORD=password
```

```bash
# Start our database
docker-compose up -d

# Run migrations
php artisan migrate
```

Finally, push your changes to Github. This step is crucial because we'll use Github Actions for automatic deployment.


![Running Laravel application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/srzw4dqk2fm7wbi4dgq5.png)

# Configure the application for Vercel

Before deploying our application to Vercel, we need to make five changes:

1. Set up a new Vercel project
2. Create a `vercel.json` file
3. Add our `api/index.php` entry point
4. Set up our trusted proxies
5. Ensure the `vendor` directory is not uploaded

## Setup a new Vecel project

We'll use the Vercel CLI to create a new project, which we plan to deploy later.

```bash
# Install the Vercel CLI
npm i -g vercel

# Login to Vercel
vercel login

# Create and link a new project
vercel link
```

Follow the step-by-step instructions provided by `vercel login` and `vercel link`. This will generate a `.vercel` directory and automatically include it in your `.gitignore`. Open the `.vercel/project.json` file and take note of the `orgId` and `projectId` as we'll need them later.

## Create a `vercel.json` file

Create a new `vercel.json` file at the root of your project and include the following:

```jsonc
// vercel.json
{
    "version": 2,
    "framework": null,
    "functions": {
        "api/index.php": { "runtime": "vercel-php@0.6.0" }
    },
    "outputDirectory": "public",
    "routes": [
        {
            "src": "/build/(.*)",
            "dest": "/build/$1"
        },
        {
            "src": "/(.*)",
            "dest": "/api/index.php"
        }
    ],
    "env": {
        "APP_ENV": "production",
        "APP_DEBUG": "true",
        "APP_URL": "YOUR APP URL",

        "APP_CONFIG_CACHE": "/tmp/config.php",
        "APP_EVENTS_CACHE": "/tmp/events.php",
        "APP_PACKAGES_CACHE": "/tmp/packages.php",
        "APP_ROUTES_CACHE": "/tmp/routes.php",
        "APP_SERVICES_CACHE": "/tmp/services.php",
        "VIEW_COMPILED_PATH": "/tmp",

        "CACHE_DRIVER": "array",
        "LOG_CHANNEL": "stderr",
        "SESSION_DRIVER": "cookie",

        "DB_CONNECTION": "mysql",
        "DB_PORT": "3306",
        "MYSQL_ATTR_SSL_CA": "/etc/pki/tls/certs/ca-bundle.crt"
    }
}
```

Let’s quickly explain some of the key concepts here.

### `functions`

```json
"functions": {
      "api/index.php": { "runtime": "vercel-php@0.6.0" }
  },
```

Our application is essentially a single serverless function, which we'll create next. We use the community [`vercel-php`](https://github.com/vercel-community/php) runtime to set up the environment and automatically install dependencies with Composer.

### `outputDirectory`

```json
"outputDirectory": "public",
```

Vercel builds our application by running `npm run build`, but by default, it looks for a `dist` directory. However, our assets are actually saved in `public/build`, so we need to instruct Vercel where to find them.

If you're using a tool other than Livewire, you may need to adjust this setting accordingly.

### `routes`

```json
"routes": [
    {
        "src": "/build/(.*)",
        "dest": "/build/$1"
    },
    {
        "src": "/(.*)",
        "dest": "/api/index.php"
    }
],
```

The first route intercepts requests for CSS and JS assets and directs them to the appropriate files. All other requests are managed by our Laravel serverless function, as specified by the second route.

### `env`

```json
"env": {
    "APP_ENV": "production",
    "APP_DEBUG": "true",
    "APP_URL": "YOUR APP URL",

    "APP_CONFIG_CACHE": "/tmp/config.php",
    "APP_EVENTS_CACHE": "/tmp/events.php",
    "APP_PACKAGES_CACHE": "/tmp/packages.php",
    "APP_ROUTES_CACHE": "/tmp/routes.php",
    "APP_SERVICES_CACHE": "/tmp/services.php",
    "VIEW_COMPILED_PATH": "/tmp",

    "CACHE_DRIVER": "array",
    "LOG_CHANNEL": "stderr",
    "SESSION_DRIVER": "cookie",

    "DB_CONNECTION": "mysql",
    "DB_PORT": "3306",
    "MYSQL_ATTR_SSL_CA": "/etc/pki/tls/certs/ca-bundle.crt"
}
```

Here, we establish our non-secret environment variables. Take note of the `MYSQL_ATTR_SSL_CA` variable. This is crucial for a secure connection to PlanetScale in the following steps. We will set up our secret environment variables in the Vercel dashboard later on.

## Add our `api/index.php` entry point

```php
// api/index.php
<?php

require __DIR__ . '/../public/index.php';
```

This simple file redirects to the default `index.php` file provided by Laravel. This is necessary because Vercel only allows functions to be located in the `api` directory.

## Set up our trusted proxies

Vercel hosts our code behind a load balancer, which forwards requests to port `80`. This can confuse Laravel when it generates secure links. To solve this issue, we need to make a simple modification to `TrustProxies.php`.

```php
// app/Http/Middleware/TrustProxies.php
<?php

namespace App\Http\Middleware;

use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array<int, string>|string|null
     */
    protected $proxies = '*';

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers =
        Request::HEADER_X_FORWARDED_FOR |
        Request::HEADER_X_FORWARDED_HOST |
        Request::HEADER_X_FORWARDED_PORT |
        Request::HEADER_X_FORWARDED_PROTO |
        Request::HEADER_X_FORWARDED_AWS_ELB;
}
```

## Ensure the `vendor` directory is not uploaded

Lastly, we need to add a `.vercelignore` file. This will prevent our vendor (created during composer installation) and any pre-built files from being included, as Vercel will build these for us.

```
# .vercelignore
/vendor
/public/build
```

# Setup a PlanetScale database

Visit PlanetScale and create an account if you haven't already. From the [dashboard](https://app.planetscale.com), create a new database.

Once your database is operational, click on "Connect". Generate a new password and record the configuration details.

![PlanetScale configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c6ag8eok9klkesfn3sfl.png)

Now we'll add those configuration details to Vercel. This will allow our deployed application to access the database. Go to the dashboard and add the following environment variables:

- `APP_KEY` which can be generated by running `php artisan key:generate`
- `DB_HOST`
- `DB_DATABASE`
- `DB_USERNAME`
- `DB_PASSWORD`

![Vercel environment variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0w051qiwtaf1pls6sfb.png)

# Automated deployment with Github Actions

Now, we can integrate everything with Github Actions. We will set up a workflow that automatically applies migrations to PlanetScale and deploys our application to Vercel.

To start, add the following `.github/workflows/main.yml` file:

```yaml
on:
    push:
        branches: main
jobs:
    deploy:
        name: Deploy
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3

            - name: Setup PHP
              uses: shivammathur/setup-php@v2
              with:
                php-version: '8.2'

            - name: Install Vercel CLI
              run: npm install --global vercel@latest

            - name: Install Dependencies
              run: composer install

            - name: Migrate DB
              run: |
                php artisan migrate --force
              env:
                APP_ENV: production
                DB_CONNECTION: mysql
                DB_HOST: ${{ secrets.DB_HOST }}
                DB_PORT: 3306
                DB_DATABASE: ${{ secrets.DB_DATABASE }}
                DB_USERNAME: ${{ secrets.DB_USERNAME }}
                DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
                MYSQL_ATTR_SSL_CA: /etc/ssl/certs/ca-certificates.crt

            - name: Deploy to Vercel
              run: vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }}
              env:
                VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
                VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
```

Next, add the following secrets to your Github dashboard:

From PlanetScale:

- `DB_HOST`
- `DB_DATABASE`
- `DB_USERNAME`
- `DB_PASSWORD`

From `.vercel/project.json`:

- `VERCEL_ORG_ID`
- `VERCEL_PROJECT_ID`

From [Vercel settings page](https://vercel.com/account/tokens):

- `VERCEL_TOKEN`

![Vercel token creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qjwq6wi24pa4q3gaiui.png)

Finally, push everything to Github.

Let’s take a closer look at what this workflow is doing:

1. Your code will be checked out.
2. A PHP environment will be set up, giving you access to `php` and `composer`.
3. The Vercel CLI will be installed.
4. Your application dependencies will be installed.
5. Migrations will be run with `php artisan migrate --force`.
6. Your application will be deployed to Vercel.

You should now have a deployed and functioning application!

# Conclusion

We've demonstrated how to host Laravel applications for free on Vercel and PlanetScale. For further details, you can visit the full repository at https://github.com/jamiedavenport/laravel-vercel-example. We hope you found this information helpful.