Below is the model we will deploy

Build Stage:
Deploy Stage:
Go to Docker Hub Container Image Library | App Containerization and create a new account.

After creating an account, you will see the main interface

If you logged in using Google or GitHub and haven’t created a password yet, go to My profile

Select Edit profile

And choose reset password, you will receive an email to change your password, click the link to change your password

After obtaining a password, log in to Docker on the Build Instance using your username and password
docker login
Your password will be stored at /home/gitlab-runner/.docker/config.json

We have successfully logged in on the Build Instance. Now let’s move to the Deploy Instance

Logged in successfully!
Previously in /root/ecommerce-fullstack-netcore-react, we had the frontend and backend projects

We will test the Dockerfile to start both the frontend and backend projects
First, we will write the Dockerfile in the backend project
cd /root/projects/backend && vi Dockerfile
Enter the following content
FROM mcr.microsoft.com/dotnet/sdk:6.0
WORKDIR /app
COPY . .
RUN dotnet restore
CMD ["dotnet", "run", "--urls", "http://0.0.0.0:5214"]
Before running the Docker file, check if port 5214 is running.
netstat -tlpun
We see that port 5214 is running with PID 70345

Stop the process by running
kill -9 70345
Please replace 70345 with your backend’s PID

We run the backend container project using the following command
Docker will build the Dockerfile into a Docker image with the tag ecommerce-backend:v1
docker build -t ecommerce-backend:v1 .
We have successfully built the Docker image

Next, we will run Docker with the name backend, with the option to run in the background with internal and external ports set to 5214 from the ecommerce-backend:v1 image
docker run --name backend -dp 5214:5214 ecommerce-backend:v1
Successfully ran

We can also use logs to check what exactly is running in the backend
docker logs -f id_container

First, we will write the Dockerfile in the frontend project
cd /root/projects/frontend && vi Dockerfile
Enter the following content
## build ##
FROM node:18-alpine as build
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
## run ##
FROM nginx:alpine
COPY --from=build /app/build/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Similar to the backend, before running the Docker file, check if port 3000 is running.
netstat -tlpun
We see that port 3000 is running with PID 263764

Stop the process by stopping pm2 because we had run CI/CD in previous lessons and started pm2. If you use kill -9 PID as usual, pm2 will still restart the project unless you use the following command.
pm2 stop all

We run the backend container project using the following command
Docker will build the Dockerfile into a Docker image with the tag ecommerce-frontend:v1
docker build -t ecommerce-frontend:v1 .
We have successfully built the Docker image

Next, we will run Docker with the name frontend with the option to run in the background with internal and external ports set to 3000 from the ecommerce-frontend:v1 image
docker run --name frontend -dp 3000:80 ecommerce-frontend:v1
The execution was successful


To save time, I will simulate it on the deploy Instance because it already has the source code. To push an image to the registry, you will push the image in the following format:
domain/project/repo:tag
But since we are pushing to Docker Hub, the format will be:
user_name/repo:tag
Let’s tag the frontend image from an existing image first.
Enter the following command:
docker tag ecommerce-frontend:v1 tunhatphuong/ecommerce-frontend:v1
Image created successfully

Let’s push this image to Docker Hub:
docker push tunhatphuong/ecommerce-frontend:v1

In the Docker Hub repository, we can see a new repo that we just pushed.

We will stop the frontend container to test pulling the image from Docker Hub:
docker ps
docker stop container_id
docker rm container_id
docker images
docker rmi your_name/ecommerce-frontend:v1
docker rmi ecommerce-frontend:v1
We have removed all containers and images of the frontend.

Let’s start the frontend project, even though there is no image.
docker run --name frontend -dp 3000:80 tunhatphuong/ecommerce-frontend:v1
When Docker cannot find the image locally, it will pull it from Docker Hub (assuming you are logged in with docker login).

Before proceeding, we will grant permissions to the gitlab-runner on the Build Instance:
visudo
Add a new line:
gitlab-runner ALL=(ALL) NOPASSWD:ALL

Go to the frontend project → Build → Pipelines Editor

Access the editor in the main branch.

Enter the following lines:
variables:
USER_PROJECT: "ecommerce"
PATH_PROJECT: "/home/${USER_PROJECT}/${CI_PROJECT_NAME}"
IMAGE_VERSION: "${CI_REGISTRY_USER}/${CI_PROJECT_NAME}-${USER_PROJECT}:${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHORT_SHA}"
stages:
- build
- push_container
- deploy
before_script:
- sudo mkdir -p $PATH_PROJECT
build:
stage: build
variables:
GIT_STRATEGY: clone
before_script:
- sudo docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PWD
script:
- sudo docker build -t $IMAGE_VERSION .
after_script:
- sudo docker logout
tags:
- group-ecommerce-shell-runner-build
only:
- tags
push_container:
stage: push_container
variables:
GIT_STRATEGY: none
before_script:
- sudo docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PWD
script:
- sudo docker push $IMAGE_VERSION
after_script:
- sudo docker logout
tags:
- group-ecommerce-shell-runner-build
only:
- tags
deploy:
stage: deploy
variables:
GIT_STRATEGY: none
before_script:
- sudo docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PWD
script:
- sudo docker pull $IMAGE_VERSION
- sudo su ${USER_PROJECT} -c "
container_exists=\$(sudo docker ps -a -q -f name=${CI_PROJECT_NAME});
if [ ! -z \"\$container_exists\" ]; then
sudo docker rm -f ${CI_PROJECT_NAME};
fi;
sudo docker run --name ${CI_PROJECT_NAME} -dp ${FRONTEND_PORT}:80 ${IMAGE_VERSION}"
after_script:
- sudo docker logout
tags:
- group-ecommerce-shell-runner
only:
- tags
Variables
"ecommerce". This is a custom variable to reference the project name.USER_PROJECT with the project name from GitLab CI variables to create a directory path for the project.USER/PROJECT-USER:COMMIT_REF_NAME_COMMIT_SHORT_SHA. Example: tunhatphuong/frontend-ecommerce:frontend_v1.0.1_abcd123.Stages
Jobs
build
buildGIT_STRATEGY: clone ensures the repository will be fully cloned for each build.IMAGE_VERSION tag.group-ecommerce-shell-runner-build runner to execute this job.push_container
push_containerGIT_STRATEGY: none does not require cloning the repository for this stage.IMAGE_VERSION tag to the Docker registry.group-ecommerce-shell-runner-build.deploy
deployGIT_STRATEGY: none does not require cloning the repository for this stage.IMAGE_VERSION tag from the registry.CI_PROJECT_NAME already exists. If so, it removes that container.CI_PROJECT_NAME, mapping port FRONTEND_PORT to port 80 of the container, and using the Docker image just pulled.group-ecommerce-shell-runner.🡇 Content 🡇
System Variables:
CI_PROJECT_NAME: The name of the GitLab project.CI_COMMIT_REF_NAME: The name of the current branch or tag.CI_COMMIT_SHORT_SHA: The short hash of the current commit.Custom Variables:
CI_REGISTRY_USER: The username or organization in Docker registry.CI_REGISTRY_PWD: The password for Docker registry (this is also a system variable but might need configuration through GitLab).FRONTEND_PORT : The port for running the frontend.🡅 Content 🡅
Perform commit changes

With the custom variables, we will add them in the ecommerce group.
Navigate to group ecommerce → Settings → CI/CD

Select Expand → Add Variable

Add the following variables:
CI_REGISTRY_USER: Docker Hub usernameCI_REGISTRY_PWD: Docker Hub passwordFRONTEND_PORT : 3000
Final result

Add Dockerfile to the main branch

Add Dockerfile and commit

Create Tags in frontend

Check the results, all jobs passed

Check Docker Hub, a repo tunhatphuong/frontend-ecommerce with tag ecommerce-fe-v1.0.2_dd6e2d25 has been pushed from the Build Instance

At the Deploy Instance, when checking, there is a running container with the name frontend and image tunhatphuong/frontend-ecommerce:ecommerce-fe-v1.0.2_dd6e2d25

Don’t worry about the second pipeline run; even though port 3000 is already in use, the script checks, finds, and removes a container named frontend if it is running!
Navigate to the backend project → Build → Pipelines Editor

Go to the editor in the main branch

Enter the following:
variables:
USER_PROJECT: "ecommerce"
PATH_PROJECT: "/home/${USER_PROJECT}/${CI_PROJECT_NAME}"
IMAGE_VERSION: "${CI_REGISTRY_USER}/${CI_PROJECT_NAME}-${USER_PROJECT}:${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHORT_SHA}"
stages:
- build
- push_container
- deploy
before_script:
- sudo mkdir -p $PATH_PROJECT
build:
stage: build
variables:
GIT_STRATEGY: clone
before_script:
- sudo docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PWD
script:
- sudo docker build -t $IMAGE_VERSION .
after_script:
- sudo docker logout
tags:
- group-ecommerce-shell-runner-build
only:
- tags
push_container:
stage: push_container
variables:
GIT_STRATEGY: none
before_script:
- sudo docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PWD
script:
- sudo docker push $IMAGE_VERSION
after_script:
- sudo docker logout
tags:
- group-ecommerce-shell-runner-build
only:
- tags
deploy:
stage: deploy
variables:
GIT_STRATEGY: none
before_script:
- sudo docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PWD
script:
- sudo docker pull $IMAGE_VERSION
- sudo su ${USER_PROJECT} -c "
container_exists=\$(sudo docker ps -a -q -f name=${CI_PROJECT_NAME});
if [ ! -z \"\$container_exists\" ]; then
sudo docker rm -f ${CI_PROJECT_NAME};
fi;
sudo docker run --name ${CI_PROJECT_NAME} -dp ${BACKEND_PORT}:${BACKEND_PORT} ${IMAGE_VERSION}"
after_script:
- sudo docker logout
tags:
- ecommerce-deploy-runner
only:
- tags
Perform commit changes

With the custom variables, add them in the ecommerce group.
Add the variable BACKEND_PORT with the value 3000

Add Dockerfile to the main branch and commit

Create Tags in backend, check if all jobs passed

Check Docker Hub, a repo tunhatphuong/backend-ecommerce with tag ecommerce-be-v1.0.2_934f8c8d has been pushed from the Build Instance

At the Deploy Instance, when checking, there is a running container with the name backend and image tunhatphuong/backend-ecommerce:ecommerce-be-v1.0.2_934f8c8d
