Docker for PHP
Developers
Chris Tankersley
@dragonmantank
php[tek] 2018
1
php[tek] 2018
Setup!
WIFI: “Sheraton Meeting”, code is `phptek2018`
Example App:
https://github.com/learningcontainers/dockerfordevs
Lunch: Around 12:30pm
Breaks: Every so often
2
php[tek] 2018
What Is Docker?
“Docker is an open platform for developers and sysadmins to build,
ship, and run distributed applications. Consisting of Docker Engine, a
portable, lightweight runtime and packaging tool, and Docker Hub, a
cloud service for sharing applications and automating workflows,
Docker enables apps to be quickly assembled from components and
eliminates the friction between development, QA, and production
environments.”
3
https://www.docker.com/whatisdocker/
php[tek] 2018
php[tek] 2018
What is a Container?
5
php[tek] 2018
Normal Bare-Metal Server
6
CPU RAM HD Network
Operating System
nginx PHP DB
php[tek] 2018
Normal Bare-Metal Server
7
CPU RAM HD Network
Operating System
nginx PHP DB
php[tek] 2018
Virtual Machines
8
CPU RAM HD Network
Operating System
nginx PHP DB
Operating System
nginx PHP DB
Operating System
Hypervisor
php[tek] 2018
Containers
9
CPU RAM HD Network
Operating System
nginxnginx PHP DB PHP DB
php[tek] 2018
Containers vs VMs
php[tek] 2018
Containers Are Not New
‱ LXC (Linux Containers)
‱ OpenVZ
‱ Systemd-nspawn
‱ BSD Jails
‱ Solaris Zones
‱ chroot
11
php[tek] 2018
Containers are just walled processes
12
Ubuntu Kernel
/
+ bin/
+ etc/
+ dev/
+ home/
+ usr/
+ var/
+ lib/
+ 

nginx
bash
/
+ bin/
+ etc/
+ dev/
+ home/
+ usr/
+ var/
+ lib/
+ 

php
php[tek] 2018
What is Docker?
13
php[tek] 2018
Docker is an Ecosystem
14
Docker Engine
php[tek] 2018
Docker is an Ecosystem
15
Docker ComposeDocker Machine Docker Swarm
php[tek] 2018
How does it work?
16
Uses a variety of existing
Container technologies
Server Containers
Hyper-V Containers xhyve Virtualization
php[tek] 2018
Sorry OSX < 10.10 and Windows < 10 Users
Docker Toolbox
17
php[tek] 2018
Let’s use Docker
18
php[tek] 2018
Running a container
‱ `docker run` will run a container
‱ This will not restart an existing container, just create a new one
‱ docker run [options] IMAGE [command] [arguments]
‱ [options ]modify the docker process for this container
‱ IMAGE is the image to use
‱ [command] is the command to run inside the container
‱ [arguments] are arguments for the command
19
php[tek] 2018
Running a simple shell
20
php[tek] 2018
Running a simple shell
21
php[tek] 2018
Running a simple shell
22
php[tek] 2018
What’s Going On?
23
Ubuntu Kernel
/
+ bin/
+ etc/
+ dev/
+ home/
+ usr/
+ var/
+ lib/
+ 

nginx
bash
/
+ bin/
+ etc/
+ dev/
+ home/
+ usr/
+ var/
+ lib/
+ 

php
php[tek] 2018
Running Two Webservers
24
php[tek] 2018
Running Two Webservers
25
php[tek] 2018
Running Two Webservers
26
php[tek] 2018
Running Two Webservers
27
php[tek] 2018
Running Two Webservers
28
php[tek] 2018
Running Two Webservers
29
php[tek] 2018
Running Two Webservers
30
php[tek] 2018
Running Two Webservers
31
php[tek] 2018
Some Notes
‱ All three containers are 100% self contained
‱ Docker containers share common ancestors, but keep their own files
‱ `docker run` parameters:
‱ --rm – Destroy a container once it exits
‱ -d – Run in the background (daemon mode)
‱ -i – Run in interactive mode
‱ --name – Give the container a name
‱ -p [local port]:[container port] – Forward the local port to the container port
32
php[tek] 2018
Volumes
33
php[tek] 2018
Modifying a running container
‱ `docker exec` can run a command inside of an existing container
‱ Use Volumes to share data
34
php[tek] 2018
Persistent Data with Volumes
‱ You can designate a volume with –v
‱ Create a named volume with `volume create`
‱ Volumes can be shared amongst containers
‱ Volumes can mount data from the host system
35
php[tek] 2018
Mounting from the host machine
36
php[tek] 2018
Mounting from the host machine
37
php[tek] 2018
Mounting from the host machine
38
php[tek] 2018
Mounting from the host machine
39
php[tek] 2018
Mounting from the host machine
40
php[tek] 2018
Mounting from the host isn’t perfect
‱ The container now has a window into your host machine
‱ Permissions can get screwy if you are modifying in the container
‱ Most things it creates will be root by default, and you probably aren’t root on
the host machine
‱ Host-mounted volumes are not portable at all
‱ OSX and Hyper-V VMs have limited pathings to mount
‱ OSX has poor I/O performance
41
php[tek] 2018
Named Data Volumes
‱ Creates a space that becomes persistent
‱ Can be mounted anywhere inside your images
‱ Have our app containers use the data volume to store data
‱ Use ‘editor containers’ to go in and modify data when needed
42
php[tek] 2018
vim Tutorial
‱ vim is a Modal text editor
‱ ESC will drop you back to default mode
‱ :new /opt/webconfig/default to create a new file
‱ In default mode, i will get us into interactive (edit) mode
‱ :w to save a file
‱ :q will quit
43
php[tek] 2018
Mounting Data Volumes
44
php[tek] 2018
Mounting Data Volumes
45
php[tek] 2018
Mounting Data Volumes
46
php[tek] 2018
Mounting Data Volumes
47
php[tek] 2018
Mounting Data Volumes
48
php[tek] 2018
Mounting Data Volumes
49
php[tek] 2018
Why go through the hassle?
‱ Data volumes are portable, depending on the driver
‱ Data volumes are safer
‱ Separates the app containers from data
‱ Production can use a data volume, dev can use a host volume
‱ Our app containers stay small
‱ Works directly with other tools
50
php[tek] 2018
Networking
51
php[tek] 2018
Networking
‱ Docker can create multiple network “pools”
‱ Each container gets an IP address
‱ Containers can be attached to multiple networks
‱ Docker network allow service discovery inside networks
52
php[tek] 2018
Legacy - Docker Links
‱ Legacy Links work with `--link`
‱ Only works on the legacy “bridge” network
‱ Doesn’t support service discovery
‱ Not worth it to use anymore
53
php[tek] 2018
Docker Networks
‱ Discreet IP pool for containers
‱ Containers can be added and removed to the network at whim
‱ Service discovery though ‘--network-alias’
‱ Can be set up to work across hosts
54
php[tek] 2018
Create a network
55
php[tek] 2018
Attach to a network
56
php[tek] 2018
Ping the web container
57
php[tek] 2018
Add another web and kill web1
58
php[tek] 2018
BREAK TIME! WOO!
59
php[tek] 2018
Other Helpful Commands
60
php[tek] 2018
Inspect a container
docker inspect [options] CONTAINER_NAME
‱ Returns a JSON string with data about the container
‱ Can also query
‱ docker inspect -f “{{ .NetworkSettings.IPAddress }}” web_server
‱ Really handy for scripting out things like reverse proxies
61
php[tek] 2018
Work with images
‱ docker pull IMAGE – Pulls down an image before using
‱ docker images – Lists all the images that are downloaded
‱ docker rmi IMAGE – Deletes an image if it’s not being used
62
php[tek] 2018
Containerizing An Application
63
php[tek] 2018
Our Goals
‱ Not change our workflow (much)
‱ Run PHP 7, Unit Tests, and webserver
‱ Deploy “easily”
64
php[tek] 2018
Just try and run it
docker run -d --name d4dapp 
-v C:dragoProjectsdockerfordevs-app:/var/www/ 
-p 8080:80
php:apache
65
php[tek] 2018 66
php[tek] 2018
Checking Logs
‱ Containers log to stdout/stderr
‱ Docker aggregates the logs
‱ Can be viewed with docker logs
67
php[tek] 2018
Oops
68
php[tek] 2018
Custom Images
‱ PHP images are pretty bare
‱ Lots of times need to install extensions
69
php[tek] 2018
Dockerfile
‱ Dockerfile is the configuration steps for an image
‱ Can be created from scratch, or based on another image
‱ Allows you to add files, create default volumes, ports, etc
‱ Can be used privately or pushed to Docker Hub
70
php[tek] 2018
docker/Dockerfile
FROM php:apache
RUN a2enmod rewrite
71
php[tek] 2018
Build it
docker build -t tag_name ./
‱ This runs through the Dockerfile and generates the image
‱ We can now use the tag name to run the image
72
php[tek] 2018
Build it
docker build -t d4dapp docker/
73
php[tek] 2018 74
php[tek] 2018
Use the new image
docker run -d --name d4dapp 
-v C:dragoProjectsdockerfordevs-app:/var/www/ 
-p 8080:80
d4dapp
75
php[tek] 2018
Use the new image
76
php[tek] 2018
Slightly better
77
php[tek] 2018
Install Dependencies
78
php[tek] 2018
Running Composer
docker run --rm 
-v c:/Users/drago/.composer:/root/.composer 
-v c:/Users/drago/Projects/workshop:/app 
-v c:/Users/drago/.ssh:/root/.ssh 
composer/composer 
install
79
php[tek] 2018
Better!
80
php[tek] 2018
Look at queues!
81
php[tek] 2018
docker/Dockerfile
FROM php:apache
RUN a2enmod rewrite
&& docker-php-ext-install pdo_mysql
82
php[tek] 2018
Rebuild the image
docker build -t d4dapp docker/
83
php[tek] 2018
Rebuild the container
$ docker rm -f d4dapp
$ docker run -d --name d4dapp 
-v C:dragoProjectsdockerfordevs-app:/var/www/ 
-p 8080:80
d4dapp
84
php[tek] 2018
Progress!
85
php[tek] 2018
Docker Compose
86
php[tek] 2018
What is Docker Compose?
‱ Multi-container orchestration
‱ A single config file holds all of your container info
‱ Works with Docker Swarm and a few other tools, like Rancher
87
php[tek] 2018
Sample docker-compose.yml
version: '2'
volumes:
mysqldata:
driver: local
services:
d4dapp:
build: ./docker/
volumes:
- ./:/var/www/
ports:
- 8080:80
mysqlserver:
image: mysql
environment:
MYSQL_DATABASE: dockerfordevs
MYSQL_ROOT_PASSWORD: 's3curep@assword'
volumes:
- mysqldata:/var/lib/mysql
88
php[tek] 2018
No longer use docker run
$ docker rm –f d4dapp
$ docker-compose up -d
89
php[tek] 2018
Now we have 2 containers
90
php[tek] 2018
Config for DB now points to the service
name
91
<?php
return [
'debug' => true,
'config_cache_enabled' => false,
'db' => [
'driver' => 'Pdo_Mysql',
'hostname' => 'mysqlserver',
'port' => '3306',
'database' => 'dockerfordevs',
'user' => 'root',
'password' => 's3curep@assword',
],
];
php[tek] 2018
Yay!
92
php[tek] 2018
Install our DB Migration Software
docker run --rm 
-v c:/Users/drago/.composer:/root/.composer 
-v c:/Users/drago/Projects/workshop:/app 
-v c:/Users/drago/.ssh:/root/.ssh 
composer/composer 
require robmorgan/phinx
93
php[tek] 2018
Set up phinx
docker run --rm 
-v C:UsersdragoProjectsdockerfordevs-app:/app 
-w /app 
php:cli php vendor/bin/phinx init
94
php[tek] 2018
Run the migration
docker run --rm 
-v C:UsersdragoProjectsdockerfordevs-app:/app 
-w /app 
--network dockerfordevsapp_default 
php:cli php vendor/bin/phinx migrate
95
php[tek] 2018
Oops
96
php[tek] 2018
Let’s use the existing container
docker-compose run --rm 
-v C:UsersdragoProjectsdockerfordevs-app:/app 
-w /app 
d4dapp php vendor/bin/phinx migrate
97
php[tek] 2018
Good

98
php[tek] 2018
It Lives!
99
php[tek] 2018
Unit Testing
docker run --rm 
-v C:UsersdragoProjectsdockerfordevs-app:/app 
-w /app 
d4dapp php vendor/bin/phpunit -c .
100
php[tek] 2018
Running the tests
php[tek] 2018
Build a service
service:
testrunner:
build: ./docker/
volumes:
- ./:/app
working_dir: /app
command: vendor/bin/phpunit -c .
102
php[tek] 2018
Run the tests with the service
docker-compose run --rm testrunner
103
php[tek] 2018
Running the tests
Docker Machine
ZendCon, October 2016 105
What is Docker Machine?
‱ A provisioning tool that is used to set up a box with Docker
‱ Used in Docker Toolbox to create the VM
‱ Supports:
‱ EC2
‱ Azure
‱ Digital Ocean
‱ Hyper-V
‱ OpenStack
‱ Virtualbox
‱ VMWare
php[tek] 2017 106
Why use it?
‱ Makes it very easy to spin up new boxes
‱ Docker Machine handles all of the dirty stuff for you
‱ Docker Toolbox users are already using it
‱ Integrates with Docker Swarm
‱ It is not necessarily portable
php[tek] 2017 107
Let’s make a machine!
php[tek] 2017 108
Let’s Connect!
php[tek] 2017 109
php[tek] 2018
BREAK TIME AGAIN! WOO!
110
Production Considerations
111
12 Factor Applications
php[tek] 2017 112
1. Codebase
One codebase tracked in revision control, many deploys
php[tek] 2017 113
Repo Tips
‱ Keep everything in your repository
‱ Tag releases
‱ Never move tags
php[tek] 2017 114
2. Dependencies
Explicitly declare and isolate dependencies
php[tek] 2017 115
Dependencies
‱ Commit both composer.json and composer.lock files
‱ Commit Dockerfiles to the same repo as the codebase
php[tek] 2017 116
3. Config
Store config in the environment
php[tek] 2017 117
Configuration
‱ Anything that is environment specific should move to environment
vars
‱ Makes it much easier to build and deploy code
‱ Code cares less what external services it is talking to
php[tek] 2017 118
Use Environment Vars
‱ Can specify them one-by-one
– docker run ­e VAR_NAME=value
‱ Can specify a file
– docker run ­­env­file=filename
‱ Can specify in docker-compose.yml
php[tek] 2017 119
4. Backing Services
Treat backing services as attached resources
php[tek] 2017 120
Everything is “external”
‱ Never talk to local sockets
‱ Don’t make a determination between “locally” hosted and third party
‱ Easier to switch environments
‱ Easier to scale up
php[tek] 2017 121
5. Build, release, run
Strictly separate build and run stages
php[tek] 2017 122
The Workflow
‱ Build step installs dependencies, compiles files, and generates a Build
Artifact that can be deployed
– Does not contain any deployment configuration
‱ Release step pushes a Build Artifact into an environment
– Runs DB migrations, anything needed to happen before running
‱ Run step runs the app fully in the environment
php[tek] 2017 123
Tips
‱ Build Artifact can be an image
‱ Builds should be completely reproducible
‱ Release always take a build artifact, never directly from the repo
‱ Tag all your builds
‱ Track all your releases
php[tek] 2017 124
Build Step - Start Small
‱ Build your application
‱ Run composer
‱ Run npm/bower
‱ Build JS/CSS
‱ Use the compiled output to build an image with docker build
‱ Push full image to private registry
php[tek] 2017 125
docker build
‱ Additional options to look at
‱ -f, --file – Specify a different filename for the Dockerfile
‱ --no-cache – Don’t use a cached layer
‱ --pull – Always pull a new version of the image
php[tek] 2017 126
Sample usage
docker build 
--no-cache 
–f docker/php/phpserver.dockerfile 
–t prod_php /opt/builds/20161010
php[tek] 2017 127
phpserver.dockerfile
FROM php:fpm
RUN docker-php-ext-install pdo pdo_mysql
COPY ./ /var/www
php[tek] 2017 128
6. Processes
Execute the app as one or more stateless processes
php[tek] 2017 129
Built Into Docker
‱ One Process per container
‱ Allows tools to scale just what needs to be scaled
‱ Allows images to be swapped out as needed
php[tek] 2017 130
7. Port Binding
Export services via port binding
php[tek] 2017 131
Built Into Docker (Again)
‱ Each container gets its own IP and exposes its own ports
‱ Processes should already be talking over a network
‱ Can work with service locators that are port-based
php[tek] 2017 132
8. Concurrency
Scale out via the process model
php[tek] 2017 133
How well does your app handle scaling?
php[tek] 2017 134
Built Into Docker (Again) (Again)
‱ One Process per container
‱ Scale up just the container that is needed
‱ App should not care how many instances of each service are running
php[tek] 2017 135
9. Disposability
Maximize robustness with fast startup and graceful shutdown
php[tek] 2017 136
Signals
‱ Docker starts containers fairly quickly
‱ Applications should gracefully shut down, not just die
‱ Docker sends a SIGTERM when shutting down a container
‱ Your CLI apps may need to handle SIGTERM properly
– Cal Evans, “Signalling PHP”
php[tek] 2017 137
10. Dev/prod Parity
Keep development, staging, and production as similar as possible
php[tek] 2017 138
11. Logs
Treat logs as event streams
php[tek] 2017 139
Logging in Docker
‱ Various logging options built in
– JSON file (default)
– Fluentd
– Syslog
– Journald
– Gelf
– Splunk
– Aws
– Etwlogs
– Gcplogs php[tek] 2017 140
Push logs remotely
‱ When possible, push Docker logs to a remote service
– Container logs only exist while the container exists
‱ Allows logs to be viewed in a single place
‱ No need to get into actual servers
‱ Can host yourself, or pay for a SaaS
‱ ELK stack is very popular
– Docker uses fluentd instead
php[tek] 2017 141
Setting up fluentd
services:
  d4dapp:
    build: ./docker/d4dapp
    depends_on:
      ­ fluentd
    volumes:
      ­ ./:/var/www/
    ports:
      ­ 8080:80
    logging:
      driver: "fluentd"
      options:
        fluentd­address: 127.0.0.1:24224
        tag: apache.access
php[tek] 2017 142
Setting up fluentd
services:
  fluentd:
    build: docker/fluentd
    depends_on:
      ­ elasticsearch
    volumes:
      ­ ./docker/fluentd/fluent.conf:/fluentd/etc/fluent.conf
    ports:
      ­ 24224:24224
      ­ 24224:24224/udp
php[tek] 2017 143
Setting up fluentd
FROM fluent/fluentd
RUN ["gem", "install", "fluent­plugin­elasticsearch", "­­no­rdoc", "­­no­ri", "­­version", 
"1.9.2"]
php[tek] 2017 144
Setting up fluentd
[See Config File in repo]
php[tek] 2017 145
Setting up ElasticSearch and Kibana
services:
  elasticsearch:
    image: elasticsearch
    expose:
      ­ 9200
    ports:
      ­ 9200:9200
  kibana:
    image: kibana
    depends_on:
      ­ elasticsearch
    ports:
      ­ 5601:5601
php[tek] 2017 146
Viewing Logs
php[tek] 2017 147
Logging notes
‱ docker logs does not work with external logging, only JSON
‱ This example can be cleaned up a bit
‱ Kibana syntax can be a bit odd to work with
php[tek] 2017 148
12. Admin Processes
Run admin/management tasks as one-off processes
php[tek] 2017 149
php[tek] 2018
BREAK TIME AGAIN! WOO!
150
Deployment using Docker Compose
php[tek] 2017 151
Very Good for Small Deployments
‱ Can be used to augment your dev environment
‱ Works well with Docker Machine
php[tek] 2017 152
Create a machine
docker-machine create --driver digitalocean 
--digital-ocean-access-token [token] 
phpworld2017
php[tek] 2017 153
SunshinePHP 2017 154
Switch to the remote node
‱ Run docker-machine env phpworld2017
& "C:Program
FilesDockerDockerResourcesbindocker-
machine.exe" env phpworld2017 | Invoke-Expression
eval $(docker-machine env phpworld2017)
php[tek] 2017 155
Set up docker-compose
‱ Docker Compose allows multiple config files with -f
‱ Have a base docker-compose.yml for Production
‱ Add a secondary one for Development
php[tek] 2017 156
version: '2'
volumes:
mysqldata:
driver: local
services:
nginx:
build:
context: ./
dockerfile: ./nginx.dockerfile
ports:
- 80:80
- 443:443
phpserver:
build:
context: ./
dockerfile: ./phpserver.dockerfile
working_dir: /var/www/public
mysqlserver:
image: mysql
environment:
[redacted]
volumes:
- mysqldata:/var/lib/mysql
php[tek] 2017 157
docker-compose.yml
version: '2'
volumes:
mysqldata:
driver: local
services:
nginx:
image: nginx
volumes:
- ./output_dev:/var/www/public:ro
- ./app/nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./ssl:/etc/nginx/ssl/
phpserver:
build:
context: ./
dockerfile: ./phpserver.dockerfile
working_dir: /var/www/public
volumes:
- ./app:/var/www/
- ./vendor:/var/www/vendor
mysqlserver:
image: mysql
environment:
[redacted]
volumes:
- mysqldata:/var/lib/mysql
php[tek] 2017 158
docker-compose.dev.yml
When doing development
docker-compose 
–f docker-compose.yml 
–f docker-compose.dev.yml 
up -d
php[tek] 2017 159
When doing a deployment
docker-compose up -d
php[tek] 2017 160
Other Alternative – Variable Substitution
‱ Docker Compose allows variable substitution inside the file
‱ Wrap variables in ${}
‱ image: ${DEPLOY_VERSION}_php
php[tek] 2017 161
When doing a deployment
DEPLOY_VERSION=20180525 docker-compose up -d
php[tek] 2017 162
Docker Swarm
php[tek] 2017 163
What is Swarm?
‱ Docker-supplied clustering
‱ Define a series of services that can be deployed
php[tek] 2017 164
Setup
php[tek] 2017 165
Manager
Node 2Node 1
Register a node as a Manager
docker swarm init ----advertise--addr 172.16.0.245
php[tek] 2017 166
Add nodes
docker swarm join --token [token] 172.16.0.245:2377
php[tek] 2017 167
docker-compose.yml
version: '3'
services:
phpserver:
image: php:apache
ports:
- 80:80
php[tek] 2017 168
Deploy the stack
$ docker stack deploy -c docker-compose.yml myapp
Creating network myapp_default
Creating service myapp_phpserver
php[tek] 2017 169
docker-compose.yml
version: '3'
services:
phpserver:
image: php:apache
ports:
- 80:80
php[tek] 2017 170
Deploy the stack
$ docker stack deploy -c docker-compose.yml myapp
Creating network myapp_default
Creating service myapp_phpserver
php[tek] 2017 171
docker-compose.yml
version: '3'
services:
phpserver:
image: php:nginx
ports:
- 80:80
php[tek] 2017 172
docker-compose.yml
version: '3'
services:
phpserver:
image: php:apache
ports:
- 80:80
php[tek] 2017 173
docker stack ps myapp
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
ERROR PORTS
72mud5othsjf myapp_phpserver.1 nginx:latest node2 Running Running 8 minutes ago
wsf3m32u9vcr _ myapp_phpserver.1 php:apache manager1 Shutdown Shutdown 11 minutes ago
xf3wyh289bec myapp_phpserver.2 nginx:latest node1 Running Preparing 20 seconds ago
fehf1vdx4m0r myapp_phpserver.3 nginx:latest manager1 Running Preparing 20 seconds ago
pwnq65e6w7ew myapp_phpserver.4 nginx:latest manager1 Running Preparing 20 seconds ago
roxtanjughq8 myapp_phpserver.5 nginx:latest node2 Running Running 20 seconds ago
php[tek] 2017 174
php[tek] 2018
Thank You!
‱ Software Engineer for InQuest
‱ Author of “Docker for Developers”
‱ https://leanpub.com/dockerfordevs
‱ Co-Host of “Jerks Talk Games”
‱ http://jerkstalkgames.com
‱ http://ctankersley.com
‱ chris@ctankersley.com
‱ @dragonmantank
175

Docker for Developers

Editor's Notes