Gitpodifying a new Laravel Application

Tuesday, May 12, 2020

by  Etin Obaseki  Etin Obaseki

Introduction

Gitpod is a SaaS platform that offers Ready-To-Code dev environments. What this means is that GitPod allows you describe your Development Environment as code and easily provision or share it.

This is great for letting people quickly try out a new library or framework and even for day to day development.

This article from the GitPod blog is a great explanation for the problem GitPod solves as well as a fantastic general purpose guide.

Here, we’ll extend on that specifically for the Laravel framework and see how to create a Ready-To-Code environment for our projects.

Getting Started

The first step is to navigate to the GitHub repository you’d like to set up.

For this example, I’ll use a Laravel project we’re building at WTXtra.

The GitHub repository is publicly available at https://github.com/WeTalkSound/trivyeah-core/.

Next, we’ll just edit this URL slightly to launch a Gitpod workspace for this repo.

We’ll add “gitpod.io/#” in front of the repo URL and hit enter. For the example above, that’ll be https://gitpod.io/#https://github.com/WeTalkSound/trivyeah-core/

For first time Gitpod users, you’ll be asked to sign in with GitHub and authorize the application.

When we go to this URL, Gitpod begins building our workspace and you’ll see the splash screen.

To determine how to set up the environment, Gitpod follows the instructions in a .gitpod.yml file. If this file does not exist, then it approximates an environment based on your repo.

Set up .gitpod.yml

The gitpod.yml file specifies the commands that the environment should run on build as well as the Dockerfile to use to build the container. Adding a custom Dockerfile is optional, but potentially offers us more control over the environment we end up with.

So, we’ll create a simple .gitpod.yml file with the following:

image:
  file: .gitpod.Dockerfile

And right next to it, create a .gitpod.Dockerfile like so:

FROM gitpod/workspace-mysql               

USER gitpod

This uses the gitpod/workspace-mysql as the base image. This image installs and configures MySQL with a root user with no password.

This works to get you an environment with (almost) everything you need, but there is still some basic housekeeping most Laravel apps will need to take care of.

Setting up a Database

On the first run of our environment, we’ll need to create the database our app will use.

If you’re familiar with docker, you can easily do this from within your custom Dockerfile. However, I prefer to do it from the .gitpod.yml file.

Within the Gitpod configuration file, we can specify any number of tasks to be run. We can specify tasks to be run only the first time a container is being provisioned or we can specify that it be run even time the container starts.

image:
  file: .gitpod.Dockerfile
tasks:
  - init: mysql -u root -e "create database trivyeah"

We’ll modify our .gitpod.yml file and add the tasks section to it. It specifies that on init, we’ll run the command mysql -u root -e "create database trivyeah" which will log into MySQL as root (with no password) and execute the command specified by the e flag, in this case, creating a database called trivyeah.

Creating a .env file

We’ll use the .env.example file provided by default with new Laravel installations. In here, we’ll specify all the keys that need to be used by the application, including the database name we created above.

...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=trivyeah
DB_USERNAME=root
DB_PASSWORD=
...

Any other default information we’ll need to provision our app can be added to the env.example file.

Next, we will copy the .env.example file to the .env file

image:
  file: .gitpod.Dockerfile
tasks:
  - init: mysql -u root -e "create database trivyeah" && cp .env.example .env

We’ll add this to the init command, since we only want it to copy for the first run of the container.

Installing Dependencies

This is another action we will need to perform on initialization of the environment. We’ll run the composer and npm (or yarn, if you prefer) commands in the init action as well.

image:
  file: .gitpod.Dockerfile
tasks:
  - init: mysql -u root -e "create database trivyeah" && cp .env.example .env && composer install && npm install

At this point, we can begin using artisan commands for any other application specific setup we need to.

For example, we’ll need to generate a unique application key for our .env

Generate Application Key

In a new Laravel application, we’ll need to run the php artisan key:generate command to fill the APP_KEY field in the .env file.

However, it’s a command we’ll only need to run the first time. So, we’ll add it to the init field of our configuration again.

image:
  file: .gitpod.Dockerfile
tasks:
  - init: mysql -u root -e "create database trivyeah" && cp .env.example .env && composer install && npm install && php artisan key:generate

At this point, all the set up should be done. If you have any other setup specific to your Laravel app, they can be added here as well.

The init entry is getting unwieldy and so we’ll refactor it as soon as we’re done with everything else.

The next thing to do is run migrations before we serve the app.

Run Migrations

For migrations, I prefer to run them each time I use the container, in case there has been a change.

I also run the seeders at this point to populate the database with any required data or test data.

Since I want the command to always run, I’ll add it to a command entry on the .gitpod.yml file.

image:
  file: .gitpod.Dockerfile
tasks:
  - init: mysql -u root -e "create database trivyeah" && cp .env.example .env && composer install && npm install && php artisan key:generate
    command: php artisan migrate --seed

Apart from running each time we run our environment, command task entries have another key differences from init tasks.

An init task expects to end. Long running processes such as running a development server or continuously compiling assets are not suited for init tasks. Command tasks on the other hand can run for as long as the environment is running and are suited to such tasks above.

Run Development Server

We can run the development server on Gitpod as well. We’ll want to run it after migrations are done.

image:
  file: .gitpod.Dockerfile
tasks:
  - init: mysql -u root -e "create database trivyeah" && cp .env.example .env && composer install && npm install && php artisan key:generate
    command: php artisan migrate --seed && php artisan serve

Running and exposing a service on Gitpod creates a publicly accessible URL, similar to ngrok, of the form <PORT>-<GITPOD-CONTAINER-ID>. gitpod.io

There’s a small problem with our current setup though. Laravel uses the value of $_SERVER['HTTP'] to generate URLs and Routes. This value will be set to localhost:8000 and not to the correct Gitpod URL.

To work around this, we’ll set our APP_URL in .env to our Gitpod URL and then instruct Laravel to use that value to generate URLs and Routes.

We’ll update our .env.example to the following:

APP_NAME="Your App Name"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=
...

Gitpod loads some environment variables. You can run env on the terminal to see all environment variables.

One of them is GITPOD_WORKSPACE_URL which is the same as the one in your browser when you launch a workspace. We’ll need to write the variable into our .env file.

We’ll use the bash utility sed for this. Essentially, we’ll do two replacements. One to put in the workspace URL and another to add the port number in front.

 sed -i "s|APP_URL=|APP_URL=${GITPOD_WORKSPACE_URL}|g" .env && sed -i "s|APP_URL=https://|APP_URL=https://8000-|g" .env

The first pass replaces “APP_URL=” with “APP_URL=https://<your-workspace-id>.gitpod.io” and the second pass replaces “APP_URL=https://” with “APP_URL=https://8000-“

We’ll now add this to our init entry in .gitpod.yml so that it happens right after our .env file is created.

image:
  file: .gitpod.Dockerfile
tasks:
  - init: mysql -u root -e "create database trivyeah" && cp .env.example .env && sed -i "s|APP_URL=|APP_URL=${GITPOD_WORKSPACE_URL}|g" .env && sed -i "s|APP_URL=https://|APP_URL=https://8000-|g" .env && composer install && npm install && php artisan key:generate
    command: php artisan migrate --seed && php artisan serve

That init hook has gotten quite unwieldy and we’ll address it shortly. In the interim, we’ll need to add some code in our AppServiceProvider to force Laravel to use our APP_URL to generate URLs and Routes.

 public function boot()
    {
        \URL::forceRootUrl(\Config::get('app.url'));
        if (\Str::contains(\Config::get('app.url'), 'https://')) {
            \URL::forceScheme('https');
        }
    }

The code we’ve added forces Laravel to use the app.url config entry (which is gotten from .env’s APP_URL) as the root URL.

Warning: If you use an older version of Laravel the \Str::contains() method won’t work and you’ll have to use the string helper str_contains() instead.

Cleaning Up .gitpod.yml

The init entry in our Gitpod configuration file is messy. We can move all that to a bash script so that it’s more readable.

Let’s create a .gitpod-init.sh file in the root of our project and copy everything in our init entry there.

mysql -u root -e "create database trivyeah"
cp .env.example .env
sed -i "s|APP_URL=|APP_URL=${GITPOD_WORKSPACE_URL}|g" .env
sed -i "s|APP_URL=https://|APP_URL=https://8000-|g" .env
composer install
npm i
php artisan key:generate

And finally, we’ll update our .gitpod.yml to use this command

image:
  file: .gitpod.Dockerfile
tasks:
  - init: bash .gitpod-init.sh
    command: php artisan migrate --seed && php artisan serve

Careful with the filenames. Watch the “.” in front.

Everything should work now.

Running our environment

At this point, we can push to our repository and run the environment. If you already had a workspace for this repo and branch, you can delete it by going to https://gitpod.io/workspaces.

Now we can create and test a new workspace by going to “gitpod.io/#https://github.com/<org-name>/<repo-name>” as we described at the beginning of the article.

If everything works as it should, we can add a badge to the Readme.md that can be clicked to launch the workspace.

[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/<org-name>/<repo-name>) 

Our Laravel project is now Gitpod enabled and can deploy ready-to-code environments in one click.

Many thanks to Gabriel Nwogu for testing this out and suggesting changes.