Saturday, August 3, 2024

Docker for Mac tutorial


Hello Everyone :)

In this this tutorial I'm going to show you guys how to install docker on mac os system. for those who don't know what docker is, docker is a container manager that allows the user to have an integrated and consistent environments between the development, test and the production environments. so to avoid having this kind of conversation.

" user : the bug is still persistent in the production environment
developer : the application works fine for me there is no bug ! "


with docker all the environments are configured the same.

to "dockerise" your environment, let's start by downloading docker  , download the dmg file and then execute it, once done let's have a sample application to dockerise :

Let's build a simple html page for our example with php that is going to show a list of objects (a list cars for example) , and a api application developed with Python that will exposes the rest endpoints.

we are going to see the concept of volumes , images and containers throughout this tutorial.

let's star coding ..

The overall files structure of the project would looks like this : 

docker-tuto-folder | -----> | app1 |----> Dockerfile
                                   |----> src ----> | index.php

                   | -----> | app2 |----> Dockerfile
                                   |----> src ----> | app.py


After downloading your docker desktop, install it in your mac
 
let's start app1
app1/Dockerfile : 
FROM php:7.0-apache
COPY src/ /var/www/html
EXPOSE 80

FROM php:7.0-apache -> download php image 
COPY src/ /var/www/html  -> copy the content of src (path of src is relative to where the Dockerfile is) file from the the host machine folder into the /var/ww/html folder inside the container's folder
EXPOSE 80 -> make sure to open port 80 so you can access thought the browser

The Docker file represent the environement that you want to run. Once it is built, it will create the image (for app1). This image will be the blueprint (or the template) for the creation of the containers (executables) . Keep in mind that the container is the instantiation of the image.
It's always a best practice to have each micro service goes in a single container.


let's put some content in the index.php
<html>
<header></header>
<body>
<h2>Hello world</h2>
<marquee>Hello world</marquee>
</body>
</html>

Once done let's create the image for app1 with Docker build command : 
>. 'Docker build -t hello-world .'   -> building the image based on the php official image. And name it hello-world and pass the path of the Dockerfile '.' which is in the current folder. 

Use this command to see the list of images in your local repository.
>. Docker images 

To create your container , use Docker run , it will create a container based on the image and run it :
>. Docker run -p 80:80 hello-world

-p 80:80 (forward the port 80 from the host (1st one) to the port 80 of the container (2nd one)) 


Open browser with localhost:80 and see the html page that you have created 


If we put some modification in the code the result is not automatic we have to kill the container and rebuild it  , which is a pain and a lot of resources being consumed


To do so we use shared volume , there are 2 types : 
  •  shared volume between containers 
  •  shared volume with the host (we are going to use this one )
With the shared volume every modification can instantly being shown on the result , the container will have an access to our code folders to pick the sources


To do so : use -v option to mount the volume like this : 

>. docker run -p 80:80 -v /yourpath/docker-tuto-folder/app1/src/:/var/www/html/ hello-world





For app2 let's create a python script for it :
 
from flask import Flask, jsonify

app = Flask(__name__)

# Sample data: list of car objects
cars = [
{"id": 1, "make": "Toyota", "model": "Corolla", "year": 2020},
{"id": 2, "make": "Honda", "model": "Civic", "year": 2019},
{"id": 3, "make": "Ford", "model": "Mustang", "year": 2021},
{"id": 4, "make": "Chevrolet", "model": "Camaro", "year": 2022},
{"id": 5, "make": "Ford", "model": "Focus", "year": 2015}
]

# Route to return the list of cars
@app.route('/cars', methods=['GET'])
def get_cars():
return jsonify(cars)

if __name__ == '__main__':
app.run(debug=True)

it's a simple app that uses flask framework for web development. The app exposes a res api that returns a list of cars through the end point /cars.


Then create the Dockerfile With :

FROM python:3.9-slim
WORKDIR /app
COPY src/ /app
RUN pip install flask
EXPOSE 5000
ENV FLASK_APP=app.py
CMD [ "python", "-m" , "flask", "run", "--host=0.0.0.0"]

The docker files says to create an environment based on python:3.9-slim image (the official python image), set the working directory of the python app into /app , copy files from src folder and past them into the working directory , install the necessary dependency for flask web server to work , exposes the port on which the app will communicate , set a necessary environment variable in order for flask to work and finally the set the command that is going to be executed inside the container once this one is created and launched.

Then we can use docker build and docker run with various arguments but it so painful when you have a lot of service hence a lot of container.
To handle this problematic we have docker compose :D
Docker compose allows to define all the container in a single configuration file.
And then they can be started and stop all at once. 


Let's create a docker-compose.yml file 


docker-tuto-folder | -----> | app1 |----> Dockerfile
                                   |----> src ----> | index.php

                   | -----> | app2 |----> Dockerfile
                                   |----> src ----> | app.py
                   
                   | -----> | docker-compose.yml

docker-compose.yml :
version: '3'

services:
app2-service:
build: ./app2
image: hello-cars:latest
container_name: app2-api
volumes:
- ./app2/src:/app
ports:
- 5000:5000

app1-service:
build: ./app1
image: hello-world:latest
container_name: app1-php
volumes:
- ./app1/src:/var/www/html
ports:
- 80:80
depends_on:
- app2-service

Make sure to not have tabulation/space in the file before the ‘ : ’ characters


VERSION :’3’-> version format of the file latest one is 3

services:
app2-service: (the service name , the file allows to define many services each reference a dockerfile)
build: ./app2 (where the Dockerfile is present)
  image: hello-cars:latest (specify the image name that is going to be used)
  container_name: app2-api (specify the container name that is going to be used)
volumes:
- ./product:/usr/src/app. -> use ‘-‘ to specify a list of parameter here only one  , the    volume mapping
ports:
- 5001:80 —> port mapping 

 app1-service:
  build: ./app1
  image: hello-world:latest
  container_name: app1-php
  volumes:
   - ./app1/src:/var/www/html
  ports:
   - 80:80
  depends_on:
   - app2-service



Making sure that app1 module is dependent on the app2-service module (see last instruction line in the config file)

Launch : docker-compose up  to see the application running with no painful pre configuration  to do just run 

>. docker-compose up 

You can run it with detach mode ‘ docker-compose up -d ‘ the service will run in background mode .

You can then check on them the following commands :

>. Docker ps 


You can stop them with 

>. docker compose down 






Now let's make the php page get the data from python endpoint. the two apps are into separate containers (two separate context) they can't communicate with each other unless a communication medium is established between them. Docker containers can communcate through shared files/folder through the host or through virtual network.

In Our example we are going to set a network (bridge network) between the two containers in order to establish the communication between app1 and app2.

First let's update the Docker compose by adding the network configuration :
networks:
app-network:
name: app-network
driver: bridge

This configuration tells Docker to create a virtual network of type bridge called app-network.
Then we need to connect the services into that network.
The Docker compose file would looks like this : 

version: '3'

services:
app2-service:
build: ./app2
image: hello-cars:latest
container_name: app2-api
volumes:
- ./app2/src:/app
ports:
- 5006:5000
networks:
- app-network

app1-service:
build: ./app1
image: hello-world:latest
container_name: app1-php
volumes:
- ./app1/src:/var/www/html
ports:
- 82:80
depends_on:
- app2-service
networks:
- app-network

networks:
app-network:
name: app-network
driver: bridge



In every module a networks param is added where it's mentioned that it should be connected to the bridge network that we created in the bottom of the file.

After update the docker compose file let's run 
>. docker compose down 
>. docker compose up --build

And make sure that network has been created and both of the modules (app1 , app2) are connected to it 

>. docker network inspect app-network



After setting the docker configuration we can update the index.php file by adding this sample code into the body tag. this piece of code is going to call the api show the data in the page
<?php
// URL of the Flask API
$api_url = 'http://172.18.0.2:5000/cars';

// Initialize a cURL session
$curl = curl_init($api_url);

// Set cURL options
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPGET, true);

// Execute the cURL request and get the response
$response = curl_exec($curl);

// Check for cURL errors
if ($response === false) {
echo "cURL Error: " . curl_error($curl);
} else {
// Decode the JSON response
$data = json_decode($response, true);

// Display the data as-is
echo '<pre>';
print_r($data);
echo '</pre>';
}

// Close the cURL session
curl_close($curl);
?> 

Note that api_url uses the internal ip of the app2 module calling the internal port 5000 , use the generated IP you have.

$api_url = 'http://172.18.0.2:5000/cars';


Once executed we can see the result on the page :




enjoy ;)




some useful commands : 

>. docker build -t hello-cars .
>. docker run -p 5003:5000 hello-cars

>. docker logs -f <id-container>
>. docker image remove <id-image> -f

>. docker run -d -p 5004:5000 --name hello-cars-ct hello-cars
    (you can name you container in order not deal with ids)
>. docker stop hello-cars-ct


No comments:

Post a Comment