GitLab Repository: Flask App - Code & Pipeline #
File & Folder Structure #
flask-app-code
├── Dockerfiles
│ └── Dockerfile
├── flask-app # Python Flask web application
│ ├── app.py
│ └── requirements.txt
├── .gitlab-ci.yml # GitLab CI Pipeline manifest
└── README.md
CI Pipeline Manifest #
- .gitlab-ci.yml
### Variables
variables:
# Define the image name, tagging it with the GitLab CI registry and the current commit SHA
IMAGE_SHA: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
# Repository URL of the "Flask Web Application Manifest" project
GIT_REPO_URL: "git@gitlab.jklug.work:python/flask-app-helm.git"
### Stages
stages:
- build
- update_helm_chart
### Build Container Image
build_image:
image: docker:stable
stage: build
services:
- docker:dind
variables:
DOCKER_TLS_CERTDIR: ""
before_script:
# Login to GitLab Container Registry using predefined CI/CD variables
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
# Build the Docker image from the specified Dockerfile in the Dockerfiles directory
- docker build --pull -t $IMAGE_SHA -f Dockerfiles/Dockerfile .
# Push the built Docker image to the GitLab Container Registry
- docker push $IMAGE_SHA
# Print the image name
- echo $IMAGE_SHA
rules:
# Rule: Run this job only for the main branch and if the specified Dockerfile exists
- if: $CI_COMMIT_BRANCH == "main"
exists:
- Dockerfiles/Dockerfile
### Update Helm Chart
update_helm_chart:
image: alpine:latest
stage: update_helm_chart
needs:
# Run this job only if the 'build_image' job succeeds
- build_image
variables:
HELM_REPO_BRANCH: "main" # Branch of the external repository to update
script:
# Install required tools
- apk add --no-cache yq git openssh
# Configure SSH for Git
- mkdir -p ~/.ssh
- echo "$pipeline_key" > ~/.ssh/id_rsa # Use the correct variable name
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -H gitlab.jklug.work >> ~/.ssh/known_hosts
# Clone the external repository
- git clone --branch $HELM_REPO_BRANCH $GIT_REPO_URL helm-chart-repo
- cd helm-chart-repo
# Debug repository contents
- ls -R
- ls -R helm-chart
# Update the container image in the Helm chart's values.yaml
- yq e ".image.repository = \"$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG\" | .image.tag = \"$CI_COMMIT_SHA\"" -i helm-chart/values.yaml
# Add and commit the changes
- git config --global user.email "ci@example.com"
- git config --global user.name "GitLab CI"
- git add helm-chart/values.yaml
- git commit -m "Update Helm chart with image $IMAGE_SHA"
# Push the changes back to the external repository
- git push origin $HELM_REPO_BRANCH
rules:
- if: $CI_COMMIT_BRANCH == "main"
Dockerfile #
- Dockerfiles/Dockerfile
### Stage 1: Compile Python Application
# Use Python-slim image as the base image / Label image as "builder"
FROM python:3.13-slim AS builder
# Set the working directory inside the container to /app
WORKDIR /app
# Copy the contents of the "flask-app" directory to "/app" inside the container
ADD flask-app/. /app
# Install the dependencies listed in "requirements.txt"
RUN pip install -r requirements.txt
# Compile all Python files to bytecode and move the desired one to /app as app.pyc
RUN python -m compileall .
### Stage 2: Final Container with Bytecode
# Use a lightweight Python image for the final stage
FROM python:3.13-rc-alpine
# Copy requirements.txt from builder
COPY --from=builder /app/requirements.txt /tmp/
# Install app requirements
RUN pip install -r /tmp/requirements.txt
# Create system user "appuser" / no PW
RUN adduser -S -D -H -h /app appuser
# Switch to "appuser"
USER appuser
# Copy compiled .pyc files and any necessary files from the builder stage
COPY --from=builder /app/__pycache__/* /app/
# Define working directory
WORKDIR /app
# Expose port 8080 for the application
EXPOSE 8080
# Execute Bytecode when the container starts
ENTRYPOINT ["python", "/app/app.cpython-313.pyc"]
Python Code #
Flask Application: app.py #
- flask-app/app.py
# Import the Flask class from the Flask library
from flask import Flask
# Create an instance of the Flask application
app = Flask(__name__)
# Define a route for the root URL ("/")
@app.route("/")
def main(): # Define a function that will execute when the root URL is accessed
return "Hi there from Python Flask application\n" # Return a simple string response to the client.
# Start the Flask app on all available network interfaces (0.0.0.0) and port 8080
app.run(host='0.0.0.0', port=8080)
Requirements: requirements.txt #
- flask-app/requirements.txt
# Flask web framework version 3.1.0
Flask==3.1.0
GitLab Repository: Flask App - Manifest #
File & Folder Structure #
flask-app-helm
├── helm-chart # Helm chart
│ ├── Chart.yaml
│ ├── templates
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ └── values.yaml
└── README.md
Helm Chart #
deployment.yaml #
- helm-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
labels:
app: flask-app
spec:
replicas: 2 # Number of replicas
selector:
matchLabels:
app: flask-app
template:
metadata:
labels:
app: flask-app
spec:
imagePullSecrets:
- name: "{{ .Values.imagePullSecrets | first }}" # Correctly access the first item in the array
containers:
- name: flask-app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 8080
env:
- name: FLASK_ENV
value: "production"
service.yaml #
- helm-chart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: flask-app
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
selector:
app: flask-app
values.yaml #
- helm-chart/values.yaml
# Define the image details
image:
repository: "gitlab-registry.jklug.work/python/flask-app-code/main" # GitLab Registry; will be updated by the pipeline
tag: "latest" # Tag placeholder; will be updated by the pipeline
imagePullSecrets:
- gitlab-registry-secret # The Kubernetes secret name
Chart.yaml #
- helm-chart/Chart.yaml
apiVersion: v2
name: flask-web-app
description: A Helm chart for deploying a Flask web application
type: application
version: 0.1.0
appVersion: 1.0.0
GitLab Deploy Key: For CI Pipeline #
Create SSH Key Pair #
Generate a SSH key pair named pipeline_key
and pipeline_key.pub
in the current directory:
# Create SSH RSA key pair: 4096 bit
ssh-keygen -t rsa -b 4096 -f pipeline_key
# Copy the public SSH key
cat pipeline_key.pub
Add Public Key to Helm Project #
Add the public SSH key to the GitLab Helm chart project flask-app-helm
:
-
Go to: (Project) “Settings” > “Repository”
-
Expand the “Deploy keys” section
-
Click “Add new key”
-
Paste the value of the public SSH key
pipeline_key.pub
-
Define a title like “Pipeline-key”
-
Select “Grant write permissions to this key”
-
Click “Add key”
Add Private Key to Pipeline Project #
Add the private SSh key as variable to the GitLab code and pipeline project `flask-app-code:
Go to: (Project) “Settings” > “CI/CD”
Expand the “Variables” section
Click “Add variable”
Select type: “Variable (default)”
Unflag “Protect variable”
Define a key name like pipeline_key
Paste the value of the private key pipeline_key
Click “Add variable”
Verify Image Name #
After the pipeline jobs run through, verify the correct image name.
Job Logs #
Verify the job logs of the “flask-app-code” project:
-
Go to: (Project) “Build” > “Jobs”
-
Select the
build_image
job -
Copy the image name from the job logs:
# Copy the image name
$ echo $IMAGE_SHA
gitlab-registry.jklug.work/python/flask-app-code/main:9ae1cd23b7ba9ad472014389730096980cac3fa8
Helm Chart values.yaml #
The values.yaml
manifest in the “flask-app-helm” project should now look like this:
- helm-chart/values.yaml
# Define the image details
image:
repository: "gitlab-registry.jklug.work/python/flask-app-code/main" # Define GitLab Registry
tag: "9ae1cd23b7ba9ad472014389730096980cac3fa8" # Placeholder; will be updated by the pipeline
imagePullSecrets:
- name: gitlab-registry-secret