Deploy Laravel App Using GitHub Actions
Tags: devops, github, githubactions, laravel
Published: 2023-10-05 14:40:46

it's very nice to build a CI/CD for your app after you finish developing or for staging to check your project on a server, so we get the best way to do that.



you can follow this lesson's steps to get your app live.

# 🏯 Install Cloudpanel

<figure><img src="https://github.com/tomatophp/docs/raw/master/.gitbook/assets/image (1).png" alt=""><figcaption></figcaption></figure>

to start working on the new server you need to install a lot of packages, or use a docker container, the easy way to start deploying a Laravel php app is to get a VPS from any service provider like AWS EC2 or Hetziner Cloud ...etc, after you got your VPS and **it must be Ubuntu 22.01** you can follow this lesson.

### What is Cloudpanel

**CloudPanel** is a free and modern server control panel to configure and manage a server with an obsessive focus on simplicity.

Run **PHP**, **Node.js**, **Static Websites**, **Reverse Proxies**, and **Python** **Applications** in no time on a **High-Performance Technology Stack**.

Quick launch support for:

* [Amazon Web Services](https://www.cloudpanel.io/docs/v2/getting-started/amazon-web-services/installation/ami/)
* [Digital Ocean](https://www.cloudpanel.io/docs/v2/getting-started/digital-ocean/installation/marketplace/)
* [Hetzner Cloud](https://www.cloudpanel.io/docs/v2/getting-started/hetzner-cloud/installation/installer/)
* [Google Compute Engine](https://www.cloudpanel.io/docs/v2/getting-started/google-compute-engine/installation/installer/)
* [Microsoft Azure](https://www.cloudpanel.io/docs/v2/getting-started/microsoft-azure/installation/installer/)
* [Oracle Cloud](https://www.cloudpanel.io/docs/v2/getting-started/oracle-cloud/installation/installer/)
* [Vultr](https://www.cloudpanel.io/docs/v2/getting-started/vultr/installation/marketplace/)
* [Other](https://www.cloudpanel.io/docs/v2/getting-started/other/)

<figure><img src="https://github.com/tomatophp/docs/raw/master/.gitbook/assets/image (2).png" alt=""><figcaption></figcaption></figure>

### Benefits

* **Free**
* **Easy to use**
* **Community Driven**
* **Blazing Fast Page Loads - Up to 250x faster**
* **Secure (free SSL/TLS certificates)**
* **Cloudflare integration**
* **High Performance**
* **Ready to go within 1 minute**
* **Supports all major clouds**
* **Support for X86 and ARM**

### Install Cloudpanel

login to your server using your terminal with SSH

```bash
ssh root@yourIpAddress
```

If you are using a **password** to log in, the **SSH command** would be:

```bash
ssh -i path_to_your_private_key root@yourIpAddress
```

If you are using a **private key** to log in, the SSH command would be:

Login via **SSH** to the **Server**.

now you are on the server root and the server is empty, to install the panel we need some packages to be installed first and the system to be updated.

```bash
apt update && apt -y upgrade && apt -y install curl wget sudo
```

This command will update the system packages, and add the required packages.

now you can start installing the Cloudpanel

```bash
curl -sS https://installer.cloudpanel.io/ce/v2/install.sh -o install.sh; \
echo "3c30168958264ced81ca9b58dbc55b4d28585d9066b9da085f2b130ae91c50f6 install.sh" | \
sha256sum -c && sudo bash install.sh

```

### Access CloudPanel

**SECURITY**

For security reasons, access **CloudPanel** as fast as possible to create the admin user. There is a small time window where bots can create the user. If possible, open port **8443** only for your IP via the **firewall**.

You can now access **CloudPanel** via Browser: **https://yourIpAddress:8443**

Ignore the self-signed certificate warning click on **Advanced** and **Proceed** to continue to **CloudPanel**.

<figure><img src="https://www.cloudpanel.io/docs/v2/img/getting-started/ignore-self-signed-certificate.png" alt=""><figcaption></figcaption></figure>

# 🔓 Link Cloudflare

<figure><img src="https://github.com/tomatophp/docs/raw/master/.gitbook/assets/image (18).png" alt=""><figcaption></figcaption></figure>

we believe that the security and cache are better for any app, so we need to secure our server by using Cloudflare DNS reverser and its power of caching.

### Create Cloudflare account

it's easy to create a new Cloudflare account like any website out to this [link](https://dash.cloudflare.com/sign-up) and create a new account

### Add your domain to Cloudflare

<figure><img src="https://github.com/tomatophp/docs/raw/master/.gitbook/assets/image (19).png" alt=""><figcaption></figcaption></figure>

to add your domain to Cloudflare, on the dashboard of Cloudflare you will find the Websites tab you can click on it and click on the right button to Add a site

after that I will ask you to add your domain and give you a namespace to change it on your domain then go to your domain service provider and change the namespace to the Cloudflare namespace, this process can take 24 hours for some providers, I use Google Domains it takes just 10min, and now your domain is ready on the Cloudflare.

### Connect Domain to Cloudpanel

now get to your domain after activating it, you will find the DNS tab click on it and add a new `A Record` with your `server IP` **make sure that your Proxy status is DNS only** and that can be pointed to `cp.`&#x20;

now go to your Cloudpanel click on Admin Area and then select Settings and change CloudPanel Custom Domain to `cp.YOUR_DOMAIN`&#x20;

it will generate auto SSL for you and link the domain for you, you can access the Cloudpanel using your domain

### Connect Project Domain

<figure><img src="https://github.com/tomatophp/docs/raw/master/.gitbook/assets/image (20).png" alt=""><figcaption></figcaption></figure>

to connect your project domain you need to add a new website, from the Website tab click on the Add Site button, select Create PHP Site App, fill in the details, and click on Create.

now Cloudpanel will redirect and add an nginx template and PHP-FPM to point your domain, go to Cloudflare and add a new DNS A Record and point it to your server domain, then go to SSL/TLS tab and create a new Origin Server SSL, it will give you a 15 years SSL for free, copy it and go to Cloudpanel and select your new Website app and from the tab of SSL/TLS Click on actions and then Import Certificate and add the Cloudflare SSL.

<figure><img src="https://github.com/tomatophp/docs/raw/master/.gitbook/assets/image (21).png" alt=""><figcaption></figcaption></figure>

now your domain is linked and secured by SSL, you can access your domain with SSL and make sure that the domain is working.

# 🔃 Clone Repo

login to your server using your terminal with SSH with the user of a created project&#x20;

```bash
ssh youproject@yourIpAddress
```

If you are using a **password** to log in, the **SSH command** would be:

```bash
ssh -i path_to_your_private_key root@yourIpAddress
```

If you are using a **private key** to log in, the SSH command would be:

Login via **SSH** to the **Server**.

you are on the server user and you can generate an SSH key to link it with your GitHub Project to generate this key use this command line

```bash
ssh-keygen -t ed25519 -C "your_email@example.com"
```

and you can just click enter after finishing you will get a path ending with `.pub` copy the path and then use this command to view the path SSH key content and then copy it

```bash
cat .ssh/rsa.pub
```

where.`ssh/rsa.pub` is the path of your key

now go to your GitHun Repo and on the Settings Tab, select Deploy keys and then add deploy Key and then select a name add your SSH key then save it.

now your server has access to this repo and you can clone it so go back to your server and then on the path of the project it can look like this

```bash
cd /home/yourproject/htdocs/yourproject.com
```

and clone your repo like this

```bash
git clone git@github.com:tomatophp/tomato.git .
```

it will clone the project on the current directory

now it's time to install the composer packages and set up the project .env file follow these steps&#x20;

### Copy .env file

use this command to copy .env.example

```bash
cp .env.example .env
```

now update your details and create a new database from Cloudpanel on the Database Tab create a new database, then add the details of the database to .env

```bash
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tomato-kit
DB_USERNAME=root
DB_PASSWORD=26111995
```

change the main URL in .env file

```bash
APP_URL=https://yourproject.com
APP_HOST=yourproject.com
```

### Install Composer packages and clean the project

to install composer packages just use this command

```bash
composer install
```

then you can run your project command and the important one is

```bash
php artisan key:generate
php artisan config:cache
php artisan storage:link
php artisan optimize:clear
```

now your app is ready for Frontend assets to make this work you need to install npm

### Install NVM/NPM/Yarn

Install [nvm](https://www.cloudpanel.io/docs/v2/php/guides/nodejs/\(https://github.com/nvm-sh/nvm\)/) with the following command:

```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash
```

Update the current shell environment:

```
source ~/.bashrc
```

Install your required **Node.js** version e.g. **18**:

```
nvm install 18
```

Activate the installed **Node.js** version:

```
nvm use 18
```

Done! Check the **Node.js** version:

```
node -v
```

now you can install Yarn using this command

```bash
npm -g i yarn
```

now you can easily run yarn to build assets

```bash
yarn & yarn build
```

your project is ready now and you can view it on your browser.

# 🎇 Workflow

the next step to build your CI/CD is to create a Workflow yaml file on your project repo, on your project file create a new folder with the name `.github` inside this folder create another one with the name `workflows` In this folder create a file with the name `ci.yaml`

```bash
mkdir .github
cd .github
mkdir workflows
cd workflows
touch ci.yaml
```

now on your yaml file add this script

```yaml
name: Testing Laravel with MySQL
on:
  pull_request:
    branches:
      - master
  push:
    branches:
      - master

jobs:
  laravel:
    name: Laravel (PHP ${{ matrix.php-versions }})
    runs-on: ubuntu-latest
    env:
      DB_DATABASE: laravel
      DB_USERNAME: root
      DB_PASSWORD: password
      BROADCAST_DRIVER: log
      CACHE_DRIVER: redis
      QUEUE_CONNECTION: redis
      SESSION_DRIVER: redis
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: false
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: laravel
        ports:
          - 3306/tcp
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

      redis:
        image: redis
        ports:
          - 6379/tcp
        options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
    strategy:
      fail-fast: false
      matrix:
        php-versions: ['8.2']
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          extensions: sqlite, pdo_sqlite, pcntl, zip, intl, exif, mbstring, dom, fileinfo, mysql
          coverage: xdebug

      - name: Get composer cache directory
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - name: Cache composer dependencies
        uses: actions/cache@v3
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          # Use composer.json for key, if composer.lock is not committed.
          # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-


      - name: Install Composer dependencies
        run: composer install --no-progress --prefer-dist --optimize-autoloader

      - name: Prepare Laravel Application
        run: |
          php -r "file_exists('.env') || copy('.env.example', '.env');"
          php artisan key:generate

      - name: Clear Config
        run: php artisan config:clear

      - name: Run Migration
        run: php artisan migrate -v
        env:
          DB_PORT: ${{ job.services.mysql.ports['3306'] }}
          REDIS_PORT: ${{ job.services.redis.ports['6379'] }}

      - name: Test with phpunit
        run: vendor/bin/phpunit --coverage-text
        env:
          DB_PORT: ${{ job.services.mysql.ports['3306'] }}
          REDIS_PORT: ${{ job.services.redis.ports['6379'] }}

      - name: Install Yarn dependencies
        run: yarn

      - name: Compile assets
        run: yarn build

      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          port: ${{ secrets.SSH_PORT }}
          password: ${{ secrets.SSH_PASSWORD }}
          script: cd ${{ secrets.SSH_PATH }} && ./.scripts/deploy.sh
```

this script takes fire when any push comes to `master` branch, and after testing everything on your app it will run the Script using SSH on your server by using details on the server on GitHub Secrets to change these details go to your GitHub repo and then the Settings tab, and then select Actions secrets and variables and then add a new secret with key `SSH_HOST` with your server IP, and you must add details like this

```bash
SSH_HOST: 8.8.8.8
SSH_USERNAME: yourproject
SSH_PORT: 22
SSH_PASSWORD: yourpassword
SSH_PATH: /home/yourproject/htdocs/yourdomain.com
```

then make sure you change these details on `.env.example`

```bash
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=password
```

now it's time to create a Deploy Script on the next step

# 🦕 Deploy Script

the deploy script is the script that runs every time you make a push, so you must add a selected command when it runs every time you make a push.

to add a script make a folder with the name .scripts and then create a file deploy.sh and make sure you give the file permissions to run with 755.

```bash
mkdir .scripts
cd .scripts
touch script.sh
chmod 755 deploy.sh
```

now your deploy script is ready add to this file this script and you can custom it as you like

```bash
#!/bin/bash
set -e

echo "Deployment started ..."

# Enter maintenance mode or return true
# if already is in maintenance mode
(php artisan down) || true

# Pull the latest version of the app
git reset --hard
git pull origin master

# Install composer dependencies
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader

# Clear the old cache
php artisan clear-compiled

# Recreate cache
php artisan optimize

# Compile npm assets
yarn
yarn build

# Run database migrations
php artisan migrate --force

# Exit maintenance mode
php artisan up

echo "Deployment finished!"

```

now push all of these updates to your GitHub repo and you will find on the Actions tab that the action is run after that, you will find the updates on your server.