How to Install OctoberCMS (Laravel) Application on Google App Engine
A Case Study by WebMotion.bg and CloudOffice.bg
The article is about Software development. It is provided by a Sr. Lead Developer at Web Motion and will be helpful to software developers, DevOps, software architects, and managers.
At Web Motion we were completing a medium-sized software development project based on OctoberCMS (Laravel) and when it was time for deployment we decided to deploy on Google App Engine in the Google Cloud Platform with the help of our Google infrastructure and architecture partners – Cloud Office.
After comparing the features of Standard Environment and Flexible Environment, we decided that the Flexible Environment is more appropriate for OctoberCMS because it allows accessing the console and making more configurations. We used Google’s official examples related to setting up Laravel on App Engine Standard Environment and Flexible Environment as a starting point. These examples covered only the basics, did not include real storage setup, by the time of writing they are outdated (2-4 years old), and they contained some minor errors in the provided code. We could not find any examples of people who have successfully set up OctoberCMS on this infrastructure before and therefore we were on our own. The task proved challenging, but after some trial and error, we did it and now our OctoberCMS (Laravel) project works stably on Google App Engine Flexible Environment.
Setting Up the Google Cloud Platform Infrastructure
You would need an App Engine Flexible Environment, a SQL instance, and a storage bucket. Before you begin setting up the application, make sure you are ready with setting up the Google Cloud Platform infrastructure.
Setting Up the OctoberCMS (Laravel) Application
Preparing the app.yaml file
Create a file named app.yaml in the project root to set up the App Engine as a flexible environment. The variables in section “env_variables” are for configuring Laravel and OctoberCMS and will be explained in the sections below. Our app.yaml file is shown as an example below:
runtime: custom
env: flex
runtime_config:
document_root: .
resources:
cpu: 2
memory_gb: 2.3
disk_size_gb: 10
volumes:
- name: ramdisk1
volume_type: tmpfs
size_gb: 0.5
manual_scaling:
instances: 1
vpc_access_connector:
name: projects/insert-project-name/locations/europe-west3/connectors/insert-connector-name
network:
instance_tag: insert-instance-tag
name: default
env_variables:
APP_STORAGE_PATH: gs://inset-bucket-name/storage/app
APP_STORAGE_URL: https://storage.googleapis.com/inset-bucket-name/storage/app
APP_DEBUG: false
APP_ENV: production
SESSION_DRIVER: cookie
DATABASE_HOST: insert-database-ip
DATABASE: insert-database-name
DATABASE_USERNAME: insert-database-user
DATABASE_PASSWORD: insert-database-password
DATABASE_PORT: 3306
URL: insert-app-engine-url
KEY: insert-laravel-key
CIPHER: AES-256-CBC
Preparing the Dockerfile
The app.yaml specifies “env: flex” which allows configuring the docker container, in which the app will run, with a Dockerfile. Create a file named Dockerfile in the project root to set up the docker container. Our Dockerfile is shown as an example below:
FROM gcr.io/google-appengine/php73
ARG COMPOSER_FLAGS='--prefer-dist'
ENV COMPOSER_FLAGS=${COMPOSER_FLAGS}
ENV SWOOLE_VERSION=4.3.4
ENV DOCUMENT_ROOT=/app
COPY . $APP_DIR
RUN apt-get update -y \
&& apt-get install -y \
unzip \
autoconf \
build-essential \
libmpdec-dev \
libpq-dev \
&& pecl install decimal \
&& curl -o /tmp/swoole.tar.gz https://github.com/swoole/swoole-src/archive/v$SWOOLE_VERSION.tar.gz -L \
&& tar zxvf /tmp/swoole.tar.gz \
&& cd swoole-src* \
&& phpize \
&& ./configure \
--enable-async-redis \
&& make \
&& make install \
&& chown -R www-data.www-data $APP_DIR \
&& /build-scripts/composer.sh \
&& mv $APP_DIR/storage $APP_DIR/storagestatic \
&& ln -s /tmp $APP_DIR/storage \
&& cp -TRv $APP_DIR/storagestatic/ $APP_DIR/storage/ \
&& chown -R www-data.www-data $APP_DIR/storage \
&& chmod -R 777 $APP_DIR/storage ;
ENTRYPOINT ["/build-scripts/entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
EXPOSE 8080
The docker container would not have the right PHP version and the necessary extensions unless you require them. Make sure you do it in the composer.json file. Below is a code snippet from our project:
"php": ">=7.2",
"ext-gd": "*",
We made the storage folder writable through the Dockerfile so that OctoberCMS (Laravel) can write temporary files and cache them into the folder inside the Docker container. The user-generated content is stored in a storage bucket. A challenge that we have faced is that we could not mount the storage bucket into the docker container, because of insufficient permissions. We ended up setting up Laravel to write files through the “gs://” protocol.
Changes in OctoberCMS (Laravel)
We had to install the “google/cloud-storage” dependency to allow Laravel to use the “gs://” protocol. Once installed, Laravel can read and write paths starting with “gs://”. The code snippet below allows Laravel to use the Google Cloud Storage Client:
//allow google cloud storage
use Google\Cloud\Storage\StorageClient;
$storage = new StorageClient([
// optionally specify the keyfile path here
'keyFilePath' => 'storage-keyfile.json',
'projectId' => 'ageweb-care-prod-env',
]);
$storage->registerStreamWrapper();
The browser still cannot read and write files if the paths start with gs://. Therefore, all URLs shown in the frontend must instead start with “https://storage.googleapis.com/”. We had to modify 3 files in the “config” folder to use the “env_variables” from the app.yaml file. These are the same variables you would normally put in the .env file.
Code snippet from the file config/database.php:
'mysql' => [
'driver' => 'mysql',
'engine' => 'InnoDB',
'host' => env('DATABASE_HOST','localhost'),
'port' => env('DATABASE_PORT','3306'),
'database' => env('DATABASE','dev_ageweb_care1'),
'username' => env('DATABASE_USERNAME','root'),
'password' => env('DATABASE_PASSWORD',''),
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
'prefix' => '',
'varcharmax' => 191,
],
Code snippet from the file config/filesystems.php:
'local' => [
'driver' => 'local',
'root' => env('APP_STORAGE_PATH', storage_path('app')),
'url' => env('APP_STORAGE_URL', '/storage/app'),
],
Code snippet from the file config/cms.php:
'storage' => [
'uploads' => [
'disk' => 'local',
'folder' => 'uploads',
//'path' => '/storage/app/uploads',
'path' => env('APP_STORAGE_URL', storage_path('/storage/app')).'/uploads',
'temporaryUrlTTL' => 3600,
],
'media' => [
'disk' => 'local',
'folder' => 'media',
//'path' => '/storage/app/media',
'path' => env('APP_STORAGE_URL', storage_path('/storage/app')).'/media',
],
'resized' => [
'disk' => 'local',
'folder' => 'resized',
//'path' => '/storage/app/resized',
'path' => env('APP_STORAGE_URL', storage_path('/storage/app')).'/resized',
],
],
Configuring paths was not a problem in Laravel, but for OctoberCMS we had to make some changes in the code of our theme. You should check the path in your setup and test with your theme and modules. The custom function below overwrites URLs for being used in the frontend.
//custom file path conversion function
function gcp_gs_path($path){
$path = str_ireplace('/app/storage/app', 'gs://ageweb-care-bucket/storage/app', $path);
$path = str_ireplace('/app/https:/storage.googleapis.com/insert-bucket-name/storage/app', 'gs://insert-bucket-name/storage/app', $path);
return $path;
}
Limitations
Editing capabilities of OctoberCMS
Data can be stored on the Docker instance only temporarily – in the “/tmp” folder. PHP does not have the right to modify the code of the application in the /app folder. Even if the code is modified, the changes are lost during the next deployment and can cause issues when scaling is enabled. As a result of this limitation, editing pages in OctoberCMS is not possible within the App Engine environment. Our solution is to edit the pages locally or using a staging server, then commit them to the code repository and deploy them on Google Cloud Platform.
Mounting the storage bucket
We used a storage bucket for storing user generated files. The storage bucket can be mounted with FUSE. Unfortunately, we could not find a way to mount the storage bucket in the Docker container, because the container did not have enough permissions. We had to use the less elegant solution of accessing the files through the “gs://” protocol, which required making changes to the application.
About the Authors
For any questions regarding your upcoming software development projects, don’t hesitate to reach out to office@webmotion.bg
Recent Blog Posts
- Web Motion is Named by The Manifest as Bulgaria’s Most Reviewed B2B Partner for 2022
- How to Install OctoberCMS (Laravel) Application on Google App Engine
- Enabling digital transformation – a webinar for Confindustria Bulgaria
- Lecture for Academy for Women Entrepreneurs at New Bulgarian University
- Web Motion OOD concluded an administrative contract for the provision of grants № BG16RFOP002-2.073-4180-C01