Containerize your PHP application (Docker 101)
Coding (Php 7.x)
How to implement docker into your work as a PHP developer
The future of installations
Have you ever seen that meme about the kid that found an old floppy disk and thought it was a 3D duplicate of the save button?
This made me think that in the last few decades, we have seen a lot of changes in the world of tech.
You can now easily connect to the wi-fi of your neighborhood's cafeterias or watch the latest episode of your chosen Netflix series while being on a train dozens of meters underground.
It wasn’t this easy years ago, and there will be technologies that will make life, even more, easier in a few years.
One of these technologies is to containerize applications and use them only when needed.
Right now if you want to play a video game you need to install it on a hard disk and then connect a flash memory to it to play.
In the future, you are going to download an instance of the videogame from the internet, mount an image of it to play, then delete it from your hard disk without leaving any trace when you finish.
That is the power of using containers to work and deploy applications.
What you are reading is a 3-part series about the containerization of PHP applications:
A quick trick for you!
keep pressing CMD (or WIN for windows) and left-click on the links below to have all the parts of this series available on your browser.
- Containerize your PHP application (Docker 101)
- Containerize your Laravel application (Docker 102)
- Container Orchestration (Docker 103)
What is docker?
Before explaining what docker is, we need to step back for a second and define what an operating system is.
An Operating System consists of 2 different parts,
the deeper one, that is responsible for making the software interact with the hardware is called the OS Kernel the second part,
which is the one we are more familiar with is the part related to the software.
The software can be the user interfaces, the compilers, the drivers, or everything that makes the machine do something
Until a few years ago the most popular way to use alternative software in your machine was to use virtual machines,
They work wonderfully but they contain entire software suites that make them very heavy and require a lot of power.
Docker is a much slimmer alternative, it creates an upper level, just above the Kernel, making it possible to run containers with their own software.
Although Docker works on any computer, it shares the same kernel with Linux distros, making Ubuntu, Fedora, and co. its ideal habitat.
Docker also runs on Microsoft and Mac machines but it does so by simulating a Linux kernel via the use of a VM.
I won’t get to the technicality of how docker works because you can find thousand of tutorials about what is it and how it works,
Here is one of the best explanations I have found so far
To make it simple Docker allows you as a web developer or web-ops to easily create and use a transportable environment in which you can deploy your PHP applications.
If you just started and want to learn more read my introduction to PHP for beginners.
As you will see below you can define the features you want your environment to have, then export and import them wherever and whenever you prefer.
In this post, I will try to cut to the chase and show you how you can start code in PHP without needing a web server or installing a development environment.
You will learn what are the fundamental parts of Docker and a step-by-step guide on how to create your first PHP application.
One of the parts of the Docker environment that you must know is Docker Hub,
Docker Hub kind of works like GitHub but it keeps files that are used to reproduce full environments.
You can browse among more than 100,000 images (more on that later).
Many organizations have their product containerized and available to be shared within a public repository.
Also, many organizations will use PHP frameworks rather than build everything from scratch.
Using Docker does not remove the possibility to use any framework of your choice.
Once you identify an image that you need and installed Docker on your local machine, you can just use the terminal to start to work with it.
I have already mentioned images a few times so let’s start talking about them.
What are those images?
Images are like packages or templates that you can use to create another or multiple containers.
An image contains all the information regarding the environment you want to use.
For instance, by pulling and then sharing with your colleagues the 7.41 Apache version of the PHP image you are using for your project, all the web developers on your team can have the same version.
An even better example is when we compare our local or staging environments to our production ones.
If you know what version of PHP, MySql, etc we are using on the server we can just dockerize it and then match them.
An image consists of several commands, each command represents a read-only layer that creates a container.
In case you cannot find the set of services you are looking for, you can create your image and make it available to the public by pushing it to Docker Hub.
To do so you will need to use a Dockerfile.
A Dockerfile (with the capital D) is a file that contains a list of instructions regarding the image you want to create.
In order to create an image, a simple exercise is to imagine are replicate what would you do if you needed to create the environment manually.
One of the first steps would be to install an OS on your computer, then you would install the software that you would need to write the application in, then install the required dependencies.
Eventually, you would need to set up the files and run certain commands to start the applications.
Docker considers all of these steps as standalone layers and there is an immense advantage in doing so.
Let’s do a few quick examples:
FROM php:7.4-cli COPY . /usr/src/myapp WORKDIR /usr/src/myapp CMD [ "php", "./your-script.php" ]
In this case, this is the Dockerfile
The list of instructions we need to use a PHP application via the Command-line interface.
On the left, we have the instructions,
After we have the arguments that specify what command to perform
Be aware that sometimes when running those commands the memory can exhaust.
In this case, you will get an error from PHP.
Don't worry though because there are many ways you can approach fixing the fatal error: allowed memory exhausted
The first instruction is FROM which selects where Docker has to start creating this image.
Every image is based on another image,
This means that all Dockerfile you will ever write will start with the command FROM and then the image you want to use.
In our case, we are using a PHP image,
The version is specified after the colon, in our case we want PHP 7.4 in a CLI version.
The next command is COPY which copies the local files onto the docker image, in our case, we will be copying the file from “.” which is the current location to the myapp/directory
The WORKDIR instruction sets the working directory for several commands such as RUN, CMD, ENTRYPOINT, COPY, and ADD instructions.
Eventually, when the application is ready we can run a command with the instruction CMD, in this case, the command is “php ./your-script.php” which will execute the PHP code inside the file and show the result in the console.
Other important instructions are:
RUN is used to execute commands on top of the images
EXPOSE indicate to Docker that the container will listen on a specified port at runtime.
ENTRYPOINT this one work like CMD by differs from it by the fact it prompts a requirement in the CLI and requires us to type the information
After having created the Dockerfile, the image needs to be built.
You can do so with the command build and indicate the name and tag of the image you are building with the flag -t
docker build Dockerfile -t [user]/[applicationName]
If you want this to be a public image you can push the image in the repository
docker push [user]/[applicationName]
Do not forget the sequence, you write a Dockerfile to set the image, you build the Dockerfile to create the image, then you push the image to make it available worldwide.
For the record, this same procedure works for every service we need.
Here is a simple Dockerfile of pulling PHP 7.4 with Apache webserver
FROM php:7.4-apache COPY src/ /var/www/html/
That’s below is Ubuntu
# Pull base image. FROM ubuntu:14.04 # Install. RUN \ sed -i 's/# \(.*multiverse$\)/\1/g' /etc/apt/sources.list && \ apt-get update && \ apt-get -y upgrade && \ apt-get install -y build-essential && \ apt-get install -y software-properties-common && \ apt-get install -y byobu curl git htop man unzip vim wget && \ rm -rf /var/lib/apt/lists/* # Add files. ADD root/.bashrc /root/.bashrc ADD root/.gitconfig /root/.gitconfig ADD root/.scripts /root/.scripts # Set environment variables. ENV HOME /root # Define working directory. WORKDIR /root # Define default command. CMD ["bash"]
We can see how Docker files can get very complex sometimes.
Each of the steps within the Dockerfile creates a layer, these layers are cached during the execution, which allows developers to reuse an image multiple times with reasonable time and size advantage.
If for example we create an image using a Dockerfile that edits the 4th instruction and builds another image the first 3 commands will be retrieved from the cache memory, and the number of resources saved makes a clear difference against, for example, virtual machines.
We have learned how to pull a pre-made image from the repository site Docker HUB, then we made some examples of how we can create our image using Dockerfile and setting all the commands we require to create an image,
Now, what do we do with the image we created?
Images are useful until a certain point, eventually, we will need to use them to create containers that allow us to run your application.
To create a container from an image we will need to use the run command.
The syntax is very simple:
docker run [image]
To utilize this command we need the name of the image
This can be either an image that you find on DockerHub or an image that you just created using a Dockerfile.
This command creates and runs an instance of the image into your docker host.
It does so by using all the layers (or instructions) of the Dockerfile.
You will find that you can run multiple instances of the same image and that once one or more layers are saved into your cache memory it will be a lot faster to run the process.
The docker run command has several flags that you can use to specify a particular image that you want or a chosen behavior.
As you have seen a little above we can choose an image to run in our Docker host, for example, I can use the command
docker run php
And a copy of the image called php will be pulled from Docker Hub if is not already present on your pc.
However, I can be more precise by specifying the version of PHP that I want my container to be.
docker run php:7.4
If that’s still not enough and I want the highest version of PHP 7.4 I can indicate an even more specific tag.
in my case, I want the alpine version of the image
docker run php:7.4-alpine
The Docker run command also includes several flags
docker run -i
Let’s imagine you have a PHP script that simply writes your shopping list for the day,
It requires you to input the strings containing the items in the list.
What would happen if you decide to dockerize the script?
By default containers do not listen to any input, it is not able to read anything from the terminal.
That is because Docker runs in a non-interactive mode.
If you want to enable the STDIN you can add the flag -i or --interactive
This will wait for you to type your string and then start the script.
docker run -t
A problem with the interactive command is that the application prompt in the terminal that uses only the interactive flag still leaves it not attached to the terminal we are using to overcome this problem we need to use the --tty or -t flags.
Using the combination docker run -it will eventually show the prompt and make the terminal ask for your shopping list.
Once your PHP project is running do not forget to add tests to it.
It is very important to add automated testing and even easier off-code testing.
Here you can read how to test your Docker project with Postman.
Docker Port management
All the applications you got on your local run inside a space called Docker host (or Docker engine)
When you run a container Docker tells us that it is running and listening on a determined port.
This means that to access your application you need to be connected to your browser using that port.
You also need an IP to access a container.
Each container has an IP,
but it is internal and not visible from the outside.
The IP we need to be able to connect to a container is the IP address of the Docker Host
To complete the operation and be able to work with your container what you need to do is to map one of the ports available in your machine to the port of the container you want to access and use the IP of the Docker Host to make the connection.
We did something similar above when we specified what port did we want to expose in the Dockerfile.
PHP containers usually output to port 80
The Docker Host that contains your container has IP: 192.168.1.5
Let’s say your port 80 is running a web server already and you want to use the 8080
You can run the container and see the output by connecting your port to one of the containers using the tag -p then the relative port separated by a colon
docker run -p 8080:80 [image]
This way you can run different containers on different ports simultaneously.
If you what to run MySql you can type
docker run -p 3306:3306 mysql
You can also run more instances of the same container on different ports at once
docker run -p 8306:3306 mysql
And it will work just fine.
Of course, you cannot map to the same port of the host more than once
Create persistent data with volumes
I am pretty sure that after all this hustle in installing and setting up your first application using Docker you would love to store some data in the database.
As a standard behavior in Docker, the data is saved within a container.
If you pull and run a MySql container, the data will be saved by default into the folder /var/lib/mysql.
Problems start when you want to delete the container because all the data that is saved inside will disappear as well.
The solution is to push the data to storage folders outside the mysql container,
Then save this new position as a volume with the flag -v
docker run -v [volume]:[container] mysql
In a more concrete case the command may look more like this:
docker run -v /opt/datadir:/var/lib/mysql mysql
In this way, when the container run the two directories are bonded and the data will be stored externally in the datadir folder, so it will be safe even if you delete your docker mysql container.
Keeping your container awake
Let’s make one thing clear, the only job that a container has to do is to perform a task.
The proof of this concept is to enter one of the commands above.
What you will see is that after Docker has downloaded all the layers of the image and made the container, it will be exited.
You can check the status of your container using the command
If there are one or more running containers they will appear here otherwise they will appear only if you add the -a to it
docker ps -a
It shows all the containers, both running and exiting, and in fact, is through the exited that you will find the container you have just pulled.
How to Dockerize a PHP application
Now you should have all the basic information for running a simple container on your local and start to work.
In this section, we’ll create and start to work with a PHP application step by step.
Since there are different ways to install docker depending on the OS you are running on your machine here there are a few links that will help you set up the software
Here is what we are going to do:
- Create a PHP application
- Write a Dockerfile
- Build an image using the Dockerfile
- Run the image to obtain a usable container
- Using persistent data
- Get the browser and start coding
Create a PHP application
As a first exercise, we are going to write a simple PHP file
echo “Hello World”;
And save it into a folder called /src
Write a Dockerfile
Here we need to answer a question,
What do we need to run this application?
We must have an OS and a web server,
Since every image needs a base image to exist, who made the official PHP image took already a base image of an OS.
It's one step less for us to care bout,
In our case then, we want PHP, probably its 7.4 version.
And I’d love us to use the Apache version, so let’s do that
Browsing Docker Hub looking for anything related to PHP you will soon find out that the core PHP team provides official images for PHP at this URL:
Reading the description can see several examples of the standard Dockerfiles you might want to use, in our case the instruction and arguments look as follow:
FROM php:7.4-apache COPY src/ /var/www/html/ EXPOSE 80
As you now know the first command is the reference of the base image, the image that needs to be used, in our case we want PHP and the tag is 7.4-apache.
The COPY command instructs the image to copy all the content inside the src/ folder of our Docker host into /var/www/html/ of the container.
Another command we want to use is the EXPOSE,
In this way, we are indicating which port we want the container to listen to.
We can copy this content and save this file next to the src/ folder (not inside).
Build an image using the Dockerfile
Now we have the Dockerfile, the recipe that let us create our image
But to make this a container that we can use we need to build it.
The command is:
docker build -t hello-world .
The flags --tag or -t indicate the name we want to give to this image,
Do not forget the "." at the end of the command.
The last parameter of docker build is the position of the Dockerfile, in this example, I presume we are in the root directory of our project and we can easily access it by typing a dot, which is the symbol for the current path.
You will then see Docker doing its magic downloading all the layers required.
Run the image to obtain a usable container
We are now taking our cake out of the oven.
Everything has been prepared and the cake is ready to be served.
To consume our container we need to use the run command.
docker run hello-world
We are using the same name of the image to indicate which image we want to run.
As you saw earlier the command run has several flags
One that we can use is the -p tag, which stands for -port.
We need that to be able to match port 80 of the host to port 80 in the container.
docker run -p 80:80 hello-world
Note that 80 is the same port we are exposing in our Dockerfile
If you now open your browser and connect to your localhost you should be already able to see the PHP message echoed
Using persistent data
You will notice that if you edit the content inside your PHP file the browser does not get updated.
The reason is that when we build the image we have made a copy of the files.
If you want to see some changes you need to save the file and run the container again.
No doubt this is a huge waste of time.
To solve this problem we are going to use a volume.
What we need to do is to mount an external directory inside our machine and map the file inside the container with the one outside
docker run -p 80:80 -v /var/lib/docker/volumes:/var/www/html/ hello-world
This new -v flag indicates in which directory we want to save the data.
Get the browser and start coding
Now, we can edit the PHP files inside the src/ folder and we’ll be able to see the updates straight away.
In this article, you have seen a really basic introduction and a step-by-step example of how Docker works.
Many developers believe that Docker is the future of development.
The improvements that this software brought to the table, compared with the old style of working with VM is outstanding.
Containers are lightweight and easy to use, once some practice they are even easy to orchestrate with tools such as Docker Swarn or Kubernetes,
Which we are going to talk about in future episodes.
This is not a beginner tool if you do not know how to use PHP or if the acronymous O.O.P. does not mean anything to you there are better places where you can focus your energy.
But, if you are serious about web development, you’d like a job in this industry or simply to understand where this industry is moving toward, Docker has to be on the list of things you must learn.