GitLab CE, Docker, PHP7.1, Laravel 5.5, SQLite CI pipeline

Posted on 02 December 2017
5 minute read

I've been coding in PHP for more than 15 years using a variety of environments; Windows, FreeBSD, Linux, MacOS, VMWare+Vagrant+Linux, but more recently, I've been wanting to make the move to Docker.

I've also gone through various methods of working with code bases, from duplicating directories and incrementing version numbers, SVN and now Git, but unlike many, I don't much use GitHub for personal projects and prefer my self-hosted instance of GitLab CE. Git and GitLab have worked fine for some time, but I've recently started thinking about CI. This posed a few issues, but I managed to get it running with GitLab CI using a shell executor. This was OK, it's my own code running on a server I run and maintain myself in my home office, but what if I want to run things under different circumstances, PHP version for example? Welcome Docker =)

I started with a PHP image from phpdockerio, shelled into the container, added some modules etc, logged out of the container and selected to use my local image. Many of you already fluent in Docker will quickly realise this doesn't work! You need more than that... being the Docker n00b that I am, I started to search the interwebz as to why my updates hadn't survived... doh! I need to commit my changes.

Docker environment

So, let's start at the beginning -- I will however, assume that you have Docker installed and running.

Let's pull the initial image

# docker pull phpdockerio/php71-fpm

Once the image has been installed, we can shell into it

# docker run -it phpdockerio/php71-fpm bash

We're now in our container. Let's make sure the OS is updated

# apt update
# apt upgrade -y

Great... now we can install a few extras for PHP

# apt install php-pear php-dev php-xdebug php-mbstring

This will install PEAR, PECL, Xdebug and the PHP MBstring module (I needed this for Laravel projects at the very least).

Let's also make sure we have the latest version of composer installed

# cd /usr/local/bin
# rm composer
# curl -o composer.phar https://getcomposer.org/composer.phar
# chmod 0755 composer.phar
# ln -s composer.phar composer

We should now be good to go. Laravel includes PHPUnit in composer.json, we now have a core install of composer and we have included Xdebug for code coverage reporting.

Before we logout of the container, we need to commit the changes. Open a new terminal and check the running docker containers

# docker ps
40f99b35c775        phpdockerio/php71-fpm           "bash"
# docker commit 40f99b35c775 mycontainers/php71-fpm:1.0.0

Let's check to make sure the new image has been created

# docker images
REPOSITORY               TAG         IMAGE ID
mycontainers/php71-fpm   1.0.0       2feaea24d6a3

Now we have our new image with all the extras included, we can remove the original

# docker rmi -f phpdockerio/php71-fpm:latest

Next up, we need to configure our GitLab runner. You can find the info for this over on GitLab's Docker Runner documentation page.

Now we need to update the runner config to use our newly created image

# docker exec -it gitlab-runner bash

Once inside the container, edit the runner config located at /etc/gitlab-runner/config.toml and add a pull_policy entry of never

[[runners]]
  name = "docker-container"
  url = "https://gitlab.mydomain.com/ci"
  token = "{your-token-here}"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "mycontainers/php71-fpm:1.0.0"
    privileged = false
    disable_cache = false
    volumes = ["/cache"]
    shm_size = 0
    pull_policy = "never"
  [runners.cache]

This will make sure that your pipeline only uses your local docker image. Save the change and logout of the container.

GitLab CI config

If you haven't already, create a gitlab-ci.yml file in the root of your project. For my most recent project project, I'm using an SQLite database.

First, we define an image to use -- this will be our newly created PHP docker image

image: mycontainers/php71-fpm

We can also cache items between builds, so let's cache our vendor directory

cache:
  paths:
  - src/vendor/

I have my code within a src directory, so before we do anything, I change to that dir and also make sure that composer is the most current

before_script:
- cd src
- composer self-update

As this project is using SQLite, I have no services or variable requirements, so the next stage is the test where we run through PHPUnit.

I use a separate .env.testing env file so I can commit this safely to the repo and modify it just for tests, so we copy it as .env, update the path to the SQLite database, generate a new app key, create the cache, create the database and run the test suite

test:
  script:
  - cp .env.testing .env
  - sed -i "s@DB_DATABASE=@DB_DATABASE=$(pwd)/database/appdatabase\.db@" .env
  - php artisan key:generate
  - php artisan config:cache
  - touch ./database/appdatabase.db
  - php artisan migrate:refresh --seed
  - vendor/bin/phpunit --configuration phpunit.xml --colors=never --coverage-text

The complete gitlab-ci.yml file should then look like

# Select local image
image: mycontainers/php71-fpm:1.0.0

# Select what we should cache between builds
cache:
  paths:
  - src/vendor/

before_script:
- cd src
- composer self-update

test:
  script:
  - cp .env.testing .env
  - sed -i "s@DB_DATABASE=@DB_DATABASE=$(pwd)/database/appdatabase\.db@" .env
  - php artisan key:generate
  - php artisan config:cache
  - touch ./database/appdatabase.db
  - php artisan migrate:refresh --seed
  - vendor/bin/phpunit --configuration phpunit.xml --colors=never --coverage-text

If everything has gone well (including your tests and code), we should have a successful process with the all important Passed icon =)

This is about the simplest pipeline I could create with this scenario. YMMV in what you need / want in regards to modules and more complex applications (mine is a simple app in the early stages of development) but I hope this may help someone else thinking about going down this path.