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 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!

New Call-to-action
blog comments powered by Disqus
Times
Check

Success!

Times

You're already subscribed

Times