Day 9 – GitLab CI/CD for Python + Container Registry + Kubernetes Deployment

Welcome to Day 8 of the Ramdan DevOps Bootcamp! Today, we dove into real-world DevOps practices by implementing CI/CD pipelines for a Python API using GitLab CI, building Docker images, pushing them to the GitLab Container Registry, and deploying them via Kubernetes.
Let’s break down what we accomplished 👇
🐍 Python App Setup with Docker
We containerized a basic FastAPI application using this Dockerfile:
# Base Python image
FROM python:3.12-slim
# Set working directory inside the container
WORKDIR /usr/local/app
# Copy requirements file and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Copy application source code
COPY src ./src
# Expose application port
EXPOSE 8000
# Switch to a non-root user for better security
RUN useradd app
USER app
# Run the application
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
✅ Lightweight
✅ Secure (non-root user)
✅ Production-ready
⚙️ GitLab CI/CD Pipeline
We configured a GitLab CI/CD pipeline to automatically:
Build the Docker image.
Push it to the GitLab Container Registry.
Optionally trigger deployment (manual or automatic).
A sample .gitlab-ci.yml might look like this:
image: docker:latest
services:
- docker:build
stages:
- build
- push
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
before_script:
- echo "$CI_JOB_TOKEN" | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
build:
stage: build
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
only:
- tags
💡 We trigger builds on Git tags like
1.2.0to produce versioned images.
🐳 Image Hosting with GitLab Container Registry
No need for Docker Hub or external registries. GitLab gives us a built-in container registry:
tgitlab.hassandevops.site:5050/ramdanbootcamp/multistage-python-cicd:1.2.0
You can access it under your project > Packages & Registries > Container Registry.
☸️ Kubernetes Deployment
After pushing our Docker image, we deployed it to Kubernetes using the following YAML configs:
✅ Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mypythonapp
spec:
replicas: 1
selector:
matchLabels:
app: mypythonapp
template:
metadata:
labels:
app: mypythonapp
spec:
containers:
- name: mypythonapp
image: gitlab.hassandevops.site:5050/ramdanbootcamp/multistage-python-cicd:1.2.0
ports:
- containerPort: 8000
imagePullSecrets:
- name: gitlab-registry-secret
🔐
imagePullSecretshelps Kubernetes authenticate with GitLab's private registry.
✅ Service
apiVersion: v1
kind: Service
metadata:
name: mypythonapp-svc
spec:
selector:
app: mypythonapp
ports:
- protocol: TCP
port: 80
targetPort: 8000
✅ Ingress (with SSL)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mypythonapp-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: "/"
spec:
rules:
- host: python.hassandevops.site
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mypythonapp-svc
port:
number: 80
tls:
- hosts:
- python.hassandevops.site
secretName: hassandevops-tls
🌐 Our Python app is now live at https://python.hassandevops.site
🔐 Pro Tip: Creating the GitLab Registry Secret
To pull private images in Kubernetes, create a secret:
kubectl create secret docker-registry gitlab-registry-secret \
--docker-server=gitlab.hassandevops.site:5050 \
--docker-username=<your-username> \
--docker-password=<your-token> \
--docker-email=you@example.com




