Recently I had a chance to learn about Docker and also played around with it for a while. It’s amazing technology, which is here to take away all the pain of deployment. In the current world of web hosting services, it’s always a pain to move from one host to another. The major reason is that you will have to configure the new hosting server completely the same way right from scratch manually and if that doesn’t go accurately, things gonna break very badly.
With docker, you have every step documented/defined(in Dockerfile or as a Docker image), and thus it will always be the same, independent of where you are going to host. And most of all, you can automate the creation/configuration of the server, which might take you hours or even days!
Today, I will try to explain how someone can Dockerize an application(which is multi containers based) as a beginner. I will be using PHP and MySQL stack. If you are looking for Dockerizing NodeJS app, then you can follow official example for that. I am using my open-source codeigniterplus PHP/MySQL project as the candidate application.
To follow this tutorial, you will need to know how docker works. You can get a rough idea from the official what is docker web page. You can also watch the video, which is very helpful as well. To get your hand dirty, you can try it on interactive web-based terminal to get used to some common commands.
Additionally, here are some terms that we will be using throughout this tutorial, which you will need to know.
- Docker Image: Docker image is a ready snapshot of a configured server/service. We are free to use a docker image as it is(if sufficient) or extend it to a new docker image to meet our needs.
- Docker Container: Containers are the running/live in action server/service of Docker images.
- DockerFile: Dockerfile contains a set of instructions/commands that will be executed to form a new docker image. That Dockerfile can be treated as a well-documented schema version of that docker image.
- Fig: It’s probably moving forward with a new name called ‘Docker Compose’, recently announced by a recent post on docker blog
Basically, you need to have two things set up on your machine where you are trying this tutorial and on the server machine where you are planning to have this docker-based deployment.
- Docker: Docker has comprehensive instruction pages for several different platforms about installations. Just follow the one you are interested in.
- Fig: As we will be using fig too, get yourself set up with official instructions.
Step 1: Make Your PHP Project Build-able Completely With Commands:
As we want our solution to be as automated as possible, try to have a set of commands(dependency installation with composer, run PHPUnit tests, change directory/file permissions etc) that will get your application completely ready for deployment. If you have no idea how to do that, you can follow my other tutorial on building php application in single step. You just focus on preparing the application itself, not the environment yet.
Step 2: Create Customize Docker File/Image From An Existing PHP-Based Docker Image:
Now it’s time to get our hands dirty with docker. Basically, you will want a ready image that has PHP installed so that you don’t have to add instructions to install PHP itself on the Dockerfile. If you can’t find anything suitable, you can refer to mine, which I created a few days ago for exercise/testing purposes.
Now you will have different configuration files for the production environment that you will want to replace or add to the source code. For that, you will have to add instructions manually in the Dockerfile to add them from the host machine to the docker container. In my cases, I have kept those such files in the ‘docker-files’ directory(feel free to store them in a directory you are flexible with).
Here is what my Dockerfile looks like.
FROM ranacseruet/php-node #App setup RUN rm -fr /app RUN rm -fr /app && git clone https://github.com/ranacseruet/dockerized-php.git /app ADD docker-files/makefile /app/makefile RUN cd /app && make ADD docker-files/database.php /app/application/config/database.php ADD docker-files/run.sh /run.sh ADD docker-files/.htaccess /app/.htaccess RUN chmod 755 /*.sh RUN rm -fr /etc/supervisor/conf.d/supervisord-mysqld.conf EXPOSE 80 CMD ["/run.sh"]Code language: HTML, XML (xml)
Just in case you don’t know, the original docker image(from which I created the PHP-node one) uses ‘/app’ as the main root web directory for Apache.
Step 3:(Optional) Create Customize DockerFile/Image For Your MySQL Server:
This step is optional if you want to have some advanced customizations on your MySQL service. I haven’t done anything regarding thus, I didn’t need an additional Dockerfile for this.
In case you are doing this, just remember to have a separate directory for a new Dockerfile.
Step 4: Create Shell Script That will run all initialization commands for you:
If you have some special instructions that you will need to run after the docker image is created, but before you have that service running(such as apache web server), add them in a shell script.
The image I mentioned above has supervisor process monitoring service installed, and applications(apache) run through it, so the only thing I needed is to run the supervisord. I also needed to create a database schema from Doctrine ORM CLI. Thus my shell script looks like below:
cd /app && vendor/bin/doctrine orm:schema-tool:create exec supervisord -nCode language: HTML, XML (xml)
Step 5: Create Fig Configuration:
Now it’s time to create a fig configuration file. It includes the configuration for our complete application infrastructure, including how they are connected. So, in this case, it includes two docker container configurations, one for the PHP application itself, and another for the MySQL database. It can also include ports to be exposed(80 for the Apache HTTP server) from a container.
Fig also suppose to facilitate mounting host volume inside containers so that on newer deployment, it can get access to production data on the host machine. But it didn’t work when I tried it on my Mac OSX machine. Most probably because of this open issue for fig on github. Thus I had to follow alternative workarounds. But I will encourage you to try yourself too from the ‘volumes’ section of fig yml official reference guide.
Here is my basic setup in fig.yml:
## YAML Template. web: build: . ports: - "80:80" links: - db db: image: mysql environment: MYSQL_DATABASE: dockerized-test MYSQL_ROOT_PASSWORD: rootCode language: PHP (php)
Step 6: Start Up Your Dockerized PHP App!
Well, if you were able to have all the previous steps done properly without any error, now you should be able to run both containers together in one single fig command:
$fig upCode language: PHP (php)
That’s it! We have our first Dockerized PHP application running with a connection to the database. Congratulations!!
I have kept my test version as a git repository, which you can use as ready-made scripts.
Extending The Setup With More Containers:
As you are a cool developer, I am sure you are not developing only with a database; rather, you might have some more attractive modern services like Redis, Rabbitmq/Gearman message queue, workers etc. Or even more than one PHP application, a mix of PHP/Node/Python/Ruby services etc. You can now go forward with whatever quest you are up to, from this simple two-container setup to as many container setups as you want. Just you should properly mention ‘link’ property to specify which container links to which.
tip 1- In production, it’s critical to mount the user data, like uploaded images/files, logs and/or database volumes etc, from the host. You must not save them inside a running container as docker containers are disposable! When there is a new version of your application, you will be creating a new docker image/container with the latest changes, thus losing all previous DB/user data!
tip 2- you don’t have to expose the port externally always, only if you want it to be publicly available. Otherwise, internally linked containers can access each other on any port without any external help. For example, if you haven’t noticed, we haven’t exposed any port for the MySQL database container because we don’t need to. With this feature of docker, you are even more protected now than ever before!
Well, as a disclaimer, I would like to let you know that I am a beginner to Docker myself, and thus my setup shown above to Dockerize an application might not be the most optimized way to get stuff done. As I learn more, I will be able to share better ways in future(so get in touch!). But it should help when you are just starting with a simple setup that you might get excited to see working.
If you are having any trouble/issues with the setups mentioned in this tutorial, please feel free to contact me via commenting below. Happy Dockerizing :)!