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

Boyan Yankov | CTO at Web Motion
Boyan Yankov | CTO at Web Motion
Angel Dobrinov | Sr. Cloud Architect at Cloud Office
Angel Dobrinov | Sr. Cloud Architect at Cloud Office

For any questions regarding your upcoming software development projects, don’t hesitate to reach out to office@webmotion.bg