DEV Community

Python-T Point
Python-T Point

Posted on • Originally published at pythontpoint.in

🚀 Building a helm chart for Python Flask API made easy

A well‑crafted Helm chart for a Python Flask API eliminates manual Kubernetes boilerplate. The guide below shows how the chart assembles container, deployment, and service definitions into a reusable package.

📑 Table of Contents

  • 📦 Helm Chart Basics — Understanding the Structure
  • 🐍 Containerizing the Flask API — Building a Docker Image
  • 🐳 Dockerfile Details
  • 🛠 Build Command
  • ⚙️ Kubernetes Manifests — Deploying the API
  • 📄 Deployment Spec
  • 📈 Chart Customization — Adding Values and Templates
  • 🗂 Template Logic
  • 🔧 Overrides
  • 🚀 Deploying the Chart — Installing the helm chart on a Cluster
  • 🟩 Final Thoughts
  • ❓ Frequently Asked Questions
  • How do I upgrade the Flask image without downtime?
  • Can I use a different WSGI server than gunicorn?
  • What is the best way to expose the API publicly?
  • 📚 References & Further Reading

📦 Helm Chart Basics — Understanding the Structure

A Helm chart is a collection of YAML templates and supporting files that Kubernetes consumes as a single unit.

# Chart.yaml
apiVersion: v2
name: flask-api
description: Helm chart for Python Flask API
type: application
version: 0.1.0
appVersion: "1.0"
Enter fullscreen mode Exit fullscreen mode

What this does: (More onPythonTPoint tutorials)

  • apiVersion: Helm chart schema version; v2 enables chart dependencies.
  • name: Identifier used by helm install and helm repo.
  • description: Human‑readable summary.
  • type: Declares the chart as an application (as opposed to a library).
  • version: Chart version for Helm repository indexing.
  • appVersion: Version of the Flask application the chart deploys.

The chart also needs a values.yaml that supplies defaults for the templates.

# values.yaml
replicaCount: 2
image: repository: yourregistry/flask-api tag: "latest" pullPolicy: IfNotPresent
service: type: ClusterIP port: 5000
resources: limits: cpu: "500m" memory: "256Mi" requests: cpu: "250m" memory: "128Mi"
Enter fullscreen mode Exit fullscreen mode

What this does:

  • replicaCount: Number of pod replicas the Deployment will maintain.
  • image.repository / tag: Docker image location; Helm substitutes these into the pod spec.
  • service.type: Exposes the Flask API only inside the cluster (ClusterIP).
  • resources: Sets CPU and memory limits/requests, enabling the scheduler to make placement decisions.

Key point: Helm separates static configuration (Chart.yaml) from user‑adjustable parameters (values.yaml), allowing the same chart to be deployed across dev, staging, and prod environments without editing templates.


🐍 Containerizing the Flask API — Building a Docker Image

A Docker image packages the Flask code, its dependencies, and the runtime environment.

# Dockerfile
FROM python:3.11-slim # Install system dependencies required by many Python wheels
RUN apt-get update && apt-get install -y -no-install-recommends gcc libpq-dev && rm -rf /var/lib/apt/lists/* # Create a non‑root user
RUN useradd -create-home appuser
WORKDIR /home/appuser # Copy and install Python requirements
COPY requirements.txt .
RUN pip install -no-cache-dir -r requirements.txt # Copy application source
COPY . . # Switch to non‑root user
USER appuser # Expose Flask default port
EXPOSE 5000 # Run the application
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
Enter fullscreen mode Exit fullscreen mode

What this does:

  • FROM python:3.11-slim: Base image with minimal OS footprint.
  • RUN apt-get …: Installs build tools needed for compiled wheels (e.g., psycopg2).
  • useradd …: Creates a low‑privilege user to avoid running as root.
  • COPY requirements.txt / RUN pip install: Layers dependency installation for caching efficiency.
  • COPY . .: Adds the Flask source code.
  • CMD [ "gunicorn", …]: Starts the app with a production‑grade WSGI server.

🐳 Dockerfile Details

The EXPOSE 5000 instruction documents the intended container port; Kubernetes later maps it via a Service.

🛠 Build Command

$ docker build -t yourregistry/flask-api:latest .
Sending build context to Docker daemon 12.3MB
Step 1/10: FROM python:3.11-slim
... (output truncated for brevity)
Successfully built a1b2c3d4e5f6
Successfully tagged yourregistry/flask-api:latest
Enter fullscreen mode Exit fullscreen mode

After the build succeeds, push the image to a registry accessible by the cluster.

$ docker push yourregistry/flask-api:latest
The push refers to repository [yourregistry/flask-api]
... (output showing layers being uploaded)
latest: digest: sha256:... size: 123MB
Enter fullscreen mode Exit fullscreen mode

⚙️ Kubernetes Manifests — Deploying the API

A Deployment controller creates ReplicaSets that manage pod lifecycles. (Also read: CI/CD: Auto-Deploy a Flask App to EC2 with GitHub Actions (2026))

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata: name: {{ include "flask-api.fullname" . }} labels: app: {{ include "flask-api.name" . }}
spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ include "flask-api.name" . }} template: metadata: labels: app: {{ include "flask-api.name" . }} spec: containers: - name: flask image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: {{ .Values.service.port }} resources: {{ toYaml .Values.resources | nindent 12 }}
Enter fullscreen mode Exit fullscreen mode

What this does:

  • replicas: Uses .Values.replicaCount from values.yaml to set the desired pod count.
  • selector.matchLabels: Ensures the Deployment controls only pods with the matching label.
  • template.metadata.labels: Labels the pod so the Service can discover it.
  • containers[0].image: Inserts the Docker image reference defined by the user.
  • resources: Applies CPU/memory limits and requests from values.yaml.

📄 Deployment Spec

The Helm template syntax ({{ }}) resolves at install time, allowing the same YAML to be reused with different values without editing the file.

# templates/service.yaml
apiVersion: v1
kind: Service
metadata: name: {{ include "flask-api.fullname" . }} labels: app: {{ include "flask-api.name" . }}
spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: {{ .Values.service.port }} protocol: TCP name: http selector: app: {{ include "flask-api.name" . }}
Enter fullscreen mode Exit fullscreen mode

What this does:

  • type: Uses the value from values.yaml (ClusterIP by default).
  • ports.port / targetPort: Exposes the same port number inside and outside the pod.
  • selector: Routes traffic to pods labeled with the chart’s name.

Key point: The Deployment guarantees that the specified number of Flask pods stay running, while the Service provides a stable DNS name ({{ include "flask-api.fullname" . }}) for intra‑cluster clients.


📈 Chart Customization — Adding Values and Templates

Values files let operators override defaults without editing chart templates.

# templates/_helpers.tpl{{ define "flask-api.fullname" }}{{ printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}{{ end }}{{ define "flask-api.name" }}{{ .Chart.Name }}{{ end }}
Enter fullscreen mode Exit fullscreen mode

What this does:

  • flask-api.fullname: Generates a unique release name that complies with DNS label length limits.
  • flask-api.name: Returns the chart name for consistent labeling.

🗂 Template Logic

Using toYaml and nindent ensures the resources block is correctly indented, which is critical because YAML parsers are whitespace‑sensitive. (Also read: 🚀 Deploy Flask API to Akamai Cloud Compute made easy)

🔧 Overrides

To run a single‑replica dev environment, supply a custom values-dev.yaml:

# values-dev.yaml
replicaCount: 1
service: type: LoadBalancer port: 80
image: tag: "dev"
resources: limits: cpu: "250m" memory: "128Mi" requests: cpu: "100m" memory: "64Mi"
Enter fullscreen mode Exit fullscreen mode

When installing, pass the file with --values values-dev.yaml to override the defaults.

Helm’s templating engine turns static YAML into a programmable deployment artifact, letting you ship production‑ready Kubernetes objects alongside your source code.

According to the Helm documentation, charts are versioned independently of the applications they deploy, which enables continuous delivery pipelines to upgrade only the infrastructure layer. (Also read: 🐍 Query Google BigQuery tables with Python pandas made easy)

Key point: By separating immutable chart structure from mutable values, teams can maintain a single source of truth for Kubernetes resources while still tailoring each environment.


🚀 Deploying the Chart — Installing the helm chart on a Cluster

The helm install command creates the Kubernetes resources defined by the chart and populates them with the supplied values.

$ helm repo add mycharts https://example.com/charts
"mycharts" has been added to your repositories $ helm install my-flask mycharts/flask-api -values values-dev.yaml
NAME: my-flask
LAST DEPLOYED: -10-12 10:15:23.123456789 +0000 UTC
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the LoadBalancer IP: kubectl get svc my-flask-flask-api -o jsonpath='{.status.loadBalancer.ingress[0].ip}'



$ kubectl get pods -l app=flask-api
NAME READY STATUS RESTARTS AGE
my-flask-flask-api-5d9c8f7c6b-abcde 1/1 Running 0 30s
Enter fullscreen mode Exit fullscreen mode

The output confirms that Helm rendered the templates, applied them to the cluster, and that the Deployment controller created a ready pod.

Why this, not the obvious alternative? Using a Helm chart abstracts away the raw kubectl apply -f workflow, providing versioned releases, rollbacks, and a declarative upgrade path that raw manifests lack.


🟩 Final Thoughts

Building a Helm chart for a Python Flask API consolidates container building, Kubernetes object definition, and environment‑specific configuration into a single, versioned artifact. The chart’s templating system ensures that changes to replica counts, resource limits, or service types are applied consistently across clusters without manual edits.

For developers, the same helm install command can be used in local testing, CI pipelines, and production, reducing drift and simplifying operational hand‑offs. The approach scales naturally: add ConfigMaps for configuration, add Ingress resources for external exposure, and the chart remains the single source of truth.

❓ Frequently Asked Questions

How do I upgrade the Flask image without downtime?

Run helm upgrade my-flask mycharts/flask-api -set image.tag=1.1.0. Helm creates a new ReplicaSet with the updated image; the Deployment controller gradually rolls out the new pods while terminating the old ones, achieving a rolling update.

Can I use a different WSGI server than gunicorn?

Yes. Update the CMD line in the Dockerfile or add an environment variable that selects the server at runtime. Then modify the Helm values to reflect any new port or command arguments.

What is the best way to expose the API publicly?

Change the Service type to LoadBalancer or add an Ingress resource that routes external traffic to the Service. The chart already includes a Service template, so you only need to add an Ingress template and configure a host name.

💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.

📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.

📚 References & Further Reading

  • Official Helm chart guide — detailed explanation of chart structure and best practices: helm.sh
  • Flask documentation — Flask application fundamentals and deployment considerations: flask.palletsprojects.com
  • Docker official docs — building minimal Python images and best practices: docs.docker.com

Top comments (0)