How I develop in PHP with CoreOS and Docker
Notice!!
Since Docker and all tools provided are moving fast, you shouldn’t be reading this post. We’ve written a new post about PHP development and Docker at Yappa. You can read it here: http://tech.yappa.be/docker-php-development
I’ve been using the Vagrant provisioned-with-Ansible-setup for a while now. But for the last month(s) I’ve been playing around with things like: Docker, boot2docker, CoreOS, etcd, .. I managed to setup a fast and easy way to develop my PHP applications. Symfony2 is my preferred weapon of choice, so I’ll explain how I’m developing a Symfony2 app.
The software stack
Most of these applications are installed with an automated Mac installation/configuration manager I’ve created a while ago. MacPlan, built on top of Ansible.
You can also download and install these applications manually or use Homebrew and Homebrew Cask to install them.
brew install vagrant
brew cask install virtualbox
brew install docker
brew install docker-compose
Vagrant and CoreOS
I’m working on Mac, so I need a virtual machine to run Docker server. I used to run boot2docker as a Docker host, but I recently switched to CoreOs because it feels faster. I didn’t benchmarked it yet.
Configure coreos-vagrant
git clone git@github.com:coreos/coreos-vagrant.git
cd coreos-vagrant
cp config.rb.sample config.rb
cp user-data.sample user-data
Edit config.rb
, uncomment and edit the following lines.
$expose_docker_tcp=2375 # expose docker to other hosts then localhost
..
$share_home=true # mount your Mac home dir in the vagrant box
..
$vm_memory = 2048
$vm_cpus = 4
..
$forwarded_ports = { 2375 => 2375 } # use localhost as docker ip on your Mac
Once these things are done, you can start the CoreOS Vagrant box.
vagrant up
Vagrant will ask for sudo permission to change the /etc/hosts file on your Mac.
...
==> core-01: adding to (/etc/hosts) : 172.17.8.101 core-01 # VAGRANT: fc39e1aea8341bd5d302603e416aa3de (core-01) / 53f5faee-f39b-486e-a004-a6db956eaab1
Password:
==> core-01: Setting hostname...
==> core-01: Configuring and enabling network interfaces...
==> core-01: Exporting NFS shared folders...
==> core-01: Preparing to edit /etc/exports. Administrator privileges will be required...
==> core-01: Mounting NFS shared folders...
==> core-01: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> core-01: to force provisioning. Provisioners marked to run always will still run.
You can test the docker daemon (already) by running:
$ vagrant ssh -c "docker ps"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Connection to 127.0.0.1 closed.
Configure Docker client on Mac
The Docker client on your Mac don’t know where to look for Docker server (yet).
$ docker ps
FATA[0000] Get http:///var/run/docker.sock/v1.18/containers/json: dial unix /var/run/docker.sock: no such file or directory. Are you trying to connect to a TLS-enabled daemon without TLS?
The easiest way is to add to following line at the bottom of ~/.bash_profile
.
Or update your dotfiles in MacPlan or personal dotfiles management repository.
echo "export DOCKER_HOST=tcp://localhost:2375" >> ~/.bash_profile
Open a new terminal or export the variable in your current terminal.
export DOCKER_HOST=tcp://localhost:2375
At this moment, Docker client will have access to Docker server on your virtual Machine with CoreOS.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
No containers running yet.
Docker compose
I’m always starting with (almost) the same docker-compose.yml
file.
This files describes the docker services I want to use.
In short the following services are used:
- A data container
- An Nginx webserver on port 80
- PHP-FPM listening in port 9000
- A MySQL server
- Mailcatcher
Data Container
I’m using this to get some volumes shared accros different containers (PHP, Nginx, ..) without file permission issues. Also the vendor folder will be symlinked to a volume on the docker containers, this speeds up the io.
Nginx
A standard webserver setup with a minimal amount of setup. I’m using a pre-built image, yappabe/docker-nginx.
PHP-FPM
A PHP-FPM instance waiting on a TCP socket, port 9000 to process the PHP files. Also a pre-built image, yappabe/docker-php. It’s possible to use an older versions of PHP, by using a tag on the docker image.
php:
image: yappabe/php:5.4
The following tags are possible:
- 5.6
- 5.4
- 5.3
- default will be the latest built: 5.6
MySQL
A normal Mysql server, listening on port 3306. You can define the default credentials and pre-create a database. You can also leave these settings empty for random credentials and no database on creation.
Run the container
Starting form my default setup you can download the docker-compose.yml
file and run the containers.
Open a new terminal window.
composer create-project --no-install symfony/framework-standard-edition projectX
cd projectX
curl -O https://gist.githubusercontent.com/jverdeyen/850bc70015eff9f7ef35/raw/f7819f762ea7a3ddb9873f44557ed040728e8100/docker-compose.yml
docker-compose up
Docker will start pulling the images, but this will only be done once. After that, you shoud see an output log of the started containers.
Creating projectx_app_1...
Creating projectx_mailcatcher_1...
Creating projectx_mysql_1...
Pulling image tutum/mysql:latest...
..
mysql_1 | 150706 7:51:03 [Note] Event Scheduler: Loaded 0 events
mysql_1 | 150706 7:51:03 [Note] /usr/sbin/mysqld: ready for connections.
mysql_1 | Version: '5.5.43-0ubuntu0.14.04.1' socket: '/var/run/mysqld/mysqld.sock' port: 3306 (Ubuntu)
Open a new terminal window to check of all containers are running.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4d228350470f yappabe/nginx:latest "/run.sh" 3 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp, 443/tcp projectx_nginx_1
a9f1580cfd84 yappabe/php:latest "/usr/sbin/php5-fpm 3 minutes ago Up 2 minutes 9000/tcp projectx_php_1
337246de94d2 tutum/mysql:latest "/run.sh" 3 minutes ago Up 2 minutes 0.0.0.0:3306->3306/tcp projectx_mysql_1
7ac07efc9df1 yappabe/mailcatcher:latest "mailcatcher --smtp- 3 minutes ago Up 3 minutes 0.0.0.0:1025->1025/tcp, 0.0.0.0:1080->1080/tcp projectx_mailcatcher_1
91a1ff1f1d90 yappabe/data:latest "/bin/sh" 3 minutes ago Up 3 minutes projectx_app_1
Install vendors
Now we need to install our vendors.
docker exec -i -t projectx_php_1 bash -c 'cd /var/www/app/ && ln -sf /vendor /var/www/app/vendor && composer install'
Fill in the correct credentials for MySQL when asked. Fill in “mysql” as database host as localhost will not work. This is because of the Docker Compose magic, it links all the containers together and updates their hosts file accordingly.
Clearing the Symfony2 Cache is also easy.
docker exec -i -t projectx_php_1 bash -c '/var/www/app/app/console cache:clear'
Now you can visit your Symfony2 application at http://172.17.8.101
, and start developing.
Since app_dev.php
has an internal check on localhost, you can’t visit the page.
An easy fix is to remove the following lines from app_dev.php
.
// This check prevents access to debug front controllers that are deployed by accident to production servers.
// Feel free to remove this, extend it, or make something more sophisticated.
if (isset($_SERVER['HTTP_CLIENT_IP'])
|| isset($_SERVER['HTTP_X_FORWARDED_FOR'])
|| !(in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1')) || php_sapi_name() === 'cli-server')
) {
header('HTTP/1.0 403 Forbidden');
exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}
Extra: Improve performance
There are some quick tips to improve the speed of a Symfony2 application while developing.
- Use
/dev/shm
as your cache and logs location - Finetune the nfs mount command in the coreos-vagrant Vagrantfile
Next?
I’ve also written something about automagic discover docker containers with dnsdock and CoreOs.
Setting up a DNS service discovery with SkyDNS or DNSDock
Thanks for reading
Feel free to leave a comment if you have remarks or like this post