Beginner’s CI/CD Guide: Deploy Laravel8 to Google Cloud Run and Cloud SQL via GitHub Actions
Application deployment is one of the important dimensions, including all of the steps, processes of installing, configuring, and enabling a specific application or set of applications. However, I didn’t figure out well what is continuous integration.
For learning it, I started to deploy a Laravel8 application to Google Cloud Run, develop and deploy highly scalable containerized applications on a fully managed serverless platform. But pieces of information about it scattered out on WEB, and I was desperate to understand it, wasting time.
So I will share with you as easily as possible the way of deployment to Google Cloud Run and Google Cloud SQL via GitHub Actions. Let’s get started!
Directory architecture
The next figure shows us the directory architecture of the git repository.
.
├── .github/
│ └── workflows
│ └── deploy.yml # yml file to run scripts in Github Actions
├── env/
├── docker/
│ ├── 000-default.conf
│ ├── Dockerfile
│ └── php.ini
├── sh/ # useful shell files
├── src/ # the directory installed Laravel application
└── docker-compose.yml
1. Build and run the Laravel application on Docker compose
1-1. Clone a repository from GitHub
In this time, we will proceed as the next repository. Under the directory you like, let’s clone it initially and move there.
$ git clone git@github.com:uuta/cloud-run-laravel8.git
$ cd cloud-run-laravel8
Build and run the Laravel application on Docker compose to make sure of working out well.
$ cp env/.env.example env/.env.local
$ docker-compose up -d --build
...
...
...
Creating cloud-run-laravel8_db_1 ... done
Creating cloud-run-laravel8_app_1 ... done
Time: 0h:00m:24s
The APP_KEY in src/.env would be empty, so create it in a docker container.
$ docker-compose exec app bash
$ php artisan route:clear && \
php artisan cache:clear && \
php artisan config:clear && \
php artisan view:clear && \
php artisan key:generate
A Laravel app key would be generated.
# before
APP_KEY=
# after
APP_KEY=base64:<key>
Alright, we'll see the default page of Laravel to access http://localhost:8080/ after the installation.
How about migration? If you execute the migration command after entering the app container, maybe you can see a message such as the next.
$ docker-compose exec app bash
$ php artisan migrate
Nothing to migrate.
The reason why migration has already finished is to be executed while building a docker image based on Dockerfile. Let’s take a look at Dockerfile.local
for a while.
FROM composer:latest as build
WORKDIR /app
COPY . /app
FROM php:8.0-apache
COPY docker/php.ini /usr/local/etc/php/
RUN apt update
RUN apt install -y git
RUN apt install -y vim
RUN apt install -y zip unzip
RUN docker-php-ext-install bcmath pdo_mysql
# composer install
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
EXPOSE 8080
COPY --from=build /app /var/www
COPY docker/000-default.conf /etc/apache2/sites-available/000-default.conf
RUN chmod 777 -R /var/www
WORKDIR /
RUN echo "Listen 8080" >> /etc/apache2/ports.conf
RUN chown -R www-data:www-data /var/www
RUN a2enmod rewrite
# Make the file executable, or use "chmod 777" instead of "chmod +x"
RUN chmod +x /var/www/sh/laravel/laravel.local.sh
# This will run the shell file at the time when container is up-and-running successfully (and NOT at the BUILD time)
ENTRYPOINT ["/var/www/sh/laravel/laravel.local.sh"]
At the end of this file, there are codes to execute a shell command.
RUN chmod +x /var/www/sh/laravel/laravel.local.sh
...
ENTRYPOINT ["/var/www/sh/laravel/laravel.local.sh"]
So let’s take a look at sh/laravel/laravel.local.sh
file. composer install
and migrate
commands are executed here.
#!/bin/bash
# initialize laravel
cd /var/www/src
composer install --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Run Laravel migration
php artisan migrate
# Run Apache in "foreground" mode (the default mode that runs in Docker)
apache2-foreground
The reason why I dare to make this file is to migrate to Google Cloud SQL in the production environment. According to the next page, the Google Cloud SQL connection is not available during the build phase. Thus, I had to put these commands out for execution after building successfully. In the local environment, this application attempts to connect with the local database instead of Google Cloud SQL.
1-2. More detail of Dockerfile
Let’s see Dockerfile.local
again. I can’t explain everything, but I’ll try to add descriptions briefly. To build an image on Google Cloud Run, shell scripts expose port of 8080.
EXPOSE 8080
...
RUN echo "Listen 8080" >> /etc/apache2/ports.conf
This command copies 000-default.conf
to configure the Apache WEB server.
COPY docker/000-default.conf /etc/apache2/sites-available/000-default.conf
2. Deploy Laravel application to Google Cloud Run
Now let's deploy the Laravel application from the local side. First, let docker containers be down.
$ docker-compose down
2-1. Set up GCP with console
Execute the following commands with checking project name in GCP.
$ gcloud auth login
$ PROJECT_NAME=(<Project name in GCP>)
PROJECT_ID=$(gcloud projects list --format 'value(projectId)' --filter name=$PROJECT_NAME)
export PROJECT_ID
gcloud config set project $PROJECT_ID
gcloud services enable \
containerregistry.googleapis.com \
cloudresourcemanager.googleapis.com \
iam.googleapis.com
2-2. Create an authorized service account
Create a service account and give it Owner privileges. Download the key, save it as laravel8_google_cloud_run_key.json, set it as an environment variable, and authorize it as a service account. The following command makes it possible.
$ gcloud iam service-accounts create deployer \
--display-name "deployer"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member serviceAccount:deployer@${PROJECT_ID}.iam.gserviceaccount.com \
--role roles/owner
export GOOGLE_APPLICATION_CREDENTIALS=credentials/laravel8_google_cloud_run_key.json
gcloud iam service-accounts keys create $GOOGLE_APPLICATION_CREDENTIALS \
--iam-account deployer@${PROJECT_ID}.iam.gserviceaccount.com
gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS
In the above example command, laravel8_google_cloud_run_key.json
is stored in the credentials directory.
export GOOGLE_APPLICATION_CREDENTIALS=~/credentials/laravel8_google_cloud_run_key.json
2-3. Build docker image
Build docker image instead of docker-compose
command.
$ export SERVICE_NAME=docker-laravel8-run
docker build -f docker/Dockerfile.prod -t asia.gcr.io/${PROJECT_ID}/${SERVICE_NAME} .
Run a docker container to work out correctly on the local environment.
$ docker run --init -d -e PORT=8080 -p 8080:8080 asia.gcr.io/${PROJECT_ID}/${SERVICE_NAME}
Make sure the local page works correctly.
2-4. Push the docker image to Container Registry
$ gcloud auth configure-docker
docker push asia.gcr.io/${PROJECT_ID}/${SERVICE_NAME}
You’ll make sure to register a repository named docker-laravel8.
2-5. Enable Cloud SQL Admin API
To connect Google Cloud Run and Google Cloud SQL, try to enable Cloud SQL Admin API.
3. Google Cloud SQL
Create a Google Cloud SQL instance temporarily for test. I’ll create an instance like the following example in this sample, but you can modify configuration for your environment. This process might take a long time.
$ DATABASE_NAME=(docker-laravel8-run)
gcloud sql instances create $DATABASE_NAME \
--database-version=MYSQL_8_0 \
--region=us-central1 \
--storage-size=100 \
--storage-type=HDD \
--tier=db-f1-micro \
--root-password=homestead
- How to securely connect to Cloud SQL from Cloud Run? - Stack Overflow
- How do I connect to Cloud SQL from a Laravel project running on Cloud Run? - Stack Overflow
It would be better to create a database named laravel.
$ gcloud sql databases create laravel \
--instance=$DATABASE_NAME
3-1. Deploy to Google Cloud Run
Let’s try to deploy to Google Cloud Run. Replace <Connection name>
to the Connection name in Google Cloud SQL and execute the following command.
$ SQL_CONNECTION_NAME=(<Connection name>)
gcloud run deploy ${SERVICE_NAME} \
--image asia.gcr.io/"${PROJECT_ID}"/"${SERVICE_NAME}" \
--region asia-east1 \
--platform=managed \
--add-cloudsql-instances ${SQL_CONNECTION_NAME} \
--allow-unauthenticated
A URL will be generated after deploying. Make sure to be shown the page for Laravel8 correctly.
4. Deploy from GitHub Actions
4-1. Brief description
Now we’ve deployed the Laravel application successfully from the Local environment. Then, let’s consider deploying from GitHub Actions. I’m assumed that GitHub Actions runs commands to deploy when commits are pushed to the master or main branch. In this example, the deploy command is defined in .github/workflows/deploy.yml
file. And the trigger for deployment is set when pushing to master
.
name: cloud-run-laravel8-deploy
on:
push:
branches:
- master
Let’s see some more. This remote repository has no .env file to configure the Laravel application by the .gitignore
file. So we should put .env
file only when deploying. Then, by encoding an environment variable decoded as base64 of the .env
file on the local, it’s available to deploy.
- name: Decode .env file
run: |
echo ${{ secrets.TEST_ENV_FILE }} | base64 --decode > src/.env
There are six environment variables in .github/workflows/deploy.yml
file.
- secrets.TEST_GCP_EMAIL: deploy email
- secrets.TEST_GCP_CREDENTIALS: the content of
credentials/laravel8_google_cloud_run_key.json
in your local environment - secrets.TEST_ENV_FILE:
env/.env.prod
- secrets.TEST_GCP_PROJECT: project id
- secrets.TEST_GCP_APPLICATION: service name
- secrets.TEST_SQL_CONNECTION_NAME: SQL connection name
4-2. Change remote repository
Then, change the remote branch to test-cloud-run
. The name can be anything you like.
$ git config remote.origin.url
git@github.com:uuta/cloud-run-laravel8.git
$ git remote set-url origin git@github.com:uuta/test-cloud-run.git
$ git config remote.origin.url
4-3. Enter the environment variables
Enter the environment variables on GitHub like this.
TEST_GCP_EMAIL can be obtained from the Google Cloud Run UI.
Modifying TEST_ENV_FILE is somewhat tricky. First of all, copy the .env.example file as .env.prod.
$ cp env/.env.local env/.env.prod
Modify the .env.prod file as follows, and don't forget to add DB_SOCKET.
APP_NAME=Laravel
APP_ENV=production
APP_KEY=<your app key>
APP_DEBUG=false
APP_URL=<your app url>
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=homestead
DB_SOCKET=/cloudsql/<connection name to Google Cloud SQL>
By the following command, values in the env
file will transfer to string encoded Base64. So you can copy and paste it as the value of TEST_ENV_FILE.
$ cat env/.env.prod | base64 | pbcopy
4-4. Git push, and then deploy automatically
$ git push
After pushing the master repository, it will automatically start deploying in your remote repository. Please check if it succeeds.
You made it!