In this example we are going to deploy a Dockerised application with Ansible to staging server as part of Jenkins CI/CD pipeline. Read below for the actual flow.


Perquisites for Jenkins server


Make sure you covered points below.



We have a Jenkins and a Staging servers with details below.



Flow


The Jenkins and GitHub integration is already set up so when I merge a PR to develop branch, GitHub communicates with Jenkins where deployment pipeline runs.


  1. Jenkins pulls the repository.

  2. Jenkins builds the docker images.

  3. Jenkins pushes the docker images to DockerHub.

  4. Jenkins cleans the docker artefacts.

  5. Ansible SSH into staging server and start deployment process.

    1. Create the application directory.

    2. Copy docker files over.

    3. Bring the application up

    4. Install secure-delete package.

    5. Securely delete everything copied over.

Structure


.
├── cicd
│   ├── merge
│   │   └── develop
│   │   └── Jenkinsfile
│   └── provision
│      └── stag
│      ├── hosts.yml
│      └── site.yml
├── docker
│   └── stag
│   ├── docker-compose.yml
│   ├── Makefile
│   └── php
│   └── Dockerfile
├── .dockerignore
├── .env
└── src
└── test.php

Files


cicd/merge/develop/Jenkinsfile


pipeline {
agent any

options {
skipDefaultCheckout(true)
}

stages {
stage('Git') {
steps {
echo '> Checking out the Git version control ...'
checkout scm
}
}
stage('Build') {
steps {
echo '> Building the docker images ...'
sh 'make -sC docker/stag build'
}
}
stage('Push') {
steps {
echo '> Pushing docker images to DockerHub ...'
sh 'make -sC docker/stag push'
}
}
stage('Destroy') {
steps {
echo '> Destroying the docker artifacts ...'
sh 'make -sC docker/stag destroy'
}
}
stage('Deploy') {
steps {
echo '> Deploying the application ...'
sh 'ansible-playbook cicd/provision/stag/site.yml -i cicd/provision/stag/hosts.yml'
}
}
}
}

cicd/provision/stag/hosts.yml


all:
hosts:
staging:
ansible_connection: ssh
ansible_user: vagrant
ansible_host: 192.168.99.30
ansible_port: 22

cicd/provision/stag/site.yml


I manually installed docker and docker-compose onto the staging server but ideally it should be done here. Also this file is open for improvements such as making use of variables so on.


---
# All tasks below are run in "staging" server.

- name: Deploy the application to the "staging" server
hosts: staging
remote_user: vagrant
become: yes
tasks:
- name: Create the application directory
file:
path: /home/vagrant/mini
state: directory
owner: vagrant
group: vagrant
- name: Copy docker files over
copy:
src: ../../../docker/stag/
dest: /home/vagrant/mini/docker/stag
owner: vagrant
group: vagrant
- name: Bring the application up
make:
chdir: /home/vagrant/mini/docker/stag
target: run
- name: Install "secure-delete" package
apt:
name: secure-delete
state: present
- name: Secure deleting application files
command: srm -vzr /home/vagrant/mini

docker/dev/php/Dockerfile


We are copying the application files into the image so the files won't be exposed to host OS after build process.


FROM alpine:3.9

RUN apk add --no-cache php7

COPY . /app

WORKDIR /app

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

CMD tail -f /dev/null

docker/stag/docker-compose.yml


Our image is already on the DockerHub.


version: "3"

services:
mini_php:
build:
context: "../.."
dockerfile: "docker/stag/php/Dockerfile"
image: "inanzzz/mini_php:latest"
env_file:
- "../../.env"

docker/stag/Makefile


build:
@docker-compose build

pull:
@docker-compose pull

push:
@docker-compose push

up:
@docker-compose up -d

destroy:
@docker system prune --force --filter 'until=2h'
@docker volume prune --force

run:
@make -s pull up destroy

src/test.php


<?php
echo 'success';
echo file_get_contents('./.env');
print_r($_SERVER);
echo getenv('DB_USER').PHP_EOL;
echo getenv('DB_PASS');

.dockerignore


.git/
.idea/
.DS_Store/
.gitignore
.dockerignore
readme.md
docker/
cicd/

.env


DB_USER=root
DB_PASS=password

Test


Assume that we have merged a PR info develop branch.


Jenkins console output


Running on Jenkins in /var/lib/jenkins/workspace/mini-merge-develop

[Pipeline] Start of Pipeline
> Checking out the Git version control ...
using GIT_SSH to set credentials
Checking out Revision 11d8e7e0a0584ue4112d4b37ee2f0a2df18abar5 (origin/develop)

> Building the docker images ...
+ make -sC docker/stag build
Successfully built cefb05a3fe90
Successfully tagged inanzzz/mini_php:latest

> Pushing docker images to DockerHub ...
+ make -sC docker/stag push
Pushing mini_php (inanzzz/mini_php:latest)...

> Destroying the docker artefacts ...
+ make -sC docker/stag destroy
Total reclaimed space: 0B

> Deploying the application ...
+ ansible-playbook cicd/provision/stag/site.yml -i cicd/provision/stag/hosts.yml
PLAY [Deploy the application to the "staging" server] **************************

TASK [Gathering Facts] *********************************************************
ok: [staging]

TASK [Create the application directory] ****************************************
changed: [staging]

TASK [Copy docker files over] **************************************************
changed: [staging]

TASK [Bring the application up] ************************************************
changed: [staging]

TASK [Install "secure-delete" package] *****************************************
ok: [staging]

TASK [Secure deleting application files] ***************************************
changed: [staging]

PLAY RECAP *********************************************************************
staging : ok=6 changed=4 unreachable=0 failed=0

[Pipeline] End of Pipeline
Finished: SUCCESS


Checking staging server


As you can see the /home/vagrant/mini we created at the beginning has been deleted at the end of the deployment process.


vagrant@staging:~$ ls -l
total 0

Let's check docker components.


vagrant@staging:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
inanzzz/mini_php latest 3ec65935d03d 6 minutes ago 14.8MB
alpine 3.9 cdf98d1859c1 4 weeks ago 5.53MB

vagrant@staging:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
99ba5891cee3 inanzzz/mini_php:latest "/bin/sh -c 'tail -f…" 7 minutes ago Up 7 minutes stag_mini_php_1

vagrant@staging:~$ docker exec -it stag_mini_php_1 php src/test.php
success
DB_USER=root
DB_PASS=password
Array
(
[HOSTNAME] => e95350a993d9
[SHLVL] => 1
[HOME] => /root
[TERM] => xterm
[PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[DB_PASS] => 123123
[PWD] => /app
[DB_USER] => inanzzz
[PHP_SELF] => src/test.php
[SCRIPT_NAME] => src/test.php
[SCRIPT_FILENAME] => src/test.php
[PATH_TRANSLATED] => src/test.php
[DOCUMENT_ROOT] =>
[REQUEST_TIME_FLOAT] => 1557934232.1256
[REQUEST_TIME] => 1557934232
[argv] => Array
(
[0] => src/test.php
)

[argc] => 1
)
root
password