A github CI workflow tailored to modern PHP applications (Laravel, Symfony, …)

Mattias Geniar, Thursday, October 3, 2019 - last modified: Sunday, October 6, 2019

Last year we wrote a blogpost about our setup we use for Oh Dear! with Gitlab, and how we use their pipelines for running our CI tests. Since then, we've moved back to Github since they introduced their free private repositories.

In this post I'll describe how we re-configured our CI environment using Github Actions.

If you have a Laravel application or package, you should find copy/paste-able examples here to get it up and running for yourself. This will only require small adaptations for Symfony, Zend, ...

It's all PHP, after all.

A custom Docker container with PHP 7.3 and extensions

I built a custom Docker container with PHP 7.3 and my necessary extensions. You're free to use it yourself, too: mattiasgeniar/php73.

I'm still pretty new to Docker, so if you spot any obvious errors, I'd love to improve the container. Here's the Dockerfile as is. It's up on Github too, so you're free to fork/modify as you see fit to create your own containers.

FROM php:7.3-cli

LABEL maintainer="Mattias Geniar <m@ttias.be>"

# Install package dependencies
RUN apt update && apt install -y libmagickwand-dev git libzip-dev unzip

# Enable default PHP extensions
RUN docker-php-ext-install mysqli pdo_mysql pcntl bcmath zip soap intl gd exif

# Add imagick from pecl
RUN pecl install imagick && echo 'extension=imagick.so' >> /usr/local/etc/php/php.ini

# Install nodejs & yarn
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
    && DEBIAN_FRONTEND=noninteractive apt-get install nodejs -yqq \
    && npm i -g npm \
    && curl -o- -L https://yarnpkg.com/install.sh | bash \
    && npm cache clean --force

# Install composer
ENV COMPOSER_HOME=/composer
ENV PATH=./vendor/bin:/composer/vendor/bin:/root/.yarn/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

WORKDIR /var/www

In short: this takes the official base image of PHP 7.3 and adds some custom extensions, the node & yarn binaries, together with composer.

It's a rather large container in total, because of the dependency of ImageMagick. By needing the source-files, it pulls in more than 100MB of dependencies. Oh well ...

Using this container in your Github Action

You can now use Github Actions to automatically run a set of commands, every time you push code. You can do so by creating a file called .github/workflows/ci.yml . In that file, you'll determine what actions need to run in a container.

Here's our current CI config for Oh Dear.

on: push
name: Run phpunit testsuite
jobs:
  phpunit:
    runs-on: ubuntu-latest
    container:
      image: mattiasgeniar/php73

    steps:
    - uses: actions/checkout@v1
      with:
        fetch-depth: 1

    - name: Install composer dependencies
      run: |
        composer install --prefer-dist --no-scripts -q -o;
    - name: Prepare Laravel Application
      run: |
        cp .env.example .env
        php artisan key:generate
    - name: Compile assets
      run: |
        yarn install --pure-lockfile
        yarn run production --progress false
    - name: Set custom php.ini settings
      run: echo 'short_open_tag=off' >> /usr/local/etc/php/php.ini
    - name: Run Testsuite
      run: vendor/bin/phpunit tests/

You can find a simplified example in my Docker example repo. The one above also takes care of the javascript & CSS compilation using yarn/webpack. We need this in our unit tests, but you might be able to remove those run lines.

Setting this up in your own repository

Here's what was needed to get this up-and-running.

  1. Sign up for the "beta" program of Github Actions. You'll get immediate access, but it's opt-in for now.
  2. Create a file called.github/workflows/ci.yml in your repository, take the content from the example above
  3. git push upstream to Github, the action should kick in after a few seconds

You can find an example Action here: mattiasgeniar/docker-examples/actions.

After that, you should see theΒ Actions tab pick up your jobs.



Hi! My name is Mattias Geniar. πŸ‘‹ I'm an independent software developer ⌨️ & Linux sysadmin πŸ‘¨β€πŸ’», a general web geek & public speaker. Currently working on DNS Spy & Oh Dear! Follow me on Twitter as @mattiasgeniar 🐦.

πŸ”₯ If you're stuck with a technical problem, I'm available for hire to help you fix it!

Share this post

Did you like this post? Help me share it on social media! Thanks. πŸ€—

Have feedback?

New comments have been disabled on this blog, existing comments will remain as-is. Want to give feedback? Is there a mistake in the post?

Send me a tweet on @mattiasgeniar!

Comments

Shuyi Thursday, October 3, 2019 at 20:21 -

Umm, I tried that work flow before, it gets super complicated when you try to connect to db and other services from within container, thus I cannot run migration,… Because Github action base image itself has mysql already installed… How did you resolve that problem?


Mattias Geniar Friday, October 4, 2019 at 09:00 -

it gets super complicated when you try to connect to db and other services from within container

We run our migrations using the sqlite driver, storing the database locally in the container. We don’t need a MySQL service for our testing, just a sqlite database will do.

$ cat .env.testing
[...]
DB_CONNECTION=sqlite
DB_DATABASE=database/database.sqlite

Stephen Musoke Sunday, October 6, 2019 at 18:46 -

Do you commit your .env.testing into version control? Do you run an API tests if so what url do you setup in the testing environment localhost?


Mattias Geniar Tuesday, October 8, 2019 at 12:12 -

Do you commit your .env.testing into version control?

We do, as it contains only testing data (mysql passwords, stripe test keys, …) it doesn’t contain sensitive data.

Do you run an API tests if so what url do you setup in the testing environment localhost?

We do, but I can’t recall having to do something particularly different to get this to work? Our tests contain code like this:

$this->delete("/api/endpoint/{$id}")->assertSuccessful();