Master Docker : How to Successfully Deploy a 3-Tier Application - Zero to Hero Guide (Part 3)

CJ writes
6 min readMay 30, 2024

--

Hi Amigos! Welcome to the 3rd blog in our Docker series. In this post, we’ll dive into how to create our own Docker image and run a container based on it. Let’s get started !

What is Dockerfile ?

The Dockerfile is a simple text file that contains the instructions needed to create a new container image. Once the Dockerfile is ready, we can easily build the image from it

Key Properties

FROM : Specifies the base image to use for the Docker image

FROM node:alpine 

RUN : Executes commands in the container during the image build process

RUN npm install

CMD : Provides the default command to run when a container is started from the image

CMD [ "java" , "-jar", "devops-proj.jar"]

ENTRYPOINT : Sets a default application to run every time a container is created from the image

ENTRYPOINT ["python3", "app.py"]

COPY : Copies files or directories from the host system into the image

COPY ./package.json /react-app

ADD : Similar to COPY, but with additional capabilities like extracting tar files and accessing remote URLs

ADD https://example.com/app.tar.gz /app/

WORKDIR : Sets the working directory for any subsequent RUN, CMD, ENTRYPOINT, COPY, and ADD instructions

WORKDIR /react-app

ENV : Sets environment variables

ENV NODE_ENV development

EXPOSE : Informs Docker that the container listens on the specified network ports at runtime

EXPOSE 3306

VOLUME : Creates a mount point with the specified path and marks it as holding externally mounted volumes from the host or other containers

VOLUME /data

ARG : Defines a variable that users can pass at build-time to the builder with the docker build command

ARG VERSION=1.0

Hands-on Demo

Let’s develop a three-tier application using React, Spring, and MySQL, leveraging Dockerfiles to containerize our project

Firstly, we will create a separate network and volume to run our application

# Bridge Network Creation
docker network create tier3-network
4182929d476d8e3db8961850633ecf9bdd5c2fb84bacb7f89827dbac8f52050c

# Named Volume Creation

docker volume create tier3-volume
tier3-volume

We will build our MySQL image and then proceed to run our MySQL container first. Our Dockerfile looks like below


# Use the official MySQL image as the base image
FROM mysql:8.0.34-debian

RUN mkdir /app

WORKDIR /app

# Set environment variables for MySQL (replace with your desired values)
ENV MYSQL_ROOT_PASSWORD=password*
ENV MYSQL_DATABASE=devops
ENV MYSQL_USER=username
ENV MYSQL_PASSWORD=password*

# Copy your custom database initialization script into the container

# Expose the MySQL port
EXPOSE 3306

# Start MySQL when the container runs
CMD ["mysqld"]

Building and running our docker image

docker build -t mysql-image . 

docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql-image latest fddac1f953eb 5 seconds ago 599MB

docker run -itd --name mysql --network tier3-network -v tier3-volume:/app sql

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
96ef6565100d sql "docker-entrypoint.s…" 9 minutes ago Up 9 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql

We will build a Docker image for Spring Boot and then run the container based on it

FROM openjdk:17-alpine

WORKDIR /appy

COPY devops-proj.jar .

EXPOSE 8080

CMD [ "java" , "-jar", "devops-proj.jar"]

Let’s build and run our spring container


docker build -t spring .

REPOSITORY TAG IMAGE ID CREATED SIZE
spring latest d8558192033e 16 minutes ago 382MB

docker run -itd --name backend --network tier3-network -v tier3-volume:/appy -e "spring.datasource.url=jdbc:mysql://mysql:3306/devops" -e "spring.datasource.username=root" -e "spring.datasource.password=password*" -e "cors.origin=*" -p 8082:8080 spring

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a8d13f6d62bf spring "java -jar devops-pr…" 6 minutes ago Up 6 minutes 0.0.0.0:8082->8080/tcp, :::8082->8080/tcp backend

Lastly, we will build a Docker image for the React app and then run the container based on it

# Fetching the latest node image on alpine linux
FROM node:alpine AS development

# Declaring env
ENV NODE_ENV development

# Setting up the work directory
WORKDIR /react-app

# Installing dependencies
COPY ./package.json /react-app

RUN npm install

# Copying all the files in our project
COPY . .

EXPOSE 3000

# Starting our application
CMD npm start

Let’s build and run our spring container

docker build -t reacty .

REPOSITORY TAG IMAGE ID CREATED SIZE
reacty latest 8017a5c05ef9 14 minutes ago 961MB

docker run -itd --name reacty --network tier3-network -v tier3-volume:/app -p 5000:3000 -e "REACT_APP_SPRING_BOOT_URL=http://3.109.58.102:8082" reacty

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
08f0f62461ca reacty "docker-entrypoint.s…" 3 minutes ago Up 3 minutes 0.0.0.0:5000->3000/tcp, :::5000->3000/tcp reacty

Also, I will share ideas to minimize the image size in upcoming blogs

I’m using the Spring container’s IP address instead of its name to avoid browser network errors. When using port forwarding for your frontend UI, it can’t resolve the backend by name due to different networking setups between your laptop and Docker

A simple fix is to expose the backend with port forwarding too, and then use the port forwarding IP and port from the frontend

So, for now, go with this setup for testing the app. In Kubernetes, we will use different solutions

Now, all our containers MySQL, Spring, and React are up and running. We’ll test it out now

Home Page :

Adding new user :

User added successfully :

Edit the user :

Deleting the user :

Yay! We’ve successfully deployed our 3-tier application in the Docker environment !

Don’t forget to push the images to the registry for later use. For now, I am using the Docker Hub registry. Create an account here DockerHub

docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sql latest 1bf44112ef26 About an hour ago 599MB

# Adding a Tag to our image

docker tag sql userid/sql:v1
root@ip-172-31-15-211:~/springboot-react-Devops# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sql latest 1bf44112ef26 About an hour ago 599MB
userid/sql v1 1bf44112ef26 About an hour ago 599MB

# Don't forgot to remove the older image

docker rmi sql
Untagged: sql:latest

# Docker login

docker login
Log in with your Docker ID or email address to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com/ to create one.

Username: user_id
Password:

Login Succeeded

# Pushing the image to Docker hub

docker push user_id/sql:v1
The push refers to repository [docker.io/user_id/sql]
5f70bf18a086: Pushed
5acdc30643cf: Pushed
4b34c7838eb3: Mounted from library/mysql
e471534b9150: Mounted from library/mysql
v1: digest: sha256:175fe67441b0e0b108c6176b0c003bc1cb65d228c4632b6065ea822c6da40e3f size: 3241

# Docker prune

docker system prune # Removes all unused data, including stopped containers, networks not used by any containers, dangling images, and unused volumes

# Alternatively, you can clean up a specific resource by using "docker resource prune" such as "docker volume prune"

That’s a wrap! Thanks for reading. Loads of love to you and your family ❤️

--

--

CJ writes

Tech explorer passionate about #DevOps, ☁️ #Cloud, 🤖 #AI. Join me as we decode tech trends and discuss global incidents! 🌐