TP 2 - Dockerizing A 3-Tier Web Application
TP 2 - Dockerizing A 3-Tier Web Application
Introduction
This lab is designed to provide you with a clear understanding and practical skills in using
Docker to containerize a web application. We will focus on a 3-tier architecture, consisting of
a React.js frontend, Django backend, and a MySQL database. By the end of this lab, you will
have gained hands-on experience in:
Requirements
Step-by-Step Instructions
● Windows:
1. Download Docker Desktop from the official Docker website :
https://docs.docker.com/desktop/install/windows-install/
2. Make sure to enable the WSL 2 feature on Windows and set it up as the
backend for Docker.
3. Follow the provided instructions during installation.
● Linux:
1. Update your package manager: sudo apt-get update
2. Install Docker: sudo apt install docker.io
3. Start Docker: sudo systemctl start docker , and enable it at boot:
sudo systemctl enable docker
This will install Docker directly from the default Ubuntu repositories. After the
installation, you can verify Docker is working with: sudo docker --version
4. Add your user to the Docker group to run commands without sudo: sudo
usermod -aG docker $USER. (You will need to log out and back in for this
to take effect).
And to test Docker, you can run: sudo docker run hello-world
Git Installation
Python Installation
Node.js Installation
Inside the repository, you only need the folder called enis-app-tp, which contains both the
frontend and backend directories.
You can either manually extract the folder or use the terminal commands to navigate inside
the required folder.
This will install all required npm packages and start the frontend development server.
1. Start XAMPP:
○ Launch XAMPP and start Apache and MySQL.
2. Create a Database:
○ Open your browser and go to http://localhost/phpmyadmin.
○ Create a new database named enis_tp:
■ In the left panel, click on New.
■ Enter the database name as enis_tp and click Create.
'default': {
'ENGINE': 'django.db.backends.mysql',
Replace 'your_username' with your MySQL username (typically root for XAMPP).
After configuring the database in the settings.py file, the next steps are to create the
database tables using Django's makemigrations and migrate commands. Here’s how
you can proceed:
1. Run makemigrations:
First, you need to create new migrations based on any changes in your Django
models: python3 manage.py makemigrations
This command checks for any changes in the models and prepares them for
migration.
2. Run migrate:
Now, apply the migrations to create or modify the database tables: python3
manage.py migrate
This command will apply the migration files and create the necessary tables in the
MySQL database (enis_tp).
Once the database setup is complete and the tables are visible:
● You can now test the application by interacting with it from the frontend by logging
with the username and password , then create a note
Step 3 : containerize the frontend
WORKDIR /app
COPY package*.json ./
COPY . .
FROM nginx:alpine
EXPOSE 80
Now that you have your Dockerfile for the frontend ready, let's go through the steps to:
Explanation:
● Explanation: The -d flag stands for detached mode. This means that the
Docker container will run in the background, and you won’t see the logs or
output directly in your terminal.
● Why Use It: When you run a container in detached mode, you can continue
to use your terminal for other commands, and the container will keep running
in the background. This is useful for long-running services like web servers,
databases, or backend applications.
● Explanation: The -p flag is used for port mapping. It maps a port on your
host machine (your computer) to a port on the Docker container.
The format for the -p flag is: -p <host_port>:<container_port>
○ Host Port (left side): The port on your local machine that you want to use to
access the application.
○ Container Port (right side): The port inside the Docker container where the
application is listening.
3. Visit the application:
a. Open your browser and go to http://localhost:81
FROM python:3.10-buster
WORKDIR /app
COPY requirements.txt ./
COPY . .
EXPOSE 8000
1. FROM python:3.10-buster
○ Purpose: This sets the base image for the container.
python:3.10-buster is a version of the official Python image that is
based on Debian Buster, a stable and full-featured Linux distribution.
○ Why Python 3.10: Python 3.10 is a more recent version of Python that
includes important new features and performance improvements.
Django, being a Python web framework, requires a Python runtime, and
Python 3.10 ensures compatibility with modern libraries and Django
features.
2. WORKDIR /app
○ Purpose: This command sets the working directory to /app inside the
container. All subsequent commands will be executed within this
directory.
○ Why Set a Working Directory: It keeps the file structure organized inside
the container.
3. COPY requirements.txt ./
○ Purpose: This copies the requirements.txt file (which lists the
dependencies) from your local machine into the container’s working
directory.
○ Why Only Copy requirements.txt First: By copying the
requirements.txt file separately, Docker can cache the installed
dependencies and avoid re-installing them unless the
requirements.txt file changes.
4. RUN pip install --no-cache-dir -r requirements.txt
○ Purpose: This command installs all Python dependencies listed in
requirements.txt.
Why pip install: Django and its dependencies need to be installed
○
inside the container for the application to run.
○ Why Use --no-cache-dir: This option prevents pip from storing
unnecessary cache, which reduces the size of the final Docker image.
5. COPY . .
○ Purpose: This copies the rest of your Django application’s code into the
container. This includes your Python files, Django settings, static files,
etc.
○ Why Copy All Code: The entire application code is needed to run the
Django app.
6. EXPOSE 8000
○ Purpose: This tells Docker that the container will listen on port 8000,
which is Django’s default port when using the development server.
○ Why Expose the Port: This allows you to access the application through
port 8000 on your machine or the mapped port you define when running
the container.
7. CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
○ Purpose: This command tells Docker to start the Django development
server. The runserver command will start the server and bind it to all
network interfaces (0.0.0.0), making it accessible externally via port
8000.
○ Why Use runserver: This command starts the Django application in
development mode, which is useful for local testing. For production,
you’d use something like Gunicorn instead.
Now that you have your Dockerfile for the backend ready, let's go through the steps to:
The error occurred because Django was trying to connect to MySQL locally (on the same
container) using the Unix socket, but there was no MySQL server running in that container.
Docker containers are isolated environments, so Django cannot automatically see other
services like MySQL unless you specifically connect the containers via a network and update
your settings.py to point to the MySQL container.
you have to :
1. Pull MySQL Docker Image: In your terminal, run the following command to pull the
official MySQL image: docker pull mysql:8.0
This will pull MySQL 8.0, which is a stable version compatible with Django and
mysqlclient.
To check the created database inside the container and view the containers in your Docker
network, follow these steps:
To access the MySQL database inside the mysql-db container, you can connect to the
MySQL instance running inside the container.
mysql -u root -p
3. Check the existing databases:
Once logged into MySQL, you can list the databases with the following SQL
command: SHOW DATABASES;
1. Open settings.py:
Locate the DATABASES configuration in your settings.py file and modify it to
match the port exposed by the MySQL container:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'PASSWORD': 'yourpassword', # The MySQL root password you set when starting the
MySQL container
Explanation:
● 'HOST': 'mysql-db': This is the container name for MySQL (mysql-db). Docker
will resolve this to the correct IP address inside the Docker network.
● 'PORT': '3307': The port number 3307 matches the port that was exposed by the
MySQL container on the host (as shown by your docker ps command).
Once the settings.py file is updated, rebuild your backend Docker image to ensure the
updated configuration is used.
Now that you've rebuilt the backend, you need to run the backend container inside the same
Docker network (my_bridge) as the MySQL container to enable communication.
Enter the backend-app container to check if the Django app can connect to the MySQL
database : docker exec -it backend-app ping mysql-db
After running the backend container, enter the container to apply the migrations and create a
superuser.
docker ps
Run Migrations:
Inside the container, run the following commands to apply the Django database migrations:
Create a Superuser:
We’ll modify the API configuration in the frontend code to point to the backend container by
its Docker container name (instead of localhost), rebuild the frontend, and run the
container in the same network as the backend and database.
Explanation:
● Port Mapping: Ensure the backend container's port is mapped to a port on the host
machine. This allows external access (from the browser) to the backend via the host
machine's IP address or localhost (if accessing locally).
● Update Axios Configuration for Browser Access: The frontend's axios
configuration should point to the host's address that is accessible from the browser.
This is typically localhost with the mapped port if you’re working on your local
machine.
{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsIm
V4cCI6MTcyNjI1NTU1NCwiaWF0IjoxNzI2MTY5MTU0LCJqdGkiOiIwNjU4ZTgzMjA0NGY0N
TE4YTc5MTYwZWMyOTlhMjNhYyIsInVzZXJfaWQiOjF9.Ctu0Jq7yvAsPquyBjzvqk9AadlGxr
SmmHm3XAzTL8X0","access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlI
joiYWNjZXNzIiwiZXhwIjoxNzI2MTcwOTU0LCJpYXQiOjE3MjYxNjkxNTQsImp0aSI6IjJhMTN
mMzVkZjE1YTQ0N2ZhMGM2YTNlNDBiNzJmMGNlIiwidXNlcl9pZCI6MX0.GAqbtXgej1S4v9
FsPZRzP3F1RuW_4Xh7cWAW_4tEt4w"}
Since we’ve made changes to the frontend source code, you need to rebuild the Docker
image.
If you have a previous frontend container running, stop and remove it before starting
a new one.
Now, run the newly built frontend container and make sure it is connected to the same
my_bridge network as the backend and MySQL containers.
Now when you inspect the network you should see all containers are connected
let’s check the connectivity : http://localhost:81/login and try to connect with the
created user , once log in , Congratulations it is all set up and all containers are
connecting to each other , enjoy creating your notes
To ensure the MySQL database data is persistent, you need to inspect the container
mounts and keep the volume linked to the MySQL database.
Here is the full docker-compose.yml file that uses the pre-built images and attaches the
MySQL database to its preserved volume:
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: Imen_Chaari2023
MYSQL_DATABASE: enis_tp
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql # Use the preserved volume to keep data
networks:
- my_bridge
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 10s
retries: 5
backend-app:
image: backend-app:latest # Use the already built image
container_name: backend-app
restart: always
ports:
- "8001:8000"
depends_on:
mysql:
condition: service_healthy
networks:
- my_bridge
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8000/admin/login/?next=/admin/ || exit 1"]
interval: 30s
timeout: 10s
retries: 5
frontend-app:
image: frontend-app:latest # Use the already built image
container_name: frontend-app
restart: always
ports:
- "81:80"
depends_on:
mysql:
condition: service_healthy
backend-app:
condition: service_healthy
networks:
- my_bridge
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]
interval: 30s
timeout: 10s
retries: 3
networks:
my_bridge:
external: true
volumes:
mysql_data:
external:
name: d211ff04cd14c527f6883cbed641a6ee85c3f54c5638ab7544cd1577c8989074
Explanation of docker-compose.yml
● MySQL Service: The mysql service uses the pre-built MySQL 8.0 image, connects
it to the preserved mysql_data volume, and exposes it on port 3307. It includes a
health check to ensure MySQL is up before starting dependent services.
● Backend Service: The backend-app uses the pre-built backend image, starts after
MySQL is healthy, and runs on port 8001. The backend also has a health check to
ensure it is up before starting the frontend.
● Frontend Service: The frontend-app uses the pre-built frontend image, starts
after both MySQL and the backend are healthy, and is exposed on port 81.
● Health Checks: These are critical to ensure that dependent services wait until their
dependencies (like MySQL and backend) are up and running before they start.
● Networks: The my_bridge network ensures that all containers can communicate
with each other.
● Volumes: The mysql_data volume ensures that the MySQL data is stored outside
the container, providing persistence across container restarts or recreations.
Bring up all services with Docker Compose: Use the following command to bring up all
services (MySQL, backend, frontend) at once and ensure they are connected to the same
network:
docker-compose up -d
● Start the MySQL container, ensuring the data is preserved in the mysql_data
volume.
● Start the backend and frontend containers, ensuring they depend on the health of the
MySQL container.
Try to connect with the credentials you created. If the login works, congratulations! All
containers are up and running, connected through the same network.
Bring down all containers: If you want to stop all containers and bring them down, you can
simply run: docker-compose down
1. Docker Hub Account: Ensure you have a Docker Hub account. If you don’t have
one, you can create it at https://hub.docker.com/.
2. Docker Login: Log into Docker Hub from your terminal. Run: docker login
1. Tag Your Images
Each image must be tagged to associate it with your Docker Hub account. The naming
convention is:<docker-hub-username>/<image-name>:<tag>
For example, if your Docker Hub username is imenchaari and the image is
backend-app, you can tag it like this:
Once the images are tagged, you can push them to Docker Hub.
After pushing the images, go to https://hub.docker.com/ and log in. Navigate to your
Repositories tab, and you should see the backend-app and frontend-app images listed.
Conclusion
You have now successfully pushed your images to Docker Hub, making them available to
pull and run from anywhere. This is a crucial step in deploying your Dockerized applications
to any environment.