🚀Day 12- Advanced Docker: A Comprehensive Guide

🎉 Hello There!!! I am Mohd Ishtikhar Khan, a skilled Cloud & DevOps engineer, having experience in automating various aspects of software development and deployment, including code integration, testing, and deployment processes and Planning and designing the cloud infrastructure in AWS.
⭐️ I thrive in bridging the gap between development and operations teams, with the goal of automating and streamlining the software development and deployment process. As Cloud and DevOps Engineers, we work to improve the speed, efficiency, and quality of software delivery, making it possible to release code faster and more reliably.
Experience in Designing and deploying dynamically scalable, available, fault-tolerant, and reliable applications on the Cloud. Maintenance and support of cloud infrastructure.Implementing cost-control strategies and Troubleshooting and resolving issues with the cloud infrastructure.
Experience in designing solutions that will help customers migrate, operate, deploy, optimize, and execute the DevOps vision of a project and also have a strong passion for technology exploration and development.
I believe that I am somebody with a very strong work ethic because I thrive in challenging environments and I also love building relationships and going out of the way to help a client.
🤝Let's connect! If you're looking to discuss DevOps fundamentals or if you want to share your experience, feel free to reach out to me at mohdishtikhar1786@gmail.com.
What is Docker Compose?
Docker Compose is a tool for defining and running multi-container Docker applications.
It allows you to specify an entire application stack, including services, networks, and volumes, in a single
docker-compose.ymlfileDocker Compose simplifies the process of managing and orchestrating multiple containers, making it easier to deploy and scale applications
You can start, stop, and restart your application and its services with a single command, and easily scale it up or down as needed.
Docker Compose can work with other orchestration tools, such as Docker Swarm, to help you manage and deploy your application at scale.
There is a three-step process to work with Docker Compose.
1. Define the application environment with Dockerfile for all services.
2. Create a docker-compose.yml file defining all services under the application.
3. Run docker-compose up to run all services under applications.

Benefits of Docker Compose
Simplifies container management: Docker Compose makes it easy to manage multiple containers as a single application.
Enables easy deployment: You can easily deploy your application to any environment using Docker Compose.
Provides environment consistency: Docker Compose ensures that all containers in an application are running on the same version of the software.
Offers scalability: Docker Compose makes it easy to scale container-based applications horizontally.
Security - All the containers are isolated from each other, reducing the threat landscape
Docker Compose Features:
Service Definition:
- Docker Compose allows you to define services, each representing a containerized component of your application. You can specify the image, environment variables, volumes, ports, and other configuration options for each service.
version: "3"
services:
web:
image: nginx:latest
ports:
- "8080:80"
Multi-Container Applications:
- With Docker Compose, you can define and run applications composed of multiple containers that work together. This is particularly useful for microservices architectures, where different services run in separate containers.
Orchestration:
- Docker Compose provides simple orchestration capabilities, allowing you to start, stop, and scale your application with a single command. This is beneficial for development, testing, and even production scenarios.
Environment Variables:
- You can use environment variables in the
docker-compose.ymlfile to configure services dynamically. This flexibility allows you to customize the behavior of containers without modifying the Docker Compose file.
- You can use environment variables in the
version: "3"
services:
web:
image: nginx:latest
environment:
- NGINX_PORT=8080
Networking:
- Docker Compose automatically creates a default network for your application, allowing containers to communicate with each other using service names as hostnames. You can also define custom networks for more complex setups.
version: "3"
services:
web:
image: nginx:latest
networks:
- mynetwork
networks:
mynetwork:
Volume Mounting:
- Docker Compose allows you to define volumes and mount them into containers. This is useful for persisting data or sharing files between containers.
version: "3"
services:
db:
image: postgres:latest
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Healthchecks:
- Docker Compose allows you to define healthchecks for services. Healthchecks determine whether a container is healthy and ready to accept requests. This feature is essential for orchestrating deployments, ensuring that services are started only when they are in a healthy state.
version: "3"
services:
web:
image: nginx:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
Build Context:
- The
builddirective in Docker Compose allows you to specify a build context, which is the path to the directory containing the Dockerfile. This feature is particularly useful when building images with dependencies located outside the current directory.
- The
version: "3"
services:
web:
build:
context: ./myapp
Dependency Management:
- Docker Compose enables you to define dependencies between services using the
depends_ondirective. This ensures that services are started in the correct order.
- Docker Compose enables you to define dependencies between services using the
version: "3"
services:
web:
image: nginx:latest
app:
image: myapp:latest
depends_on:
- web
Variable Substitution:
- Docker Compose supports variable substitution within the
docker-compose.ymlfile. This allows you to reuse and interpolate values, making the file more dynamic and easier to maintain.
- Docker Compose supports variable substitution within the
version: "3"
services:
web:
image: nginx:${NGINX_VERSION:-latest}
Secrets Management:
- Docker Compose supports the use of Docker secrets for managing sensitive data such as passwords or API keys. Secrets can be defined and used in services without exposing them in the
docker-compose.ymlfile.
- Docker Compose supports the use of Docker secrets for managing sensitive data such as passwords or API keys. Secrets can be defined and used in services without exposing them in the
version: "3.1"
services:
db:
image: postgres:latest
secrets:
- db_password
secrets:
db_password:
file: ./db_password.txt
External Networks:
- Docker Compose allows you to connect services to external networks, facilitating communication between containers in different Compose projects or with services outside the Compose environment.
version: "3"
services:
web:
image: nginx:latest
networks:
- external_network
networks:
external_network:
external: true
Override Files:
- Docker Compose supports the use of override files to extend or modify an existing
docker-compose.ymlconfiguration. This is useful for defining environment-specific settings without modifying the main file.
- Docker Compose supports the use of override files to extend or modify an existing
$ docker-compose -f docker-compose.yml -f override.yml up
Volume Drivers:
- Docker Compose allows you to specify volume drivers for services, extending the functionality of volumes. This is particularly useful when using external storage systems or cloud-based volume drivers.
version: "3"
services:
app:
image: myapp:latest
volumes:
- myvolume:/app/data
- type: volume
source: myvolume
target: /app/data
volume:
nocopy: true
volumes:
myvolume:
driver: local
Extending Services:
- Docker Compose enables the extension of services from external files. This can be useful when splitting a large configuration into smaller, more manageable files.
version: "3"
services:
web:
extends:
file: common.yml
service: web
These features contribute to the flexibility, scalability, and ease of use of Docker Compose, making it a powerful tool for managing complex containerized applications in various scenarios.
Use Cases
Development Environments:
- Docker Compose is widely used in development environments to define and run applications with multiple interconnected services. It simplifies the process of setting up and tearing down development environments.
Testing and Continuous Integration:
- Docker Compose is valuable for testing scenarios where an application requires multiple services. It's often integrated into continuous integration pipelines to ensure consistent testing environments.
Local Deployment for Demonstrations:
- Developers often use Docker Compose to deploy a local version of an application with all its services for demonstrations or testing in an environment that closely mimics production.
Microservices Architectures:
- Docker Compose is a practical choice for deploying and managing microservices-based applications. Each microservice can be defined as a separate service in the
docker-compose.ymlfile.
- Docker Compose is a practical choice for deploying and managing microservices-based applications. Each microservice can be defined as a separate service in the
Prototyping:
- When prototyping new applications or services, Docker Compose provides a quick and efficient way to define and deploy the necessary components.
Distributed Applications:
- Applications with distributed components or databases benefit from Docker Compose's ability to define the entire application stack in a single file, making it easier to manage and deploy.
Multi-Container Workflows:
- For workflows that involve multiple containers working together, such as a frontend and a backend service, Docker Compose simplifies the orchestration and management of these containers.
Docker Compose Example File
$ cat docker-compose.yaml
---
version: '3'
services:
db:
image: mysql
container_name: mysql_db
restart: always
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD="secret"
web:
image: apache
build: .
container_name: apache_web
restart: always
ports:
- "8080:80"
---
version : "3.3"
services:
web:
image: djangoimage:latest
deploy:
replicas: 2
ports:
- "8001:8001"
volumes:
- djangovolume:/app
db:
image: mysql
ports:
- "3306:3306"
environment:
- "MYSQL_ROOT_PASSWORD=test@123"
volumes:
djangovolume:
external: true
docker-compose.yml is a configuration file used by Docker Compose to define and manage multi-container Docker applications. This YAML file allows you to specify various components of your application, such as services, networks, and volumes, along with their configurations and relationships.
Here's a breakdown of what you can define in a docker-compose.yml file:
Services: Each service represents a container in your application. You can specify the image to use, environment variables, ports, volumes, and other container-specific settings for each service.
Networks: You can define custom networks for your services to communicate with each other. This helps in isolating and securing communication between containers.
Volumes: Define persistent data storage locations (volumes) that can be mounted into containers. Volumes ensure that data is preserved even if containers are destroyed and recreated.
Environment Variables: Set environment variables for your services to configure their behavior.
Dependencies: You can define dependencies between services, ensuring that one service starts only after another service is up and running.
Scaling: You can specify the desired number of replicas for each service to scale horizontally based on demand.
Resource Limits: Configure resource limits for each service, such as CPU and memory limits.
By using a docker-compose.yml file, you can describe your entire application stack in a single document, making it easier to manage complex applications and their interdependencies. Docker Compose reads this configuration file and handles the orchestration of containers, networks, and volumes according to the defined specifications. This simplifies the deployment and management of multi-container applications while promoting consistency and reproducibility across different environments.
Docker-compose Commands
Building and Starting Services:
Build or rebuild services:
$ docker-compose build ## Build all services $ docker-compose build web ## Build single serviceStart services:
$ docker-compose upStart services in the background:
$ docker-compose up -d ## Create all containers $ docker-compose up -d web ## Create single containerBuild and start services in the background:
$ docker-compose up -d --buildStart specific service(s):
$ docker-compose up service_name
Stopping and Removing Services:
Stop services:
$ docker-compose downStop and remove containers, networks, and volumes:
$ docker-compose down --volumesStop and remove specific service(s):
$ docker-compose down service_name
Scaling Services:
Scale a service to a specified number of containers:
$ docker-compose up --scale service_name=3
Viewing Container Logs:
View logs for all services:
$ docker-compose logsView logs for a specific service:
$ docker-compose logs service_name
Checking Service Status:
Show the status of running services:
$ docker-compose ps
Executing Commands in Services:
Run a one-time command in a service:
$ docker-compose run service_name commandRun a command in a running service:
$ docker-compose exec service_name command
Inspecting Service Configuration:
Show the configuration of all services:
$ docker-compose configShow the configuration of a specific service:
$ docker-compose config service_name
Pausing and Unpausing Services:
Pause services:
$ docker-compose pauseUnpause services:
$ docker-compose unpause
Other Commands:
List services:
$ docker-compose psRun a command in a service:
$ docker-compose exec service_name commandView network details:
$ docker-compose network lsView volume details:
$docker-compose volume ls
Note: Docker Compose uses yaml file for its configuration so let's learn some basics about YAML
What is YAML?
YAML (YAML Ain't Markup Language or YAML Ain't a Markup Language) is a human-readable data serialization format. It is often used for configuration files and data exchange between languages with different data structures. YAML is designed to be easy to read and write, making it a popular choice for configuration files and data representation.
YAML files use a .yml or .yaml extension
Here are some key characteristics of YAML
Human-Readable:
- YAML is designed to be easy for humans to read and write. It uses indentation to represent the structure of data, making it visually clear and less cluttered than some other formats.
Whitespace-Sensitive:
- YAML relies on indentation to represent the structure of data. This indentation must be consistent within the same level of hierarchy. Spaces and tabs are both supported, but it's important to be consistent with the chosen method.
Data Types:
- YAML supports various data types, including scalars (strings, numbers, and booleans), sequences (arrays or lists), and mappings (key-value pairs or dictionaries). The data types can be nested to represent complex structures.
Comments:
- YAML supports comments using the
#symbol. Comments can be used to provide additional information or explanations within the file.
- YAML supports comments using the
Key-Value Pairs:
Key-value pairs are represented by a colon (
:), and the key and value are separated by whitespace. For example:key: value
Lists:
Lists or arrays are represented using dashes (
-) followed by a space. For example:- item1 - item2 - item3
Mappings:
Mappings or dictionaries are represented using key-value pairs with indentation. For example:
key1: value1 key2: value2
Anchors and Aliases:
YAML allows the use of anchors (
&) and aliases (*) to reference the same data in multiple places, reducing redundancy.first: &anchor_name This is the anchor second: *anchor_name
Multi-Line Strings:
Multi-line strings can be represented using the
>or|symbols. The>symbol folds the string, while the|symbol keeps the line breaks.multiline: > This is a multi-line string.
Document Separation:
- YAML documents can be separated using three dashes (
---). This allows multiple documents to be included in the same file.
- YAML documents can be separated using three dashes (
---
key1: value1
---
key2: value2
YAML is commonly used in configuration files for applications, Docker Compose files and other contexts where human readability and easy data interchange are important. While YAML is not a strict superset of JSON, it shares similarities with JSON, making it easy to convert between the two formats.
Docker Volumes
IDocker volume is a mechanism for persisting data generated by and used by Docker containers. Volumes are used to share data between containers, as well as to persist data even if the container is stopped or removed. Volumes provide a way to manage data in a Dockerized application by decoupling the data from the container itself.
Here are some key aspects of Docker volumes:
Volume Creation:
Volumes can be created explicitly using the
docker volume createcommand. For example:$ docker volume create myvolume
Volume Types:
Docker supports various types of volumes, including local volumes, named volumes, and anonymous volumes.
Local Volumes: Created and managed on the host machine. Paths on the host machine are mounted into the container.
$ docker run -v /path/on/host:/path/in/container myimageNamed Volumes: Explicitly created using
docker volume createand referenced by a user-defined name.$ docker volume create myvolume $ docker run -v myvolume:/path/in/container myimageAnonymous Volumes: Created and managed by Docker, and not explicitly named. They are typically used for temporary data.
$ docker run -v /path/in/container myimage
Volume Mounting:
Volumes are mounted into containers at specified paths using the
-vor--volumeoption when running a container.$ docker run -v myvolume:/path/in/container myimage
Data Persistence:
- Data stored in volumes persists even if the container is stopped or removed. This allows for decoupling data from the container's lifecycle.
Container-to-Container Communication:
- Volumes can be used to facilitate communication between containers by sharing a common data source.
Populating Volumes with Data:
- Volumes can be populated with initial data during volume creation or by using a separate container to copy data into the volume.
Inspecting Volumes:
The
docker volume inspectcommand allows you to view details about a specific volume, including its mount point on the host system.$ docker volume inspect myvolume
Removing Volumes:
Volumes can be removed using the
docker volume rmcommand. Before removing a volume, ensure that it is not in use by any containers.$ docker volume rm myvolume
Volume Drivers:
- Docker supports volume drivers, which allow volumes to be created and managed by external storage systems such as NFS, Amazon EBS, or third-party plugins.
Docker Compose and Volumes:
- Volumes are commonly used in Docker Compose files to define persistent storage for services. This ensures that data is retained across container restarts.
version: "3"
services:
app:
image: myimage
volumes:
- myvolume:/path/in/container
volumes:
myvolume:
Docker volumes play a crucial role in managing data in containerized applications, providing a flexible and persistent storage solution. They are essential for scenarios where data persistence, sharing, and decoupling from container lifecycles are important considerations.
Docker Networking
Docker networking enables communication between containers running on the same host or across different hosts. It provides an isolated network environment for containers, allowing them to communicate with each other or with external networks while maintaining security and isolation. Here are key concepts and features related to Docker networking:
Default Bridge Network:
- When Docker is installed, it creates a default bridge network called
bridge. Containers connected to this network can communicate with each other using container names as hostnames. However, this network is isolated from the host machine's network.
- When Docker is installed, it creates a default bridge network called
User-Defined Bridge Networks:
- Users can create custom bridge networks to facilitate communication between containers on the same host. Containers connected to the same user-defined bridge network can resolve each other's names as hostnames.
# Create a custom bridge network
$ docker network create mynetwork
# Run a container connected to the custom network
$ docker run --name container1 --network mynetwork -d myimage
# Run another container connected to the same network
$ docker run --name container2 --network mynetwork -d myimage
Host Networking:
- Containers can use the host network directly, bypassing Docker's network isolation. This means the container shares the network namespace with the host, and it has access to all host network interfaces.
# Run a container using the host network
$ docker run --name container1 --network host -d myimage
Overlay Networks (Swarm Mode):
- Docker Swarm, the built-in orchestration feature in Docker, supports overlay networks. Overlay networks enable communication between containers across multiple Docker hosts. This is essential for deploying and scaling services in a distributed environment.
# Create an overlay network in Swarm
$ docker network create --driver overlay myoverlay
# Deploy a service connected to the overlay network
$ docker service create --name myservice --network myoverlay myimage
Macvlan Networks:
- Macvlan networks allow containers to have their own MAC addresses and appear as physical devices on the network. This is useful for scenarios where containers need to be directly accessible from the physical network.
# Create a Macvlan network
$ docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 mymacvlan
# Run a container connected to the Macvlan network
$ docker run --name container1 --network mymacvlan -d myimage
Service Discovery and DNS:
- Docker provides automatic DNS resolution for containers connected to the same network. Containers can resolve each other's names using DNS. This feature simplifies service discovery within a Docker network.
These networking features allow Docker containers to communicate effectively, whether they are running on the same host or distributed across multiple hosts in a Swarm cluster. The choice of networking configuration depends on the specific requirements of your application and the desired level of isolation.
Docker Swarm
Docker Swarm is a clustering and orchestration solution built into Docker that allows you to create and manage a swarm of Docker nodes, turning them into a single, virtual Docker host. With Docker Swarm, you can deploy, manage, and scale containerized applications across a cluster of machines. Here are key concepts and features of Docker Swarm:
Node:
- A Docker node is an individual machine (physical or virtual) running the Docker daemon. Nodes are the building blocks of a Docker Swarm.
Swarm Mode:
- Docker Swarm operates in swarm mode, a built-in orchestration mode introduced in Docker 1.12. Swarm mode allows you to create and manage a swarm of Docker nodes.
# Initialize a Docker Swarm on a manager node
$ docker swarm init
Manager Node:
- In a Docker Swarm, there are one or more manager nodes that control the swarm and orchestrate the deployment of services. Manager nodes maintain the desired state of the swarm and handle tasks such as service updates and scaling.
# Add a manager node to the swarm
$ docker swarm join-token manager
Worker Node:
- Worker nodes are the worker machines that run containerized applications deployed by the swarm. They execute tasks delegated by the manager nodes.
# Add a worker node to the swarm
$ docker swarm join-token worker
Service:
- A service in Docker Swarm defines the desired state of a group of tasks. It represents a scalable, distributed application that can be easily deployed and managed.
# Deploy a service to the swarm
$ docker service create --name myservice myimage
$ docker service create --name my-web --replicas 3
--network mynetwork -p 80:80 nginx
Task:
- A task is a single instance of a service running on a node in the swarm. Tasks are the atomic units of work in Docker Swarm.
Scaling:
- Docker Swarm allows you to scale services up or down by adjusting the desired number of replicas. The swarm manager takes care of distributing tasks across the available nodes.
# Scale a service to 5 replicas
$ docker service scale myservice=5
Rolling Updates:
- Docker Swarm supports rolling updates for services, allowing you to update a service to a new version while maintaining application availability.
# Update a service with a new image
$ docker service update --image newimage myservice
Load Balancing:
- Swarm mode includes built-in load balancing for services. Requests to a service are automatically load-balanced across the available tasks.
Global Services:
- Global services run one task on each node in the swarm, ensuring that the service is available on every node.
# Deploy a global service
$ docker service create --name global-service --mode global myimage
Network Overlays:
- Swarm mode supports network overlays that span the entire swarm, allowing containers on different nodes to communicate seamlessly.
# Create an overlay network
$ docker network create --driver overlay mynetwork
Secrets Management:
- Docker Swarm provides a secure way to manage sensitive information such as passwords or API keys by supporting secrets.
# Create a secret
$ echo "mysecret" | docker secret create mysecret -
Visualizer:
- Docker Swarm Visualizer is a third-party tool that provides a web-based visualization of your Docker Swarm. It helps you understand the distribution of tasks across nodes.
# Deploy Swarm Visualizer
$ docker run -it -d -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock dockersamples/visualizer
In simple terms, Docker Swarm is a tool that allows you to easily manage and organize multiple containers by dividing the management into two parts, manager nodes, and worker nodes, where manager nodes make sure everything runs smoothly and worker nodes run the containers.
Docker Swarm provides a user-friendly and integrated solution for orchestrating containerized applications, with features designed to simplify the deployment, scaling, and management of applications in a clustered environment. Its native integration with the Docker platform makes it accessible to a wide range of users.
Docker Swarm Advantages
Docker Swarm offers several advantages for orchestrating and managing containerized applications in a clustered environment. Here are some key advantages of using Docker Swarm:
Ease of Use:
- Docker Swarm is integrated into the Docker platform, making it straightforward for users already familiar with Docker to adopt Swarm for orchestration. The commands and concepts are consistent with standard Docker commands.
Built-In Orchestration:
- Docker Swarm provides built-in orchestration capabilities for managing and scaling containerized applications. This eliminates the need for third-party orchestration tools in many cases.
Scalability:
- Docker Swarm enables the seamless scaling of applications by allowing you to increase or decrease the number of replicas (tasks) for a service. Scaling is achieved by simply updating the desired number of replicas.
High Availability:
- Docker Swarm ensures high availability by distributing tasks across multiple nodes in the swarm. In the event of a node failure, tasks are automatically rescheduled to healthy nodes.
Load Balancing:
- Swarm mode includes built-in load balancing for services, ensuring that requests to a service are distributed evenly across the available tasks. This simplifies the setup of load-balanced applications.
Rolling Updates:
- Docker Swarm supports rolling updates, allowing you to update a service to a new version without causing downtime. This is achieved by gradually updating tasks while maintaining the availability of the application.
Global Services:
- Docker Swarm allows the deployment of global services, which run one task on each node in the swarm. This ensures that the service is available on every node, making it suitable for certain types of applications.
Security:
- Swarm mode includes security features such as mutual TLS (Transport Layer Security) authentication between nodes. This ensures secure communication within the swarm.
Overlay Networks:
- Docker Swarm supports overlay networks that span the entire swarm. This allows containers on different nodes to communicate seamlessly, facilitating the creation of multi-service architectures.
Secrets Management:
- Swarm mode provides built-in support for managing sensitive information, such as passwords or API keys, using secrets. Secrets are securely stored and only made available to the necessary services.
Integration with Docker Compose:
- Docker Swarm integrates with Docker Compose, allowing users to define multi-container applications and deploy them to a Swarm cluster using a single
docker-compose.ymlfile.
- Docker Swarm integrates with Docker Compose, allowing users to define multi-container applications and deploy them to a Swarm cluster using a single
The Docker Underlying Technologies
The docker is written in go language and takes advantage of several features of the Linux kernel to deliver its functionality
Namespaces:
Linux namespaces are used to provide process isolation. Docker uses namespace technology to provide an isolated workspace called a container. When you run a container, docker creates a set of namespaces for that container. each aspect of a container runs in a separate namespace and its access is limited to that namespace.
Docker utilizes the following namespaces:-
PID (Process ID) - Process Isolation
NET (Networking) - Managing Network Interface
IPC (Inter-Process Communication) - Managing access to IPC Resources
UTS (UNIX Time-sharing System) - Isolating kernel & version
MNT(Mount) - Managing FileSystem Mount Points
Control Groups (cgroups):
- Control groups are a Linux kernel feature that Docker uses to limit and isolate resource usage (such as CPU, memory, and I/O) of containers. Cgroups ensure that containers do not consume more resources than allocated.
Union File Systems (UnionFS):
Union file systems operate by creating layers, making them very lightweight and fast. The Docker engine uses UnionFS to provide building blocks for containers.
Union file systems allow Docker to overlay multiple file systems into a single, unified view. This enables the creation of lightweight and efficient container images by layering changes on top of a base image.
Docker initially used AUFS (Another Union File System) as the default, but other storage drivers like OverlayFS, btrfs, vfs, and device mapper. overlay has become more common due to better performance and mainline kernel support.
Container Format:
- The Docker engine combines the namespace, cgroups & UnionFS into a wrapper called a container format. The default container format is libcontainer. In the future docker may support other container formats by integrating with technologies such as BSD jails or Solaris zones
Thank you for reading. I hope you will find this article helpful. if you like it please share it with others
Mohd Ishtikhar Khan : )

