Deploy your PHP app with
Docker

Asmir Mustafic

(and a couple of other tools)

May 2017 - NomadPHP

Me

Asmir Mustafic

Software Engineer and Consultant

Germany (Berlin), Italy (Venice), Bosnia

Open source

  • Doctrine (contributor)
  • JMS Serializer  (maintainer)
  • HTML5-PHP (maintainer)
  • XSD2PHP (author)
  • Twital (author)
  • Many SOAP-related packages...

The problem

How to
"From my local application

to the world?"

The past

FTP / SFTP / ssh / rsync / bash

and so on...

Now

More machines, environments, tests, configurations....

Docker

Docker

Docker automates the deployment of applications inside software containers.

Docker containers can be deployed on any server that has docker support

Docker divided application dependencies from
operating system dependecies

Docker is a set of Tools

docker

Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications, whether on laptops, data center VMs, or the cloud.

docker-compose

docker-compose is a tool for defining and running multi-container Docker applications

docker-machine

Docker Machine is a tool that lets you install Docker Engine on virtual hosts, and manage the hosts with docker-machine commands

docker-share

Docker Share is a tool that allows to export to a single tar.gz file configurations regarind a machine created/imported via docker-machine

https://github.com/grinnery/machine-share

Intro

Create a local development environment
with Docker

# docker-compose.yaml
version: '2'
services:
    db:
        image: postgres:9.6
        ports: # used to debug
            - "5050:5432"
        volumes:
            - .docker_data/pg-data:/var/lib/postgresql/data/pgdata
    www:
        image: goetas/nginx:${TAG}
        build: ./docker/nginx
        ports: # used to debug and develop, http://localhost:8080/
            - "8080:80"
        depends_on:
            - php
        volumes_from:
            - data
    php:
        image: goetas/php:${TAG}
        build: ./docker/php-fpm
        volumes_from:
            - data

    data:
        image: goetas/data:${TAG} 
        build: .
        volumes:
            - .:/var/www
# docker/php/Dockerfile

FROM php:7-fpm

RUN curl https://getcomposer.org/composer.phar > /usr/local/bin/composer \
    && chmod a+x /usr/local/bin/composer

# customized ini directives for my app
COPY ini/app.ini /usr/local/etc/php/conf.d/app.ini

WORKDIR /var/www

php

# docker/nginx/Dockerfile

FROM nginx:1

COPY conf /etc/nginx

nginx

# Dockerfile

FROM busybox

COPY . /var/www
VOLUME /var/www

ENTRYPOINT busybox tail -f /dev/null

data

Docker volumes are a good alternative...

docker-compose up

Done!

build, test, commit, repeat

Production?

Staging, test and so on...

The big picture

Step 1. Push code

Create locally your new application

and...

Push your code to a VCS server

 

The big picture

Step 2. Trigger build

Configure you VCS system to trigger a build on some server

github/bitbucket/gitlab
have already integrations with almost every CI server as
travisCI, circleCI, Jenkins, bamboo...

 

Build responsibilities

  1. get dependencies

  2. run tests

  3. prepare images
    (and push images to the registry [step 3])

  • run next steps (optional)

    • trigger deploy

Build

The CI server runs the build

 

(running build means just running some bash commands)

Build 1/3

# infrastructure deps
docker-compose up -d --build

# application deps
docker exec php_container_name composer install -o

# docker exec node_container_name npm install
# docker exec other_container_name other command

Dependencies

Build 2/3

# load some test data/fixtures into database
magic command here

# run tests
phpunit

Tests

You have tests, right?

Build 3/3

# rebuild to include composer vendor folder (depends on your app)
docker-compose build data 

# login to docker registry
docker login -e $EMAIL -u $USERNAME -p $PASSWORD

# set the target version
export TAG="$BRANCH_NAME"

# push to docker registry
docker-compose bundle --push-images

Step 3. push to docker registry

The big picture

Step 4. Trigger Update

Decide if deploy or not

 

based mostly on
commit content / branch name / or something else

Where to deploy?

# create our instance, if not already done
docker-machine create --driver amazonec2 aws01

# syntax docker-machine
# docker-machine create [options] --driver [driver-options] machine-name

# export machine credentials to a file named aws01.tar.gz
machine-export aws01

This code can be executed on the CI server or manually
(depends on workflow)

The big picture

Step 5. Deploy

# get docker machine credentials
machine-import aws01.tar.gz

# tell docker client to use instance aws01 
docker-machine env aws01

# set the deploy target
export TAG="$BRANCH_NAME"

# download latest docker images
docker-compose -f docker-compose.live.yml pull

# start your fresh application
docker-compose -f docker-compose.live.yml up -d

This code is executed on the CI server

Done!

# docker-compose.live.yaml
version: '2'
services:
    db:
        image: postgres:9.6
        volumes:
            - /var/pg-data:/var/lib/postgresql/data/pgdata

    data:
        image: goetas/data:${TAG}

    php:
        image: goetas/php:${TAG}
        volumes_from:
            - data

    www:
        image: goetas/nginx:${TAG}
        ports:
            - "80:80"
        depends_on:
            - php
        volumes_from:
            - data

docker-compose.live.yml

Extras 1

Database migrations

# after deploy

# run database migrations ot other tasks post deploy
docker exec container_name command

Extras 2

.dockerignore

docker
tests
docker-compose*
circle.yml
Dockerfile
.docker_data
.git
**/.git
.gitignore
.idea
web/app_dev.php
**.css.map
var/cache/*
var/logs/*
var/sessions/*
var/uploads/*

Extras 3

machine-import

# https://github.com/grinnery/machine-share

machine-export <machine-name>
>> exported to <machine-name>.tar 

machine-import <machine-name>.tar
>> imported

Extras 4

no downtime deploy

# deploy.sh [tag]
# nginx proxy + docker-gen (https://github.com/jwilder/docker-gen)

# ${name} is the project name

if [ $(docker ps --filter=name="${name}Va" --format "{{.ID}}" | wc -l) = "0" ]; then
    project_up="${name}Va"
    project_down="${name}Vb"
else
    project_up="${name}Vb"
    project_down="${name}Va"
fi

export TAG=$1

docker-compose -p $project_up pull
docker-compose -p $project_up up -d

# do health-check on $project_up and invoke docker-gen

docker-compose -p $project_down down -v

Extras 5

Docker swarm

If your target machine is a swarm master, everything works as usual

except that you are distributing your application across a cluster of nodes! :) 

Thank you!

 

Enjoy Docker!

Deploy your app with Docker

By Asmir Mustafic

Deploy your app with Docker

  • 1,854