Virtual Machines (i.e. AWS EC2) can be used to deploy applications and web solutions. They are scalable to some extent but if you need to deploy many small applications and some of the applications will require various OS and runtimes of different versions and conflicting dependencies. In such cases, you will need to spin up servers for each group of applications with the exact OS and dependencies requirements.
When it scales out to tens/hundreds and even thousands of applications (i.e. Microservices), this approach becomes very tedious and challenging to maintain.
This project focuses on how to solve this problem with the use of the the technology that revolutionized application distribution and deployment back in 2013! We are talking of Containers and simply Docker. Even though there are other application containerization technologies, Docker is the standard and the default choice for shipping your app in a container!
A virtual machine (i.e. any cloud provider) or your workstation that will host the Docker containers.
The following steps are taken to migrate to the cloud with containerization using docker:
- Write a shell script to install Docker.
cat <<EOF | tee docker.sh
#!/bin/bash
sudo apt-get update
sudo apt-get -y install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
newgrp docker
EOF
- Run the shell script.
bash docker.sh
- Verify the installation.
docker ps
We are migrating the Tooling Web Application (PHP-based web solution backed by a MySQL database) from a VM-based solution into a containerized one.
- Pull the MySQL Docker Image from Docker Hub Registry. You can download a specific version or opt for the latest release, as seen in the following command:
docker pull mysql/mysql-server:latest
- List the images to check that you have downloaded them successfully.
docker images
- Once you have the image, move on to deploying a new MySQL container.
docker run --name <container_name> -e MYSQL_ROOT_PASSWORD=<my-secret-pw> -d mysql/mysql-server:latest
-
Replace
<container_name>
with the name of your choice. If you do not provide a name, Docker will generate a random one. -
The
-d
option instructs Docker to run the container as a service in the background. -
Replace
<my-secret-pw>
with your chosen password. -
In the command above, we used the latest version tag. This tag may differ according to the image you downloaded.
- Check to see if the MySQL container is running: Assuming the container name specified is
mysql-server
.
docker ps -a
You should see the newly created container listed in the output. It includes container details, one being the status of this virtual environment. The status changes from health: starting
to healthy
, once the setup is complete.
We can either connect directly to the container running the MySQL server or use a second container as a MySQL client. Let us see what the first option looks like.
Connecting directly to the container running the MySQL server.
docker exec -it <container_name> mysql -uroot -p
First, create a network.
docker network create --subnet=172.18.0.0/24 tooling_app_network
Creating a custom network is not necessary because even if we do not create a network, Docker will use the default network for all the containers you run. By default, the network we created above is of DRIVER Bridge
. So, also, it is the default network. You can verify this by running the docker network ls
command.
But there are use cases where this is necessary. For example, if there is a requirement to control the cidr
range of the containers running the entire application stack. This will be an ideal situation to create a network and specify the --subnet
.
For clarity's sake, we will create a network with a subnet dedicated for our project and use it for both MySQL and the application so that they can connect.
-
Run the MySQL Server container using the created network.
-
Create an environment variable to store the root password.
export MYSQL_PW=<root-secret-password>
- Pull the image and run the container, all in one command like below:
docker run --network tooling_app_network -h mysqlserverhost --name=mysql-server -e MYSQL_ROOT_PASSWORD=$MYSQL_PW -d mysql/mysql-server:latest
Flags used:
-d
runs the container in detached mode--network
connects a container to a network-h
specifies a hostname
Note: If the image is not found locally, it will be downloaded from the registry.
- Verify the container is running.
docker ps -a
As you already know, it is best practice not to connect to the MySQL server remotely using the root user. Therefore, we will create an SQL script that will create a user we can use to connect remotely.
- Create a file and name it
create_user.sql
and add the below code in the file:
CREATE USER '<user>'@'%' IDENTIFIED BY '<client-secret-password>';
GRANT ALL PRIVILEGES ON * . * TO '<user>'@'%';
- Run the script.
docker exec -i mysql-server mysql -uroot -p$MYSQL_PW < ./create_user.sql
If you see a warning like below, it is acceptable to ignore:
mysql: [Warning] Using a password on the command line interface can be insecure.
The good thing about this approach is that you do not have to install any client tool on your laptop and you do not need to connect directly to the container running the MySQL server.
- Run the MySQL Client Container:
docker run --network tooling_app_network --name mysql-client -it --rm mysql mysql -h mysqlserverhost -u <user-created-from-the-SQL-script> -p
Flags used:
--name
gives the container a name-it
runs in interactive mode and Allocate a pseudo-TTY--rm
automatically removes the container when it exits--network
connects a container to a network-h
a MySQL flag specifying the MySQL server Container hostname-u
user created from the SQL script-p
password specified for the user created from the SQL script
- Clone the Tooling-app repository from here.
git clone https://github.com/darey-devops/tooling.git
- On your terminal, export the location of the SQL file.
export tooling_db_schema=~/tooling/html/tooling_db_schema.sql
You can find the tooling_db_schema.sql
in the html
folder of cloned repo.
- Use the SQL script to create the database and prepare the schema. With the docker exec command, you can execute a command in a running container.
docker exec -i mysql-server mysql -uroot -p$MYSQL_PW < $tooling_db_schema
- Update the
db_conn.php
file with connection details to the database.
vi tooling/html/db_conn.php
$servername = "mysqlserverhost";
$username = "<user>";
$password = "<client-secret-password>";
$dbname = "toolingdb";
- Go into the tooling directory.
cd tooling && ll
- Build your image.
docker build -t tooling:0.0.1 .
- Run the container.
docker run --network tooling_app_network -p 8085:80 -it tooling:0.0.1
- If everything works, you can open the browser and type:
http://localhost:8085
- Input your name and password used when updating the
db_conn.php
file.
- Create a network.
docker network create --subnet=172.18.0.0/24 my_todo_network
- Run the MySQL Server container using the created network.
docker run --network my_todo_network -h mysqlserverhost --name mysql-todo -d mysql/mysql-server:latest
Flags used:
-d
runs the container in detached mode.--network
connects a container to a network.-h
specifies a hostname.--name
gives a custom name to the container.
docker ps
- Once initialization is finished, the command's output is going to contain the random password generated for the root user; check the password with this command:
docker logs mysql-todo 2>&1 | grep GENERATED
Run the mysql client
within the MySQL Server
container you just started and connect it to the MySQL Server.
- Use the
docker exec -it
command to start a mysql client inside the Docker container you have started, like this:
docker exec -it mysql-todo mysql -uroot -p
- When asked, enter the generated root password. Because the MYSQL_ONETIME_PASSWORD option is true by default, after you have connected a mysql client to the server, you must reset the server root password by issuing this statement:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';
- Create the user and the database.
CREATE USER 'ikenna'@'%' IDENTIFIED BY 'password1'; GRANT ALL PRIVILEGES ON *.* TO 'ikenna'@'%';
- Create the
tododb
database.
CREATE DATABASE tododb;
- Exit the console.
exit
-
Clone the PHP-Todo-app repository from here.
-
Go into the
php-todo
directory.
cd php-todo
- Create an
.env
file with connection details to the database.
cat <<EOF | tee .env
APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomString
APP_URL=http://localhost:8000
DB_HOST=mysqlserverhost
DB_DATABASE=tododb
DB_USERNAME=ikenna
DB_PASSWORD=password1
DB_CONNECTION=mysql
DB_PORT=3306
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
EOF
- Create an application script for your application.
cat <<EOF | tee app.sh
#!/bin/bash
php artisan migrate
php artisan key:generate
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan serve --host=0.0.0.0
EOF
- Create a Dockerfile to build the image.
cat <<EOF | tee Dockerfile
# Use an official PHP image as a parent image
FROM php:7.4-cli
USER root
# ENV DB_HOST=
# ENV DB_DATABASE=
# ENV DB_USERNAME=
# ENV DB_PASSWORD=
ENV COMPOSER_ALLOW_SUPERUSER=1
# Set the working directory in the container
WORKDIR /var/www/html
# Install dependencies (including Composer)
RUN apt-get update && apt-get install -y libpng-dev zlib1g-dev libxml2-dev libzip-dev libonig-dev zip curl unzip && docker-php-ext-configure gd && docker-php-ext-install -j2 gd && docker-php-ext-install pdo_mysql && docker-php-ext-install mysqli && docker-php-ext-install zip && docker-php-source delete && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Copy the rest of the application code into the container
COPY . /var/www/html
# Install Laravel dependencies
RUN composer install
# Expose port 8000 for the Laravel development server
EXPOSE 8000
# Define the command to start the Laravel development server
ENTRYPOINT [ "bash", "app.sh" ]
EOF
- Build the Docker image the Todo app will use.
docker build -t todo-app-image:1.0.0 .
- Run the container.
docker run --network my_todo_network --name todo-app-coantainer -p8000:8000 -d todo-app-image:1.0.0
Let us observe those flags in the command:
We need to specify the --network
flag so that both the Todo app and the Database can easily connect on the same virtual network we created earlier.
The -p
flag is used to map the container port with the host port. Within the container, Laravel is the webserver running and by default, it listens on port 8000
.
We used port 8000
directly on our host because it is not in use yet. If it was in use, the workaround is to use another port that is not used by the host machine, so we can then map that to port 8000
running in the container (could somethong like -p 8085:8000).
- Access the web browser.
http://ip_host_vm:8000
-
Please make sure you have a Docker Hub account before you proceed.
-
Create a public
php-todo-app
repository on Docker Hub.
- Log into Docker Hub from your VM, input your email address and password in the prompt..
docker login
- Tag your Docker Image.
ocker tag local-image:tagname DockerHubusername/repositoryname:tagname
- Push the Docker Image.
docker push username/repositoryname:tagname
- Check your Docker account to confirm it has been pushed suuccessfully