Enterprise-Ready Laravel with Openshift

Timothy Teoh
8 min readMay 25, 2018

--

This post was written for the Laravel Blog Contest , see the entries here and vote if you find this interesting!

For small projects, pretty much anything will do to host Laravel. For larger organizations or complex apps spanning multiple services, these problems become common:

  • How do you provision clusters of apps dynamically and securely, and in a reproducible, disposable way?
  • How do you ensure the runtime environment of your apps are both reproducible yet flexible?
  • What is the best way to avoid having to run npm install and composer install in production?

The answer is inevitably Kubernetes.

Openshift from Redhat is Kubernetes-as-a-service, with snazzy features and a great GUI and CLI tools — you get the power of Kubernetes without having to worry about maintaining its internals.

This post assumes familiarity with Laravel and Docker, although it should suffice if you have both installed.

The Application

I needed an app that was decently complex to showcase, and after a quick search settled on Laravel Starter Kit. I’ve forked it at https://github.com/chalcedonyt/laravel-starter-kit which is where you’ll find the code I go through in this post.

Signing Up

Go to https://www.openshift.com/learn/get-started/ and sign up for a starter account. Create a project, we’ll call this my-laravel-app:

To get a taste of what Openshift can do, let’s go ahead and spin a MySQL instance we’ll use later. Click on the Catalog and click on “MySQL”.

When prompted, accept the default values.

This quickly spins up a MySQL instance. Click on “Resources > Secrets” and click on the “mysql” secret. You’ll notice you have a set of secure credentials you can use in your application.

Deploying code to Openshift

The Openshift Catalog ships with a simple Laravel template, but there are some things we can learn by building our own Laravel app on Openshift:

  • How to compile our code before deploying it. We don’t want to have to run npm install , composer install , or npm run production on a production server.
  • Control over the runtime (the environment) the code runs in.
  • How to come up with a reproducible runtime
  • How to gracefully bring up a new version of our code.

Builder and Runtime images

Openshift introduces the idea of a builder and runtime image with a framework called Source-to-Image (S2I). The goal of S2I is to provide a framework which produces a runnable Docker image containing everything you need to serve your app.

A Builder Image contains the environment in which we “compile” the app before passing it on to the next step. This happens in Laravel when we compile Javascript files — our final image doesn’t need NodeJS or npm, it just needs the files produced by npm run production . When building, it runs its assemble script. The result is an artifact that is passed to the runtime image.

A Runtime Image contains the environment with software that serves the app. For Laravel, this is typically a PHP7 image with Apache/Nginx installed. When building, it runs its assemble-runtime script. The result is a runnable image. S2I expects arun script in the runtime image for Docker CMD.

S2I files typically live in the s2i folder. We’ll create these files:

s2i
| bin
| -- assemble
| -- assemble-runtime
| Dockerfile.runtime
| Dockerfile.builder

The Builder Image

Dockerfile.builderis based on the official NodeJS image and copies the contents of s2i/bin to $STI_SCRIPTS_PATH , setting appropriate permissions.

The assemble script runs npm , deletes unneeded files, and exports a build artifact.

The Runtime Image

To create a runtime image, we’ll copys2i/bin/assemble-runtime from the official version, which runs Composer, but add some lines to restore the artifact created by our build step:

The Dockerfile for our runtime is based on the official PHP7.1 image, and copies our assemble-runtime file. This is also where you would install any additional PHP extensions if you need them.

IMPORTANT: The base image includes a run script that runs Apache in the foreground on port 8080. Its document root can be changed with the DOCUMENTROOTvariable, relative to WORKDIR (i.e. /public for Laravel apps).

Creating our runnable Docker image

We want to build and tag our builder image and runtime image, then run s2i build to produce a runnable image. To make this easier, we fall back on a good ol’ Makefile:

Note thats2i build accepts either a code repo or a directory to read its code from.

Running make s2i && make runnable-image will create an image ( my-laravel-app/laravel-starter-kit:latest) that is a runnable instance of our code, with Javascript compiled and composer dependencies installed. You can run it locally:

docker run -e DOCUMENTROOT=/public -p 8080:8080 my-laravel-app/laravel-starter-kit:latest

All we need now is a database. We can complete our Makefile by adding code to push our image into the Openshift registry:

To authenticate into the Openshift docker registry and get the registry address,

  • Click on your name on the top right of the console, then click “Copy login command”. Paste this into your terminal.
  • Click on the button next to your name and click on “About”.
  • Copy the value under “Authenticating”, and use that in your terminal to log into the registry. Also use the registry address in your Makefile.

Running make publish should now push your image to Openshift.

IMPORTANT: The project name ( my-laravel-app) needs to match the Openshift project you created.

Creating an OpenShift template

An Openshift template is a description of the cluster we will create.

Click on “Add to Project > Import YAML/JSON” on the top right of your Openshift Console, paste this snippet in, and click “Create”, then “Continue”.

Let’s stop to break down the template we are about to process:

Parameters

Parameters are configurable variables for our app, which we can use throughout the template.

Other than typical Laravel environment values( APP_KEY, APP_ENV, APP_DEBUG, etc) we also define:

  • NAME: What we’ll call our Openshift app — to keep it simple we’ll use this name for stuff that Openshift will create.
  • APP_IMAGE_NAME and APP_IMAGE_TAG: The name and tag of the runnable image we’ve built.
  • The MySQL instance we created earlier automatically creates an internal service called mysql, which we use as the DB_HOST.
  • APP_URL is a dummy value we’ll update later.

The DeploymentConfig

A DeploymentConfig contains information about how to deploy and configure our app.

This snippet may look intimidating, but let’s break it down:

  • replicas determines how many instances of our app there will be (keep this at 1 for now, as our starter account has strict usage limits)
  • triggers determine how when our app deploys itself. Here we set it to deploy from 1) A change in configuration (e.g. updating an environment variable), 2) An ImageStream — pushing new images to my-laravel-app/laravel-starter-kit:latestautomatically triggers a deployment that gracefully replaces the previous code.
  • env lists the environment variables for our application. The value of every variable can be one of: 1) A simple string, 2) A template Parameter e.g. ${APP_DEBUG}, or 3) loaded from a Secret.

Using Secrets, we can securely store a reference to the database password and username that were generated earlier!

The Service

A Service is a proxy to the containers deployed by our DeploymentConfig. We’ll connect it to port 8080 (remember that the official PHP7.1 image exposes Apache on port 8080). Remember that deploying MySQL from the catalog earlier automatically created a service called mysql which could be connected to internally.

Creating the cluster

Hit “Create” to process the template, wait for a bit and you’ll see the app appear!

Click on the pod circle, and the Terminal tab. This gives us access to the application, where we can issue our artisan commands:

php artisan migrate:refresh --seed#https://laravel-starter-kit-docs.darrylfernandez.com/installation/
php artisan storage:link

Creating a Route

Almost there! Now all we need is a public URL. Click on “Create Route” under the Networking section, and hit “Create”.

Wait for a minute and you should see your app for the first time!

Login with the credentials admin@gmail.com / 12345678 and smile in triumph.

Making Laravel stateless

For an app to be scalable across multiple web servers, it needs to be stateless. Laravel ships with a default session driver of file, which uses (ephemereal) local storage — not the statelessness we want.

//config/sessions.php
...
'driver' => env('SESSION_DRIVER', 'file'),

We can add a new environment variable by editing the DeploymentConfig. Click on the link to it:

Click on the “Environment” tab, “Add Value”, and set SESSION_DRIVER to cookie. While you’re at it, modify APP_URL to the dynamically generated URL you got earlier.

After clicking “Save”, you’ll notice Openshift perform a rolling update — a new container is spun up with your runnable image, and gracefully takes the place of the previous one.

Welcome to Enterprise-ready Laravel!

That’s it — we managed to deploy an existing Laravel app to Openshift quite quickly, and made it scale.

By going for a paid account or running the open-source version of Openshift on your own, you’ll be able to do things like quickly scale up the number of pods and assign predefined route URLs in templates.

There are many other great ways to use and supplement Laravel in Openshift:

  • With CronJobs and Jobs, you can run queue workers and scheduled tasks off the same runnable images.
  • Spin up Redis instances instantly.
  • Run your own free object storage with a Minio instance

Credits to my colleagues Alan Burgoyne and Dmitry Gordienko, who pioneered Openshift where I work.

--

--

Timothy Teoh

Full-stack software architect and technology leader from Kuala Lumpur, Malaysia