Blog: Setting Up A Local Development Environment With PHP & Docker
Some operating systems come with a preinstalled version of PHP. You can determine if you have PHP installed by opening up your terminal or command line interface and typing the following
This may return something like this.
➜ ~ php -v PHP 5.5.36 (cli) (built: May 29 2016 01:07:06) Copyright (c) 1997-2015 The PHP Group Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies
If PHP is installed, you might have noticed the version is not the latest version available. Instead of trying to update this PHP we will move forward with using Docker.
Docker will help us to set up a contained and platform independent development environment that will closely, if not exactly match our production environment.
Keeping this in mind we can install exactly the server operating system, PHP version, database type, & many other options.
Let’s jump right in and install docker locally. I will be using OS X for my install, but since docker works the same on Windows and Linux, it will not be challenging to work along with me. Head over to [Docker’s Website] and follow the prompts to download the correct installer for you operating system.
Open the downloaded file and follow the prompts to install Docker. You will most likely want to leave everything at their default values.
Once you have installed the Docker native application you should be able to open up a terminal and type
docker info which will output something that will look like this.
➜ ~ docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0
Your numbers may be different than this, but it is not an issue, we are just validating that we can access docker from our terminal.
If you have any issues getting it up and running here are the getting started links from Docker for two of the major operating systems https://docs.docker.com/docker-for-mac/ https://docs.docker.com/docker-for-windows/
Creating a Local Development Environment
Now that we have Docker installed and working, it is time to create our local development environment.
Docker has a lot of options to use and understand, which in time you will want to understand, but for now you can download or clone a repository that has been set up for you.
Clone or download the following project onto your computer at the location where you would like to keep all of your PHP development projects.
Once it is cloned, or downloaded and extracted, change directory into that folder.
Here is what the folder structure looks like:
Let’s walk through these folders to get a basic understanding of the function of each.
The CONF folder holds the configuration files for some of the server technologies. [Nginx] (the web server), PHP (the interpreter), & [Supervisord] (a process monitor) each have their own file here to configure to our needs.
The LOGS folder holds all the logs for each of our containers, such as PHP and nginx.
The SITES folder holds the nginx configuration for each of the websites we want to work on. In this development environment we are configuring each new site will have its own vhost file that matches the folder inside of www/.
The WWW folder holds all of our site files, each website will have its own folder under www. Each folder will have a matching vhost file in the sites folder. This is to our advantage as each site is contained and configured independently of the others.
DOCKERFILE The next couple of files are the two that interact directly with Docker to create and build our server. The Dockerfile is the base configuration of our server, and what we want to install on it. It also copies all of our local configuration files onto the server. There is a lot to learn about Dockerfiles, so when you have time and lots of coffee, visit the Dockerfile Documentation.
The DOCKER-COMPOSE.YML: file is our configuration and interface for using Docker Compose. When we are using docker compose, we are specifying how our application is structured and each item linked together. Docker Compose is a powerful tool that allows us to run some simple command in our terminal to build, start, and stop our server. Docker compose workflow comes down to three basic steps. These are clearly stated on the docker compose website.
- Define your app’s environment with a Dockerfile so it can be reproduced anywhere.
- Define the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.
- Lastly, run
docker-compose upand Compose will start and run your entire app.
Let’s take a quick look at the Dockerfile and the docker-compose.yml
This line is setting up our base container, which is a pre-built nginx container running Alpine. You can see the official images such as [nginx] at hub.docker.com.
# Remove default nginx configs. RUN rm -f /etc/nginx/conf.d/*
Any lines with a # in front is a comment. Here we are to RUN a command just like we would on a unix command line, removing all folders and files within the specified path.
# Install PHP 7 Repo RUN apt-get update && apt-get install -my wget RUN sh -c "echo 'deb http://packages.dotdeb.org jessie all' >> /etc/apt/sources.list" RUN sh -c "echo 'deb-src http://packages.dotdeb.org jessie all' >> /etc/apt/sources.list" RUN wget https://www.dotdeb.org/dotdeb.gpg -O - | apt-key add - RUN wget https://nginx.org/keys/nginx_signing.key -O - | apt-key add -
Here we are installing a program called wget to fetch some keys so that we can install PHP 7 from a custom url as well as updating a key for nginx.
#Install packages RUN apt-get update && apt-get install -my \ supervisor \ curl \ php7.0-curl \ php7.0-fpm \ php7.0-mysql \ php7.0-mcrypt \ php7.0-sqlite \ php7.0-xdebug \ php-apc
Here we are running a update then an install of several applications and packages using the unix package manager apt.
# Ensure that PHP7 FPM is run as root. RUN sed -i "s/user = www-data/user = root/" /etc/php/7.0/fpm/pool.d/www.conf RUN sed -i "s/group = www-data/group = root/" /etc/php/7.0/fpm/pool.d/www.conf # Prevent PHP Warning: 'xdebug' already loaded. XDebug loaded with the core RUN sed -i '/.*xdebug.so$/s/^/;/' /etc/php/7.0/mods-available/xdebug.ini
Next we are updating the user and group for PHP7 FPM in the existing configuration files on the server and avoiding some known warnings.
# Add configuration files COPY conf/nginx.conf /etc/nginx/ COPY conf/supervisord.conf /etc/supervisor/conf.d/ COPY conf/php.ini /etc/php/7.0/fpm/conf.d/40-custom.ini
These last lines are making a COPY the local configuration files for PHP, nginx, and supervisord.
All of this in the Dockerfile is setting up the base configuration for our server. Once this is build, we only need to revisit it if we change our three configuration files above, or need to install some new packages.
version: '2' services:
Or services in this case are PHP and MySQL
php: build: .
‘build .’ is building out our container, that was defined in our Dockerfile
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
then command runs whatever single command we want to trigger when the container is started
ports: - "80:80" - "443:443"
ports exposes whoever ports we want to make accessible from our host computer inside of the docker container
links: - mysql
links sets up, as you may have guessed it, a link between the php container and the mysql container so that one may see the other
volumes: - ./www:/var/www - ./sites:/etc/nginx/conf.d - ./logs:/var/log/supervisor
volumes sets up a connection between our local file system and our containers file system. The format is pretty simple
/local/location : /container/location.
mysql: image: mysql ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: password
for the mysql service we are using a pre defined image from docker, exposing the default mysql port, and setting a default root mysql password in the container’s environment variables.
Build & Run Containers
The next step for us is to build our container, by running
docker-compose build within the folder where the Dockerfile and docker-compose.yml files are located.
➜ docker-compose build mysql uses an image, skipping Building php … Successfully built fe03206fb1a1
Once successfully built, which could take a while, you will need to bring your server up. Run
Now head over to the browser and navigate to 0.0.0.0
You should see a PHP info page.
Within Compose there are quite a few commands that can be used from the command line. We will review the most common ones below, but you can find a full listing with a good bit of detail here [https://docs.docker.com/compose/reference/]
This is one of the most commonly used commands which 'builds, (re)creates, starts, and attaches to containers for a service.' If you run this command with the detatched flag
docker-compose up -d it will run the processes in the background.
This is a common flow for us once we have used the up command.
docker-compose stop Stops running containers without removing them. They can be started again with
docker-compose start, and as you might expect we can always reboot/restart when needed with
This command 'Stops containers and removes containers, networks, volumes, and images created by
docker-compose up.' We will not always use this command, mostly you will use the start/stop/restart flow from above.
Run is a very helpful command, it 'runs a one-time command against a service.' A very common use is to run bash on the remote service container. For our service 'php', which is the name we defined in the docker-compose.yml file, we would run the following.
docker-compose run php bash which is the run command followed by the container name followed by the command which in this case is bash or /bin/bash. The run command has quite a few option flags that you can use, make sure to read up on them at the link above.
If you want to run a command on an existing and running container you can use the exec command. As an example lets get the php version on our container.
docker-compose exec php php -v
Breaking down this command is very similar to the run command, we are using the php service container, followed by the php command with the argument -v to check the version.
Now that we have a good understanding of the docker-compose suite of commands, lets talk about workflow.
Adding a New Site
We will for sure need to add a new site at some point, so let's add an example site called 'hello'.
We will want to contain the site without killing our default site, which is the php info page. This will allow us to have a separate SCM repository for each of our sites.
In the www folder is where we will be keeping our project files. Let's start with adding a new folder named hello in the www folder and add an index.php file in that hello folder.
For the php file, index.php, let's just add just to make sure that it is working. At this moment it will not work, until we tell nginx that we have a new site.
Our sites folder is where we will define each site on the container. The best option is to create a duplicate of default.vhost and rename it hello.vhost. The name of this file should match the name of your site.
There are quite a few options we can set in this file, and it would be a good idea to review all of the options that you can place into a .vhost file. You can find all of that info on the nginx site. https://www.nginx.com/resources/admin-guide/nginx-web-server/
For our example we will just be focusing on a few of the options defined here. The first being server_name:
The nginx documentation explains the usage of server_name like this. 'If there are several servers that match the IP address and port of the request, NGINX Plus tests the request’s Host header field against the server_namedirectives in the server blocks. '
So in our case we will set server_name to hello.
The next two options are 'root' and 'index' where root tells nginx where to look for the site files and the index sets up the name of initial file to serve.
Here are our settings:
server\_name hello.dev; root /var/www/hello; index index.php;
editing the hosts file
The final steps in this setup is to edit our computers hosts file. The hosts file will allow us to set up static urls and map them to an ip address. We will want to create a site url for hello.dev and set the ip address to 127.0.0.1, or our local host.
Each operating system is different in how it handles hosts files and how to modify them. I will be using OS X as the example here. If you need a different operating system rackspace.com has a nice guide. [https://support.rackspace.com/how-to/modify-your-hosts-file/]
In OS X open a terminal and run the following command
sudo nano /etc/hosts.
You will need to supply you password as you are modifying a protected system file. You can use any editor you like, but you will need to elevate your permissions before hand. If you are using atom.io for instance, you can run the same command replacing 'nano' with 'atom'.
Your file should look a bit like this.
## # Host Database # # localhost is used to configure the loopback interface # when the system is booting. Do not change this entry. ## 127.0.0.1 localhost` 255.255.255.255 broadcasthost ::1 localhost`
Underneath the localhost entry add the following
Then save the hosts file by pressing Control+x and answering y, or yes to the save location, and press enter/return.
You may need to run
dscacheutil -flushcache to refresh the changes.
The final step will be to run
docker-compose restart to make the changes to nginx configs update.
Now we can browse to http://hello.dev and should see 'Hello World'
Thank you for taking the time to work through this process. Now that we have the environment set up, it will be easy to set up new sites in the future and develop with near production products, no matter your operating system!
Please leave any questions you may have in the comments or on our forums at ....