image

Gitlab CI Series - Building PHP Containers with Docker and Gitlab

This tutorial expects you to have a basic understanding of Docker and a running Gitlab CI and working Docker registry with a Docker-Runner.

If you are a PHP developer, you will often need a PHP container, either for development or for your live servers. In an active test-driven environment you will also sometimes have the need to test your code with different PHP versions.

As our basic PHP setup stays the same between projects, we build PHP docker images of nearly every version. This tutorial shows you how we do this.

If you want, you can also use our PHP containers directly. See our PHP container registry for details.

The dockerfile

To keep our docker container small, we use the Alpine base images from the official PHP docker repository as a basis. (you can find the complete dockerfile here)

1
FROM php:7.4.0-fpm-alpine

Then we define some variables which will need later on:
3
4
5
6
ENV COMPOSER_ALLOW_SUPERUSER=1 \
    COMPOSER_DISABLE_XDEBUG_WARN=1 \
    PHPREDIS_VERSION=5.1.1 
ENV LD_PRELOAD /usr/lib/preloadable_libiconv.so php

Let’s install some building dependencies:
9
10
11
12
RUN set -xe \
 && apk add --no-cache --virtual .build-deps \            
            tzdata \
	    $PHPIZE_DEPS \

And some packages which we need to remain. Don’t combine this - as we will remove the apk set `.build-deps later on.
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  && apk add gnu-libiconv --update-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ --allow-untrusted \
  && apk add --no-cache \
         openssl-dev \
         bash \
         freetype-dev \
         libpng-dev \
         libjpeg-turbo-dev \
         sqlite-dev \
         curl \
         curl-dev \
         libsodium-dev \
         icu-dev \
         libxml2-dev \
         recode-dev \
         libxslt-dev \
         git \
         postgresql-client \
         postgresql-dev \
         openssh-client \
         libmcrypt-dev \
         libmcrypt \
         libzip-dev \
         libgcrypt-dev \
         oniguruma-dev \
  && apk --update --no-cache add grep \
 

Time to compile some extensions - we don’t need them all the time - but it doesn’t hurt to have them.
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
  && docker-php-ext-configure gd \
      --with-freetype=/usr/include/ \
 
  && docker-php-ext-install -j$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1)\
    	gd \
        bcmath \
        opcache \
        iconv \
        mysqli \
        pdo \
        pdo_mysql \
        pdo_sqlite \
        pdo_pgsql \
        zip \
        xml \
        xsl \
        intl \
        json \
        mbstring \
        curl \
        simplexml \
        soap \
        bcmath \
        redis \
 && docker-php-ext-install sodium \

As mentioned earlier, time to clean up, so we have a nice small container.
64
65
 && apk del .build-deps \
 && rm -rf /tmp/* /var/cache/apk/* 

Now it’s time to set some ini parameters we want to use:
66
67
68
69
70
71
72
73
74
    RUN echo "memory_limit = 128M" > /usr/local/etc/php/conf.d/custom.ini \
    && echo "max_execution_time = 60" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "error_reporting=E_ALL" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "log_errors=On" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "error_log = /proc/self/fd/2" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "expose_php = Off" >> /usr/local/etc/php/conf.d/custom.ini \
    && echo "pm.status_path = /status" >> /usr/local/etc/php-fpm.d/zz-docker.conf \
    && echo "ping.path = /ping" >> /usr/local/etc/php-fpm.d/zz-docker.conf \
    && echo "catch_workers_output = yes" >> /usr/local/etc/php-fpm.d/zz-docker.conf

As we need composer anyway, we install it right in the container:
75
76
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN composer global require hirak/prestissimo --no-plugins --no-scripts

Building it with Gitlab CI

Create a new project in your Gitlab and add the Dockerfile to it.

The next step is to create the `.gitlab-ci.yml file to let Gitlab build our container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
image: docker:latest

services:
  - docker:dind

before_script:
  - docker login -u "gitlab-ci-token" -p "$CI_JOB_TOKEN" $CI_REGISTRY

7.4-fpm:
  stage: build
  tags:
    - docker 
  script:
    - docker build --pull -t "$CI_REGISTRY_IMAGE/fpm:7.4.0" -t "$CI_REGISTRY_IMAGE/fpm:7.4" "."
    - docker push "$CI_REGISTRY_IMAGE/cli:7.4.0"
    - docker push "$CI_REGISTRY_IMAGE/cli:7.4"

The tag docker tells Gitlab to use one of our docker runners.

Push this to your project and watch Gitlab build your PHP container. It will be stored in the Docker registry of the project.

To use it in your own project (or to further extend it in a project) you can use your docker registry url and the project name. You find the complete urls in the registry. In our example it would be dockerhub.cwd.at/docker/php/fpm:7.4.0

Warning

If your project is not public - you will have to docker login to your registry to use your container! (see https://docs.gitlab.com/ee/user/packages/container_registry/#using-with-private-projects for details)