How to Create a Helm Chart for a Django App
At Caktus, we use Helm charts to simplify our deployment process for Django projects. Helm is a package manager for Kubernetes, and using Helm charts allows us to automate the process of writing Kubernetes configuration files for our Django applications. We use it together with GitHub Actions and Ansible to streamline our deployment processes.
This post includes an accompanying GitHub
repo, which contains a
simple Django project. The project is mostly based on our 2017 post
about using Docker in
production,
so please refer to that for more context, particularly on the
Dockerfile
that we'll be using to build a Docker image that we'll
publish to the GitHub Container Registry (GHCR).
Setting up a GitHub Actions workflow to publish a Docker image to GHCR
In order to deploy our application using Helm, we'll need to provide a
container image repository from which the image for our application can
be pulled. We use GHCR for this, though you can use any other container
registry as well. Since we use GitHub to host our code repositories, we
also use GitHub Actions to automate some deployment processes. This
GitHub Actions workflow enables us to build a container image and
publish it whenever a push is done to our main
branch.
Create a file in the .github/workflows
directory with this content.
See the GitHub Actions
documentation
for more details:
name: Docker
on:
push:
# Change this to the branches you want to trigger publishing a new container image
branches: [main]
# Publish semver tags as releases.
tags: ["v*.*.*"]
env:
REGISTRY: ghcr.io
REGISTRY_WITH_PATH: ghcr.io/${{ github.repository_owner }}
jobs:
build-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_WITH_PATH }}/helm-charts-post
# Generate Docker tags based on the following events/attributes
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}.{{hotfix}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Now every time a commit is pushed to the main
branch, a new container
image will be published in your GHCR repository at
https://<your-GitHub-repo-URL>/pkgs/container/<repo-name>
Setting up a Helm chart and automating its releases
For simplicity, we are creating our Helm chart in the same repo as our code, though Helm charts can be created in their own repo, where we can have the charts for different applications. Once you have installed Helm, creating a Helm chart is simple.
We first create a charts
directory, where we will create our Helm
chart. We will be using the chart-releaser
action to
automatically create new releases of our Helm chart, and it will be
looking for a charts
directory in our repo by default. You can rename
this directory if needed, but you'll also need to change the
charts_dir
input for chart-releaser
. You also need to create a
gh-pages
branch in the repo that contains your Helm chart. See all the
prerequisites for the chart-releaser
action.
mkdir charts -p
cd charts
helm create helm-charts-post
The helm create
command creates a skeleton structure for our Helm
chart, with various templates of Kubernetes config files. These
templates can be edited according to your needs. For the
helm-charts-post
repo, we only made a few changes to the generated
files:
- added a
secret.yaml
template, for our environment variables; - added
envFrom
in thedeployment.yaml
template, which specifies the source from which our environment variables should be retrieved; - and a
checksum/config
annotation in thedeployment.yaml
template, so that our deployment can be automatically restarted if our environment variables are changed.
For automatic releases using GitHub Actions, create a file in the
.github/workflows
directory with this content:
name: Release Charts
on:
push:
# Change this to the branches you want to trigger a new Helm chart release
branches:
- main
jobs:
release:
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"
- name: Install Helm
uses: azure/setup-helm@v3
- name: Build dependencies
run: |
helm repo add dandydev https://dandydeveloper.github.io/charts
cd charts/helm-charts-post && helm dependency build
- name: Run chart-releaser
uses: helm/[email protected]
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
Now every time a commit is pushed to the main
branch, this workflow
will be run, but a new Helm chart release will only be created if your
chart has changed. Chart releases will be in
https://<your-GitHub-repo-URL>/releases
.
Using Ansible with the Helm chart for deployment
The deployment
folder contains a sample Ansible playbook
(deploy.yaml
) that can be used to install the Helm chart. Update it as
appropriate, along with the hosts definition in your Ansible inventory
(inventory.yaml
for our repo).
- name: Helm Charts Post deployment
hosts: k8s
gather_facts: false
vars:
ansible_python_interpreter: "{{ ansible_playbook_python }}"
tasks:
- name: Create helm-charts-post namespace
kubernetes.core.k8s:
context: "{{ k8s_context|mandatory }}"
kubeconfig: "{{ k8s_kubeconfig }}"
name: "{{ k8s_namespace }}"
api_version: v1
kind: Namespace
state: present
- name: Add helm-charts-post Helm chart
kubernetes.core.helm:
state: present
context: "{{ k8s_context|mandatory }}"
kubeconfig: "{{ k8s_kubeconfig }}"
# To test local chart. Update with the path to the chart
# chart_ref: ../../helm-charts-post/charts/helm-charts-post/
# dependency_update: yes
# Use released chart:
chart_repo_url: https://caktus.github.io/helm-charts-post
chart_ref: helm-charts-post
chart_version: "{{ k8s_chart_version }}"
release_name: helm-charts-post
release_namespace: "{{ k8s_namespace }}"
release_values: "{{ k8s_release_values }}"
wait: yes
You should have a Kubernetes node already set up and accessible either remotely or from the host itself. To test that your Ansible inventory definition is correct and the host is accessible, run:
ansible k8s -m ping
You can check that your Ansible variables are correct by running:
ansible-playbook debug.yaml -l k8s
This will print out the variables that are used in the playbook, in JSON
format. The values in k8s_release_values
will be used by the Helm
chart to fill in its templates, and will override the default values in
the chart's values.yaml
file. You can copy the JSON object printed
out for k8s_release_values
, save it in a file, then run the following
command to see what the final configuration files will look like without
actually deploying the application:
helm install --debug --dry-run helm-charts-post charts/helm-charts-post/ -f path/to/the/k8s_release_values.json
When you're ready, you can deploy the application with:
ansible-playbook deploy.yaml -l k8s
To use the local Helm chart instead of the one released on GitHub, uncomment the 2 lines under "To test dev chart" and comment out the 3 lines under "Use released chart".
Summary
That concludes this high-level introduction to creating a Helm chart for deploying a Django application. Each application's needs will be different, but I hope this provides a good starting point for your Helm chart. Good luck!