
Thank you for reading, keep learning and keep growing.
Reactions
No comments yet.
Imagine you just finished a web application with Python 3.10 and PostgreSQL 14. Everything runs smoothly on your laptop. But once moved to the production server... boom! Errors appear everywhere.
It turns out the server still uses Python 3.6 and PostgreSQL 10. You're stuck tweaking configurations just to make the server environment match your local setup. And the classic complaint emerges:
"But it works on my laptop! 😅"
This isn't a new problem. Environment inconsistencies like this have become daily drama for developers. Not to mention when one server is forced to run various applications with different versions and requirements. Dependency conflicts become unavoidable.
And this is where Docker emerges as a solution.
Containerization is a method for packaging applications along with all their dependencies into a single unit called a container. Unlike Virtual Machines (VMs) that require separate operating systems for each instance, containers simply share the kernel from the host OS, making them lighter, faster, and more efficient.
Container:
Virtual Machine (VM):
Docker is a popular platform for creating, running, and managing containers. With Docker, you can ensure that applications working locally will run the same way on any server without needing to reconfigure the environment from scratch.
The workflow in Docker follows this sequence: Dockerfile → Image →
Container.
Dockerfile: A text file containing instructions for building an image. Like a recipe or source code.
Docker Image: The result of a Dockerfile. Read-only in nature, contains everything the application needs. Can be considered as a blueprint.
Docker Container: The runtime version of an image. This is where the application actually runs. One image can be used to create many containers.
Docker has a modular and efficient architecture, enabling isolated and consistent container management. Here are its main components:
The diagram above illustrates Docker's basic architecture and the workflow
between main components: Client, Docker Host, and Docker Registry.
Based on the architecture above, let's study Docker components in more detail, including additional components that support the Docker ecosystem:
Docker Engine is the core of the Docker platform running inside the Docker Host (physical or virtual machine). Its main components include:
dockerd): Background process that manages images,
containers, networks, and volumesdocker CLI): Command interface for users like docker run,
docker build, etc.Docker Image is a read-only blueprint containing all components needed to run an application, including code, dependencies, and instructions from Dockerfile.
docker build commandDocker Container is a runtime instance of an image. Containers run applications lightly and in isolation, while still sharing the kernel with the host system.
Docker Registry is a service for storing and distributing Docker images.
docker pull and docker push commandsDocker Networking manages communication between containers and with external networks.
Docker Volume is used to store data persistently, even when containers are deleted.
Docker Compose is a tool for organizing and running multi-container applications easily.
docker-compose.yml file to define services, networks, volumes, and
other dependencies declarativelyDocker can be installed on almost all major operating systems:
Windows & macOS: Use Docker Desktop which provides GUI and CLI
Linux: Install Docker Engine through official repositories according to each distribution
Here are basic commands for managing Docker images and Docker Containers.
Docker Image
# View Image List
docker image ls
# Download Image from Registry
docker image pull <image_name>:<tag>
# Delete Image
docker rmi <image># View Image List
docker image ls
# Download Image from Registry
docker image pull <image_name>:<tag>
# Delete Image
docker rmi <image>Docker Container
# Create Container
docker container create --name <container_name> <image_name>:<tag>
# Run Container
docker container start <container_name>
# Stop Container
docker container stop <container_name>
# Delete Container (Make sure container is stopped)
docker container rm <container_name>
# View Container List
docker container ls # active ones
docker container ls -a # all (including stopped)
# View Logs
docker container logs <container_name>
docker container logs -f <container_name> # real-time
# Enter Container (exec)
docker container exec -it <container_name> bash
# Port Forwarding (forward port from container to host)
docker container create --name web --publish <host_port>:<container_port> <image_name>:<tag>
# Environment Variable
docker container create --name <container_name> \
--publish <host_port>:<container_port> \
--env <VAR1>=<value1> \
--env <VAR2>=<value2> \
<image_name>:<tag>
# View Resource Statistics
docker container stats
# Limit Resources
docker container create --name <container_name> \
--memory <memory_amount> \
--cpus <cpu_amount> \
<image_name>:<tag># Create Container
docker container create --name <container_name> <image_name>:<tag>
# Run Container
docker container start <container_name>
# Stop Container
docker container stop <container_name>
# Delete Container (Make sure container is stopped)
docker container rm <container_name>
# View Container List
docker container ls # active ones
docker container ls -a # all (including stopped)
# View Logs
docker container logs <container_name>
docker container logs -f <container_name> # real-time
# Enter Container (exec)
docker container exec -it <container_name> bash
# Port Forwarding (forward port from container to host)
docker container create --name web --publish <host_port>:<container_port> <image_name>:<tag>
# Environment Variable
docker container create --name <container_name> \
--publish <host_port>:<container_port> \
--env <VAR1>=<value1> \
--env <VAR2>=<value2> \
<image_name>:<tag>
# View Resource Statistics
docker container stats
# Limit Resources
docker container create --name <container_name> \
--memory <memory_amount> \
--cpus <cpu_amount> \
<image_name>:<tag>Inspection and Cleanup (Inspect & Prune)
# View object details in JSON format
docker inspect <object_name>
# Remove stopped containers
docker container prune
# Remove unused images
docker image prune
# Remove all unused resources
docker system prune# View object details in JSON format
docker inspect <object_name>
# Remove stopped containers
docker container prune
# Remove unused images
docker image prune
# Remove all unused resources
docker system pruneDockerfile is a script containing sequential instructions for building a Docker image. With Dockerfile, we can automatically and consistently package applications along with all their dependencies into a container.
Basic Dockerfile Template
# Use base image
FROM python:3.12
# Set working directory
WORKDIR /app
# Copy & install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy entire application files
COPY . .
# Expose port 8000
EXPOSE 8000
# Default command when container is run
CMD ["python", "app.py"]# Use base image
FROM python:3.12
# Set working directory
WORKDIR /app
# Copy & install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy entire application files
COPY . .
# Expose port 8000
EXPOSE 8000
# Default command when container is run
CMD ["python", "app.py"]Important Dockerfile Instructions
| Instruction | Function |
|---|---|
FROM | Specify base image |
WORKDIR | Working directory in container |
COPY | Copy files from host to image |
RUN | Execute command during build |
EXPOSE | Open application port |
CMD | Default command when container is run |
Build & Run
# Build image from Dockerfile
docker build -t <image_name> .
# Run container from image
docker run -d --name <container_name> -p <host_port>:<container_port> <image_name>
# View container logs
docker logs <container_name># Build image from Dockerfile
docker build -t <image_name> .
# Run container from image
docker run -d --name <container_name> -p <host_port>:<container_port> <image_name>
# View container logs
docker logs <container_name>.dockerignoreThe .dockerignore file is used to exclude unnecessary files when building
images, thereby improving security, reducing image size, and speeding up build
time.
How to Use
.dockerignore in the project root directory (same
location as Dockerfile).gitignore, using # for comments and ! for exceptionsExample .dockerignore content
# Git & dependencies
.git
node_modules/
__pycache__/
# Log files and environment
*.log
.env
# Operating system & IDE files
.DS_Store
.vscode/
# Docker files themselves
Dockerfile
.dockerignore
docker-compose.yml# Git & dependencies
.git
node_modules/
__pycache__/
# Log files and environment
*.log
.env
# Operating system & IDE files
.DS_Store
.vscode/
# Docker files themselves
Dockerfile
.dockerignore
docker-compose.ymlDocker Networking allows containers to communicate with each other and connect to external networks. By default, containers are isolated and can only communicate if they're on the same network.
Types of Networks in Docker
1. Bridge (Default): Docker's default virtual network for communication between containers in the same network.
# Create bridge network
docker network create --driver bridge <network_name>
# Run container and connect directly to network
docker run -d --name <container1> --network <network_name> <image1>
docker run -d --name <container2> --network <network_name> <image2># Create bridge network
docker network create --driver bridge <network_name>
# Run container and connect directly to network
docker run -d --name <container1> --network <network_name> <image1>
docker run -d --name <container2> --network <network_name> <image2>2. Host: Container shares network directly with host (Linux only).
# Create host network (Linux)
docker run --rm --network host <image_name># Create host network (Linux)
docker run --rm --network host <image_name>3. None: Container will have no network access at all.
# Create none network
docker run --rm --network none <image_name># Create none network
docker run --rm --network none <image_name>Basic Docker Networking Commands
# View network list
docker network ls
# Create network
docker network create <network_name>
# Delete network
docker network rm <network_name># View network list
docker network ls
# Create network
docker network create <network_name>
# Delete network
docker network rm <network_name>Container Management in Network
# Connect container when created
docker container create --name <container_name> \
--network <network_name> \
<image_name>:<tag>
# Connect existing container
docker network connect <network_name> <container_name>
# Disconnect container from network
docker network disconnect <network_name> <container_name># Connect container when created
docker container create --name <container_name> \
--network <network_name> \
<image_name>:<tag>
# Connect existing container
docker network connect <network_name> <container_name>
# Disconnect container from network
docker network disconnect <network_name> <container_name>Inter-Container Communication (containers in the same network can communicate using container names)
# Container A can access Container B with:
http://container-b:port# Container A can access Container B with:
http://container-b:portData inside containers is temporary. If a container is deleted, the data is also lost. To store data permanently, use:
Bind mount is a method for sharing files or folders from the host system into a container. Data is managed directly by the host file system, with file or folder locations specified explicitly. This method is useful when you want to access or update files from outside the container.
Common Bind Mount Commands:
# Using bind mount in container
docker run --rm \
--mount type=bind,source=<host_path>,destination=<container_path> \
<image_name># Using bind mount in container
docker run --rm \
--mount type=bind,source=<host_path>,destination=<container_path> \
<image_name>Docker volume is a storage area managed directlyby Docker, which is safer, portable, and doesn't depend on host file structure. Volumes provide consistency across environments, support easy backup and restore processes, and can be managed through Docker CLI commands.
Common Docker Volume Commands:
# Create volume
docker volume create volume_name
# Use volume in container
docker run --rm \
--mount type=volume,source=<host_path>,destination=<container_path> \
<image_name>
# View volume list
docker volume ls
# Delete volume
docker volume rm <volume_name># Create volume
docker volume create volume_name
# Use volume in container
docker run --rm \
--mount type=volume,source=<host_path>,destination=<container_path> \
<image_name>
# View volume list
docker volume ls
# Delete volume
docker volume rm <volume_name>Volume backup and restore is done by creating temporary containers that mount the target volume and local folder as backup or restore location. The purpose of backup is to store copies of volume data for future use, while the purpose of restore is to return data to a volume from previously created backup copies.
Volume Backup & Restore Commands:
# Backup volume
docker container run --rm --name <backup_container_name> \
--mount type=bind,source=<host_backup_path>,destination=/backup \
--mount type=volume,source=<volume_name>,destination=/data \
<image_name> tar cvf /backup/<backup_file_name>.tar.gz /data
# Restore volume
docker container run --rm --name <restore_container_name> \
--mount type=bind,source=<host_backup_path>,destination=/backup \
--mount type=volume,source=<restore_volume_name>,destination=/data \
<image_name> tar xvf /backup/<backup_file_name>.tar.gz -C /data --strip-components 1# Backup volume
docker container run --rm --name <backup_container_name> \
--mount type=bind,source=<host_backup_path>,destination=/backup \
--mount type=volume,source=<volume_name>,destination=/data \
<image_name> tar cvf /backup/<backup_file_name>.tar.gz /data
# Restore volume
docker container run --rm --name <restore_container_name> \
--mount type=bind,source=<host_backup_path>,destination=/backup \
--mount type=volume,source=<restore_volume_name>,destination=/data \
<image_name> tar xvf /backup/<backup_file_name>.tar.gz -C /data --strip-components 1Docker Compose is a tool for defining and running multi-container Docker
applications using a docker-compose.yml configuration file. Very useful for
managing interdependent services, such as web applications and databases.
docker-compose.yml Template
# Compose version
version: '3.8'
# Application services
services:
# Web app
web:
build: .
ports:
- '8000:8000'
depends_on:
- database
restart: always
# Database
database:
image: postgres:13
environment:
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data
restart: always
# Volume for data
volumes:
db_data:# Compose version
version: '3.8'
# Application services
services:
# Web app
web:
build: .
ports:
- '8000:8000'
depends_on:
- database
restart: always
# Database
database:
image: postgres:13
environment:
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data
restart: always
# Volume for data
volumes:
db_data:Common Docker Compose Commands
# Run all services (background)
docker compose up -d
# Stop and remove all containers
docker compose down
# View container status
docker compose ps
# Rebuild image before running
docker compose up --build# Run all services (background)
docker compose up -d
# Stop and remove all containers
docker compose down
# View container status
docker compose ps
# Rebuild image before running
docker compose up --buildHere's a complete and sequential tutorial for learning Docker from basics to deploying to Docker Hub with GitLab CI/CD integration. We'll create a simple web application using Python Flask.
In this first step, we'll learn basic Docker concepts by creating a simple Flask application and packaging it into a Docker container. This is the foundation for understanding how Docker works in packaging and running applications.
mkdir docker-tutorial
cd docker-tutorialmkdir docker-tutorial
cd docker-tutorialapp.py file (simple Flask application)from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('index.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)requirements.txt fileflask==2.3.3flask==2.3.3Dockerfile# Use official Python image as base image
FROM python:3.12
# Set working directory in container
WORKDIR /app
# Copy requirements file first to utilize layer caching
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy entire application code
COPY . .
# Expose port used by Flask
EXPOSE 5000
# Command to run the application
CMD ["python", "app.py"]# Use official Python image as base image
FROM python:3.12
# Set working directory in container
WORKDIR /app
# Copy requirements file first to utilize layer caching
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy entire application code
COPY . .
# Expose port used by Flask
EXPOSE 5000
# Command to run the application
CMD ["python", "app.py"]templates/ folder and templates/index.html file<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello Docker</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(to right, #00b4db, #0083b0);
color: #fff;
text-align: center;
padding-top: 100px;
margin: 0;
}
h1 {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 20px;
opacity: 0.9;
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 30px;
max-width: 500px;
margin: auto;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body>
<div class="card">
<h1>Hello World! 🚀</h1>
<p>Getting Started with Your App using Docker!</p>
</div>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello Docker</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(to right, #00b4db, #0083b0);
color: #fff;
text-align: center;
padding-top: 100px;
margin: 0;
}
h1 {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 20px;
opacity: 0.9;
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 30px;
max-width: 500px;
margin: auto;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body>
<div class="card">
<h1>Hello World! 🚀</h1>
<p>Getting Started with Your App using Docker!</p>
</div>
</body>
</html>docker build -t my-flask-app .docker build -t my-flask-app .docker run -d -p 5000:5000 --name flask-container my-flask-appdocker run -d -p 5000:5000 --name flask-container my-flask-apphttp://localhost:5000/ to view the application.docker stop flask-container
docker rm flask-containerdocker stop flask-container
docker rm flask-containerDocker Volume is a feature that allows us to store data persistently outside containers. In this step, we'll try creating a simple volume and using it when running containers.
docker volume create flask-datadocker volume create flask-datadocker run -d -p 5000:5000 --name flask-container -v flask-data:/app/data my-flask-appdocker run -d -p 5000:5000 --name flask-container -v flask-data:/app/data my-flask-appdocker volume inspect flask-datadocker volume inspect flask-data[
{
"CreatedAt": "2025-07-27T12:57:02+07:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/flask-data/_data",
"Name": "flask-data",
"Options": null,
"Scope": "local"
}
][
{
"CreatedAt": "2025-07-27T12:57:02+07:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/flask-data/_data",
"Name": "flask-data",
"Options": null,
"Scope": "local"
}
]docker stop flask-container
docker rm flask-containerdocker stop flask-container
docker rm flask-containerDocker Network allows containers to communicate with each other safely and in isolation. In this step, we'll try creating a simple custom network and running containers within it.
docker network create flask-networkdocker network create flask-networkdocker run -d -p 5000:5000 --name flask-container --network flask-network my-flask-appdocker run -d -p 5000:5000 --name flask-container --network flask-network my-flask-appdocker network inspect flask-networkdocker network inspect flask-network[
{
"Name": "flask-network",
"Id": "ce89c2ab75c528d162f651a135beabd4dbd43c8a7d9344ac8d702ba55d4873fb",
"Created": "2025-07-27T12:57:56.113078894+07:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv4": true,
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"9d1028daf54597527bc6b72a17129d0bfd03173566e398b87dad3abb69a01250": {
"Name": "flask-container",
"EndpointID": "6dfca38bba5f7e359da06c6f8190a209fec96d5e6fd5c3750a0125dc72c3eaef",
"MacAddress": "be:2d:bf:9a:74:0e",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
][
{
"Name": "flask-network",
"Id": "ce89c2ab75c528d162f651a135beabd4dbd43c8a7d9344ac8d702ba55d4873fb",
"Created": "2025-07-27T12:57:56.113078894+07:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv4": true,
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"9d1028daf54597527bc6b72a17129d0bfd03173566e398b87dad3abb69a01250": {
"Name": "flask-container",
"EndpointID": "6dfca38bba5f7e359da06c6f8190a209fec96d5e6fd5c3750a0125dc72c3eaef",
"MacAddress": "be:2d:bf:9a:74:0e",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]docker stop flask-container
docker rm flask-containerdocker stop flask-container
docker rm flask-containerAfter successfully running applications using manual commands (build image, run container, connect volume and network), it's time to simplify everything using Docker Compose.
Docker Compose allows us to define entire application configuration (services, volumes, networks) in one YAML file. This greatly helps in team collaboration and deployment automation.
docker-compose.yml fileversion: '3.8' # (optional) No longer applies in Compose V2, can be removed
services:
web:
build: .
ports:
- '5000:5000'
volumes:
- flask-data:/app/data # Volume name
networks:
- flask-net # Network name
volumes:
flask-data: # Volume definition
networks:
flask-net: # Network definition
driver: bridge # Using bridge driverversion: '3.8' # (optional) No longer applies in Compose V2, can be removed
services:
web:
build: .
ports:
- '5000:5000'
volumes:
- flask-data:/app/data # Volume name
networks:
- flask-net # Network name
volumes:
flask-data: # Volume definition
networks:
flask-net: # Network definition
driver: bridge # Using bridge driverdocker-compose up -ddocker-compose up -ddocker-compose downdocker-compose downDocker Hub is a public registry for storing and sharing Docker images. In this step, we'll publish the image we created to Docker Hub so it can be accessed and used by others from anywhere. This is very useful for application distribution and team collaboration.
docker logindocker loginusername/repository:tag formatdocker tag my-flask-app <username>/my-flask-app:latestdocker tag my-flask-app <username>/my-flask-app:latestdocker push <username>/my-flask-app:latestdocker push <username>/my-flask-app:latestOpen https://hub.docker.com/repositories/ and ensure the image is successfully
published and appears in the account's repository list.
In this step, we'll test the process of pulling images from Docker Hub and running them in different environments (can be on server or local). This demonstrates one of Docker's advantages: portability - the same image can run anywhere consistently.
⚠️ Note: This step is not mandatory to run on a server, it can also be done locally. But make sure there are no containers or images with the same name.
If they already exist and are still active:
docker stop flask-app # Stop container
docker rm flask-app # Remove container
docker rmi <username>/my-flask-app:latest # (optional) Remove old imagedocker stop flask-app # Stop container
docker rm flask-app # Remove container
docker rmi <username>/my-flask-app:latest # (optional) Remove old imagessh <user>@<server_ip>ssh <user>@<server_ip>docker pull <username>/my-flask-app:latestdocker pull <username>/my-flask-app:latestReplace username with your Docker Hub account name that was pushed earlier.
docker run -d --name <container_name> -p 5000:5000 <username>/my-flask-app:latestdocker run -d --name <container_name> -p 5000:5000 <username>/my-flask-app:latestThe -d option runs in background, -p to map host:container port.
Open http://localhost:5000 (local) or http://<server-ip>:5000 (server).
Here's the result of the application successfully running through the server.
After successfully building images locally, uploading them to Docker Hub, and pulling images on the server, the next step is to automate the build and push image process using GitLab CI/CD. With GitLab pipeline, this process will run automatically every time you push to the repository.
GitLab CI allows us to run Docker-based CI/CD processes through the
.gitlab-ci.yml file. For the Continuous Delivery stage, simply do docker pull
on the server and restart the container so changes can be applied.
.gitlab-ci.yml file in project rootThis file will instruct GitLab CI/CD to build and push Docker images.
# Use Docker image & enable Docker-in-Docker
image: docker:latest
services:
- docker:dind
# Variables for Docker configuration
variables:
DOCKER_DRIVER: overlay2
IMAGE_NAME: $CI_REGISTRY_USER/my-flask-app
before_script:
# Login to GitLab Container Registry
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER"
--password-stdin
build:
stage: build
script:
# Build & push Docker image
# Use tag latest_v2 because index.html will be updated (previously latest)
- docker build -t $IMAGE_NAME:latest_v2 .
- docker push $IMAGE_NAME:latest_v2# Use Docker image & enable Docker-in-Docker
image: docker:latest
services:
- docker:dind
# Variables for Docker configuration
variables:
DOCKER_DRIVER: overlay2
IMAGE_NAME: $CI_REGISTRY_USER/my-flask-app
before_script:
# Login to GitLab Container Registry
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER"
--password-stdin
build:
stage: build
script:
# Build & push Docker image
# Use tag latest_v2 because index.html will be updated (previously latest)
- docker build -t $IMAGE_NAME:latest_v2 .
- docker push $IMAGE_NAME:latest_v2Select the New Project → Create blank project menu.
After the project is created, initialize Git in local folder and connect to
GitLab.
git init
git remote add origin https://gitlab.com/username/your-repo.gitgit init
git remote add origin https://gitlab.com/username/your-repo.gitGitLab project → Settings → CI/CD → click Expand on
Variables sectionCI_REGISTRY_USER → fill with Docker Hub usernameCI_REGISTRY_PASSWORD → fill with Docker Hub password/tokenindex.html content to see changes when pulling imageEdit index.html file (or other HTML files) so the changes are visible when
pulled from server.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello Docker</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(to right, #00b4db, #0083b0);
color: #fff;
text-align: center;
padding: 50px 20px;
margin: 0;
}
h1 {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 20px;
opacity: 0.9;
margin-bottom: 30px;
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 30px;
max-width: 500px;
margin: 20px auto;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
max-width: 1100px;
margin: 0 auto;
}
.card h2 {
margin-top: 0;
color: #fff;
}
.card p {
margin-bottom: 0;
}
</style>
</head>
<body>
<div class="card-container">
<div class="card">
<h1>Hello World! 🚀</h1>
<p>Getting Started with Your App using Docker!</p>
</div>
<div class="card">
<h2>Docker Features</h2>
<p>✔ Containerization made easy</p>
<p>✔ Lightweight and portable</p>
<p>✔ CI/CD integration ready</p>
</div>
<div class="card">
<h2>Next Steps</h2>
<p>➡ Build your Docker image</p>
<p>➡ Push to container registry</p>
<p>➡ Deploy anywhere!</p>
</div>
</div>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello Docker</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(to right, #00b4db, #0083b0);
color: #fff;
text-align: center;
padding: 50px 20px;
margin: 0;
}
h1 {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 20px;
opacity: 0.9;
margin-bottom: 30px;
}
.card {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 30px;
max-width: 500px;
margin: 20px auto;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
max-width: 1100px;
margin: 0 auto;
}
.card h2 {
margin-top: 0;
color: #fff;
}
.card p {
margin-bottom: 0;
}
</style>
</head>
<body>
<div class="card-container">
<div class="card">
<h1>Hello World! 🚀</h1>
<p>Getting Started with Your App using Docker!</p>
</div>
<div class="card">
<h2>Docker Features</h2>
<p>✔ Containerization made easy</p>
<p>✔ Lightweight and portable</p>
<p>✔ CI/CD integration ready</p>
</div>
<div class="card">
<h2>Next Steps</h2>
<p>➡ Build your Docker image</p>
<p>➡ Push to container registry</p>
<p>➡ Deploy anywhere!</p>
</div>
</div>
</body>
</html>git add .
git commit -m "Initial commit with Docker setup"
git push -u origin maingit add .
git commit -m "Initial commit with Docker setup"
git push -u origin mainCI/CD → Pipelines menu in GitLabdocker login → successfully logged into Docker Hubdocker build → image successfully builtdocker push → image successfully sent to Docker HubAfter the GitLab pipeline successfully builds and pushes the new image, simply
do docker pull and restart the container to apply updates.
The steps are similar to Step 6, just replace with the latest tag:
# (Optional) Stop and remove old container
docker stop flask-app
docker rm flask-app
# Pull latest image version
docker pull <username>/my-flask-app:latest_v2
# Restart container
docker run -d --name flask-app -p 5000:5000 <username>/my-flask-app:latest_v2# (Optional) Stop and remove old container
docker stop flask-app
docker rm flask-app
# Pull latest image version
docker pull <username>/my-flask-app:latest_v2
# Restart container
docker run -d --name flask-app -p 5000:5000 <username>/my-flask-app:latest_v2The application will now display the latest version according to the last
index.html update in GitLab.
docker-tutorial/
├── app.py
├── Dockerfile
├── docker-compose.yml
├── .gitlab-ci.yml
├── requirements.txt
└── templates/
└── index.htmldocker-tutorial/
├── app.py
├── Dockerfile
├── docker-compose.yml
├── .gitlab-ci.yml
├── requirements.txt
└── templates/
└── index.html