
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 the deployment.yaml template, which specifies the source from which our environment variables should be retrieved;
- and a checksum/config annotation in the deployment.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 "$GITHUB_ACTOR@users.noreply.github.com"
- 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/chart-releaser-action@v1.6.0
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!