Docker Based Deployment Of Legacy Application With Different PHP Versions In Windows/Mac
I recently saw a tweet saying we should consider changing the name of legacy application to revenue-generating application and, to be honest, it is a fact, no matter what we are working on the tech industry is constantly changing with new versions of packages coming every day. Whatever we are working on is going to become a legacy application someday and will have a different set of software and package requirement then the latest one installed in your system.
Take the example of Laravel 5.6, So if we have one project in Laravel 5.6 and other in Laravel 5.4 or 5.5 we have to switch versions on our local system.
Now the question is how to keep our latest setup separate with all these different legacy applications and provide them their needed set of PHP and Package versions to run?
To be clear here Laravel is a very mature framework right now and since it is latest, there are lots of option available to run different framework versions, For local deployment best one is Laradock, What I am talking about is applications which are in core PHP 5.3 or have frameworks like Codeigniter older versions or Cake PHP Older versions.
Today we will be looking into a solution to this problem for the windows/MAC operating system.
To begin If you have some basic understanding of what docker is and how it works it will be a lot easier for you to grasp If not then you do not have to worry we will go step by step doing exactly what needed to be done for getting your system ready.
Docker Basics Detailing And Some Useful Commands: In a nutshell docker is a better version of VirtualBox providing you lightweight operating systems on your current OS which you can quickly destroy and modify based on your need and run your application inside them. Being a developer you must have heard about Virtualbox.
So in the virtualization concept, there is an image that you can download, customize them and build run OS (or containers) based on those images. From an expert perspective, there can be a lot of differentiation but for a starting point about docker, this is all that you need to know.
Now let’s get started with docker commands and practices, just the basics one’s.
- Install Docker for windows/Mac
- Open PowerShell or Terminal
- Run the command
docker run hello-world
you will see docker downloading hello-world container image(since it doesn’t exist locally) and running it.
Hello from Docker!This message shows that your installation appears to be working correctly.
Since after printing hello world the container exited you can see it by running the command:
docker ps -a
above command lists all running and stopped containers with their details.
Remember container is just a small OS running inside docker virtual environment.
So now we have reached a point where we know what container is and how to run it locally.
Now let’s get familiar with some small useful docker commands before we proceed further which will come handy in our journey.
- docker ps: It shows you your currently running containers
- docker ps -a: It shows you all your running and stopped containers
- docker stop <container-id>: It will stop the container whose ID you will pass (In the above screenshot you can see the container id in the leftmost column)
- docker rm <container-id>: It will remove the container whose id you will pass
- docker rmi <container-id>: It will remove the image whose id you will pass
- docker images: shows you all your local downloaded images based on which you can build an image, If you request a new image first it will be downloaded in your system then the running instance will be built on it. The difference between image and container is that an Image is just a description but a container is an actual object based on any image description which can be started and stopped.
Although we won’t be using any image-related operations in our journey, still some knowledge might come in handy if you want to play around.
Now let’s start building our setup
- First, we need a project
- Let’s create a project folder with a named legacy anywhere on our computer.
- Let’s create an infrastructure folder in our legacy folder
- Inside your infrastructure folder create a file with the name Dockerfile
- Write below code in your Dockerfile
FROM orsolin/docker-php-5.3-apacheCOPY . /var/www/htmlCOPY /infrastructure/start.sh /usr/local/bin/RUN chmod +x /usr/local/bin/start.shENTRYPOINT service apache2 restart && bashRUN /usr/local/bin/start.sh# remove already running container# docker rm $(docker stop $(docker ps -q))
Let's explain above file line by line
FROM orsolin/docker-php-5.3-apache
This is the location you are telling docker to pull image from, it is like a unique name same as your username on git / your public repository name. Docker hub follows the same for docker images. You can create your own image and place them on the docker hub and use them in your file. Be creative here and use an image that suits your project requirement, try to find an image with the setup as close as possible to your project configuration.
COPY . /var/www/html
here we are copying our parent project folder into the /var/www/html folder of the project, now this means you will not be able to make changes outside your container if you do this you will have to ssh inside your container and make changes there in the file. There is another way of just referencing your outside project inside your container image which I will talk about when we will start our container.
COPY /infrastructure/start.sh /usr/local/bin/
We will be copying a start.sh file inside our container and running it when the project starts for setup purposes. In case we need additional programs to be installed in your container.
RUN chmod +x /usr/local/bin/start.sh
here we are changing the permission of copied start.sh, file to be executable.
ENTRYPOINT service apache2 restart && bash
Here we are defining a command to start apache2 when the container starts
RUN /usr/local/bin/start.sh
After our container starts we are running our start.sh file.
docker rm $(docker stop $(docker ps -q))
This command is chaining of all the docker commands we need to use from our terminal, what it does is
* First takes all the running containers ID
* Stops them
* Then remove them
I have done this to make sure when you are running and making changes in your setup all the already containers get removed to avoid confusion.
The way docker workes is it utilizes a lot of caching features which makes it faster which is good for quick deployments but If I don’t disable it while we are doing our setup, sometimes in process of learning and trying different command we forget to remove cache and don’t see our changes in output and waste lot of time only to realize later that the cache needs to be cleared.
For small local setup, this none caching mechanism will work without wasting much time.
Now that we have our docker file ready, let's create a start.sh file which will be copied in our /usr/local/bin directory to run when the container starts.
For this create a start.sh file in the infrastructure folder.
#!/bin/bashset -eecho ‘welcome’ >> /var/www/html/welcome.php
copy above code inside it, there is nothing much going on here just echoing welcome in a file and placing it in our html directory inside docker which we can test later when our container starts. You can add as many installation commands along with further customization.
just remember to add -y when you install a software in your start.sh file so that it doesn’t askes you for y/n each time.
e.g apt-get install -y vim
Now let's have a look at image creation and docker startup.
For this, we have two paths to continue.
1. .sh file: For Mac
2. .bat file: For windows
Let’s go with both one by one.
- Create a DeployLocal.sh File in your infrastructure directory
docker build -t legacy -f ../infrastructure/Dockerfile ../ --no-cachedocker run -ti -d -p 8765:80 legacy:latest /bin/bash#start firefox.exe http://localhost:8765/#start chrome http://localhost:8765/open http://localhost:8765/ # open default browser in Mac
Copy the above code in that file.
Now let's explain our DeployLocal.sh file line by line.
docker build -t legacy -f ../infrastructure/Dockerfile ../ --no-cache
here we are building an image from our Docker file, this can be done faster if you remove —no-cache but for the first-time aspect and avoid confusion I have added that option.
docker run -ti -d -p 8765:80 legacy:latest /bin/bash
This command will start a container based on our latest image build.
As I was talking that in our Dockerfile copy command is copying our code to our docker container and we were not able to modify that file without going inside our container. What that means is you can comment that copy line and then add -v option in this run command and specify our local to container mounting like -v . : /var/www/html which will mount our project root to html folder. In such case this command will become:
docker run -ti -d -p 8765:80 -v .:/var/www/html legacy:latest /bin/bash
open http://localhost:8765/
This command will open our project root folder on the port we mapped in our docker run command.
2. Create a DeployLocal.bat File in your infrastructure
docker build -t legacy -f ../infrastructure/Dockerfile ../ --no-cachedocker run -ti -d -p 8765:80 legacy:latest /bin/bash::start firefox.exe http://localhost:8765/start chrome http://localhost:8765/::open http://localhost:8765/
This is the same as our .sh file which we can run inside Powershell, just changing the way of opening the browser.
Now let’s talk about some issues you might face on following this tutorial
1. Port already in use issue in the terminal when running DeployLocal.bat or .sh: what this means is there is already a container running and using the port means you have run DeployLocal.bat or .sh twice without destroying the previous container. Just run below command on your terminal and deploy it again.
docker rm $(docker stop $(docker ps -q))
2. My application can’t access my localhost:3306 MySql: For this docker has provided URL’s for windows and Mac, what you need to do is on the place of localhost put below URL in your configuration file:
in windows case: host.docker.internal
in Mac case: docker.for.mac.localhost
and on the run time it will translate to the localhost of your host OS and you will be able to connect to your host machine localhost.
I will be improving the code further Feel free to give PR or raise Issues on Github if you face any problem in running it locally or you have any suggestions, I will be more then happy to help you out further.