In this example we are going to manually run Jenkins pipeline stages against our GitHub repository. When we trigger a build in Jenkins, it will connect to GitHub via SSH and run all the stages listed in the Jenkinsfile which is also stored in the GitHub repository.


Flow


Jenkins pulls our dockerised GitHub repository, builds containers, runs all tests in containers, stops containers and clears docker artifacts. That's all!


Prerequisites


Jenkins server GitHub SSH integration


Make sure that the SSH key of Jenkins server is added to GitHub repository. Login to Jenkins UI and do the following.


  1. Click "Credentials" link on the left.

  2. Click "Jenkins/Global" and then "Add credentials" link.

  3. Select "SSH Username and private key" from "Kind" option.

  4. Set a username (e.g. GitHub-inanzzz) for "Username" option.

  5. Tick "Enter directly" radio button and paste content of the ~/.ssh/id_rsa.pub file into the textarea.

  6. Save and exit.

Allow Jenkins user to execute "docker" commands


First of all make sure that the "docker" and "docker-compose" are installed on the Jenkins server. In addition to that, make sure that the jenkins user can execute $ docker ... and $ docker-compose ... CLI commands. Run $ sudo usermod -aG docker $USER and $ sudo usermod -aG docker jenkins commands. You will need to restart the Jenkins server to let the changes take affect. Note: You can use $ sudo -i -u jenkins command to login as jenkins user on Jenkins server.


Setup "Pipeline" project


In Jenkins's main page, click "New Item" link to create a new item called "Test" and then select "Pipeline" option in the following page.


  1. Select "Pipeline script from SCM" option under "Pipeline" section.

  2. Select "Git" as SCM.

  3. Use https://github.com/inanzzz/game for "Repository URL".

  4. Select GitHub-inanzzz from "Credentials".

  5. Optionaly change */master to */develop.

  6. Add ci/pipeline/branch/develop/Jenkinsfile to "Script path".

  7. Save and exit.

If you click "Build Now" link, it should successfuly run the first build and the console output should look like below.


Application structure


.
├── ci
│   └── pipeline
│   └── branch
│   └── develop
│   └── Jenkinsfile
├── composer.json
├── docker
│   └── ci
│   ├── docker-compose.yml
│   ├── .env
│   ├── Makefile
│   └── php
│   └── Dockerfile
├── src
│   └── Football.php
└── tests
   └── FootballTest.php

Files


Jenkinsfile


pipeline {
agent any

options {
skipDefaultCheckout(true)
}

stages {
stage('Checkout SCM') {
steps {
echo '> Checking out the source control ...'
checkout scm
}
}
stage('Git Pull') {
steps {
echo '> Pulling the code from GitHub repository ...'
sh 'git remote update && git checkout develop && git pull origin develop'
}
}
stage('Docker Up') {
steps {
echo '> Building the docker containers ...'
sh 'cd docker && cd ci && make build'
}
}
stage('Composer Install') {
steps {
echo '> Building the application within the container ...'
sh 'cd docker && cd ci && make composer'
}
}
stage('Test') {
steps {
echo '> Running the application tests ...'
sh 'cd docker && cd ci && make test'
}
}
}
}

composer.json


{
"name": "inanzzz/game",
"require": {
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.13",
"phpstan/phpstan": "^0.11.2",
"phpunit/phpunit": "^7.3"
},
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
}
}

docker-compose.yml


version: '3'

services:
php:
build:
context: php
container_name: '${COMPOSE_PROJECT_NAME}_php'
hostname: '${COMPOSE_PROJECT_NAME}-php'
env_file:
- .env
volumes:
- ../../:/app:consistent
working_dir: /app

.env


COMPOSE_PROJECT_NAME=game

Makefile


The reason why we have this file is because the Jenkins Pipeline plugin doesn't run $ docker-composer ... commands.


CONTAINER := game_php

build:
@docker-compose up -d

composer:
@docker exec -i $(CONTAINER) composer install

test:
@docker exec -i $(CONTAINER) vendor/bin/php-cs-fixer fix src --rules=@PSR2 --using-cache=no --dry-run --verbose --diff
@docker exec -i $(CONTAINER) vendor/bin/phpunit tests
@docker exec -i $(CONTAINER) vendor/bin/phpstan analyse src tests --no-progress --level=max
@make -s down

down:
@docker-compose down --volumes
@make -s clean

clean:
@docker system prune --volumes --force

Dockerfile


FROM php:7.2-cli-alpine

RUN apk update \
&& apk add --no-cache $PHPIZE_DEPS \
bash \
git \
zip \
unzip

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

RUN rm -rf /var/cache/apk/*

CMD tail -f /dev/null

Test


If you click "Build Now" link, it should successfuly run the first build and the console output should look like below.


Started by user root
Obtained ci/pipeline/branch/develop/Jenkinsfile from git https://github.com/inanzzz/game
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/game
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Checkout SCM)
[Pipeline] echo
> Checking out the source control ...
[Pipeline] checkout
using credential 1a41495c-ad85
> git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
> git config remote.origin.url https://github.com/inanzzz/game # timeout=10
Fetching upstream changes from https://github.com/inanzzz/game
> git --version # timeout=10
using GIT_SSH to set credentials
> git fetch --tags --progress https://github.com/inanzzz/game +refs/heads/*:refs/remotes/origin/*
> git rev-parse refs/remotes/origin/develop^{commit} # timeout=10
> git rev-parse refs/remotes/origin/origin/develop^{commit} # timeout=10
Checking out Revision 139991 (refs/remotes/origin/develop)
> git config core.sparsecheckout # timeout=10
> git checkout -f 139991
Commit message: "Format"
> git rev-list --no-walk 86e9d7 # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Git Pull)
[Pipeline] echo
> Pulling the code from GitHub repository ...
[Pipeline] sh
+ git remote update
Fetching origin
+ git checkout develop
Previous HEAD position was 139991f... Format
Switched to branch 'develop'
Your branch and 'origin/develop' have diverged,
and have 10 and 1 different commit each, respectively.
(use "git pull" to merge the remote branch into yours)
+ git pull origin develop
From https://github.com/inanzzz/game
* branch develop -> FETCH_HEAD
Merge made by the 'recursive' strategy.
ci/pipeline/branch/develop/Jenkinsfile | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Docker Up)
[Pipeline] echo
> Building the docker containers ...
[Pipeline] sh
+ cd docker
+ cd ci
+ make build
Creating network "game_default" with the default driver
Creating game_php ...
[1A[2K
Creating game_php ... [32mdone[0m
[1B[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Composer Install)
[Pipeline] echo
> Building the application within the container ...
[Pipeline] sh
+ cd docker
+ cd ci
+ make composer
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Generating autoload files
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] echo
> Running the application tests ...
[Pipeline] sh
+ cd docker
+ cd ci
+ make test
php-cs-fixer
phpunit
phpstan
Loaded config default.
.
Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error

Checked all files in 0.008 seconds, 10.000 MB memory used
PHPUnit 7.5.4 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 18 ms, Memory: 4.00MB

OK (1 test, 1 assertion)

[OK] No errors

Stopping game_php ...
[1A[2K
Stopping game_php ... [32mdone[0m
[1BRemoving game_php ...
[1A[2K
Removing game_php ... [32mdone[0m
[1BRemoving network game_default
Total reclaimed space: 0B
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS