Django (polls app) development in Kubernetes using Tilt with PostgreSQL, Adminer and MinIO installed using Helm
Tags: tilt, kubernetes, django, minio
Published: 2022-12-02 16:36:01

Sample Django project demonstrating how to use Tilt with Helm to develop a Django application in Kubernetes. This project uses the [polls app](https://docs.djangoproject.com/en/4.1/intro/tutorial01/) from the Django documentation and is configured to use PostgreSQL and MinIO as the database and file storage backend respectively.

---

### Step 1 - Start the local Kubernetes cluster from Docker Desktop
Go to Docker Desktop > Preferences > Kubernetes > Enable Kubernetes and click Apply & Restart

![Docker Desktop Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dko29eb2dey6t7nip2h9.png)

---

### `Optional` Step 2 - Set the kubectl context to Docker Desktop
If you have multiple kubectl contexts, you can set the current context to Docker Desktop using the following command:
```bash
kubectl config get-contexts
kubectl config use-context docker-desktop
```

---

### Step 3 - Install Tilt for macOs (if not already installed)
Follow the instructions on the [Tilt website](https://docs.tilt.dev/install.html) to install Tilt.
```bash
brew install tilt-dev/tap/tilt
```

---

### Step 4 - Local development using Tilt
```bash
# Clone the repository
git clone https://github.com/garis-space/django-polls-app-tilt-k8s-pg-minio.git
cd django-polls-app-tilt-k8s-pg-minio

# Environment variables (edit as needed)
cp .env.example .env
```

The Tiltfile

```python
# Disable Titl analytics
analytics_settings(enable=False)

# Disable Tilt redacted secret
secret_settings(disable_scrub=True)

# Load Tilt extensions (https://github.com/tilt-dev/tilt-extensions)
load('ext://helm_resource', 'helm_resource', 'helm_repo')
load('ext://uibutton', 'cmd_button', 'bool_input', 'text_input', 'location')
load('ext://secret', 'secret_create_generic', 'secret_from_dict')
load('ext://configmap', 'configmap_create')
load('ext://dotenv', 'dotenv')

# Check if Kubernetes context is set to docker-desktop
if k8s_context() != 'docker-desktop':
    fail('Kubernetes context is not set to docker-desktop')

# Read .env file
dotenv('.env')

###
# PostgreSQL
###

# Create secrets for PostgreSQL
k8s_yaml(secret_from_dict("django-polls-postgresql", inputs = {
    'postgres-password' : os.getenv('POSTGRESQL_PASSWORD')
}))

# Add PostgreSQL Helm resource (https://artifacthub.io/packages/helm/bitnami/postgresql)
helm_repo('bitnami', 'https://charts.bitnami.com/bitnami')
helm_resource(
    name='postgresql',
    chart='bitnami/postgresql',
    namespace='default',
    flags=[
        '--set=image.tag=15-debian-11',
        # Load secret values from created secret
        '--set=global.postgresql.auth.existingSecret=django-polls-postgresql',
    ],
    labels=['database']
)

###
# Adminer
###

# Add Adminer Helm resource (https://artifacthub.io/packages/helm/cetic/adminer)
helm_repo('cetic', 'https://cetic.github.io/helm-charts')
helm_resource(
    name='adminer',
    chart='cetic/adminer',
    namespace='default',
    flags=[
        '--set=service.type=LoadBalancer',
        '--set=service.port=8080',
        '--set=config.externalserver=postgresql',
    ],
    labels=['database']
)

###
# MinIO
###

# Create secrets for MinIO
k8s_yaml(secret_from_dict("django-polls-minio", inputs = {
    'rootUser' : os.getenv('MINIO_ROOT_USER'),
    'rootPassword' : os.getenv('MINIO_ROOT_PASSWORD'),
}))

# Add MinIO Helm resource (https://artifacthub.io/packages/helm/minio/minio)
helm_repo('minio', 'https://charts.min.io')
#update_settings(k8s_upsert_timeout_secs=120)
helm_resource(
    name='minio-s3',
    chart='minio/minio',
    namespace='default',
    flags=[
        # Load secret values from created secret
        '--set=existingSecret=django-polls-minio',
        '--set=service.type=LoadBalancer',
        '--set=mode=standalone',
        '--set=persistence.enabled=false',
        '--set=replicas=1',
        '--set=consoleService.type=LoadBalancer',
        '--set=resources.requests.memory=256Mi',
        # Add buckets
        '--set=buckets[0].name=static,buckets[0].policy=public',
        '--set=buckets[1].name=media,buckets[1].policy=public',
    ],
    labels=['storage']
)

###
# Django
###

# Create a secrets from .env file for Django app
secret_create_generic('django-polls', from_env_file='.env')

# Build Django polls app
docker_build(
    'app/django-polls',
    context='./django-polls',
    live_update=[
        sync('django-polls', '/app'),
        run('cd /app && pip install -r requirements-dev.txt',
            trigger=['./django-polls/requirements.txt', './django-polls/requirements-dev.txt']),
        run('cd /app && python manage.py migrate',
            trigger=['./django-polls/polls/migrations']),
        run('cd /app && python manage.py collectstatic --noinput',
            trigger=['./django-polls/polls/static']),
    ],
)

# Kubernetes deployment
k8s_yaml('./django-polls/k8s.yml')

# Kubernetes create resource
k8s_resource(
    'django-polls',
    port_forwards=8000,
    resource_deps=['postgresql', 'minio'],
    labels=['app'],
)

###
# Custom commands for Tilt
###

# Add a button to quickly run a command in a pod
pod_exec_script = '''
    set -eu
    # get k8s pod name from tilt resource name
    POD_NAME="$(tilt get kubernetesdiscovery "$resource" -ojsonpath='{.status.pods[0].name}')"
    kubectl exec "$POD_NAME" -- $command
'''
cmd_button('podexec',
    argv=['sh', '-c', pod_exec_script],
    location=location.NAV,
    icon_name='',
    text='Exec in a pod',
    inputs=[
        text_input('resource'),
        text_input('command'),
    ]
)

# Add a button to quickly create a superuser
cmd_button('django-polls:createsuperuser',
    argv=['sh', '-c', pod_exec_script],
    env=[
        'resource=django-polls',
        'command=python manage.py createsuperuser --noinput --username {} --email {}'.format(
            os.getenv('DJANGO_SUPERUSER_USERNAME'),
            os.getenv('DJANGO_SUPERUSER_EMAIL')
        ),
    ],
    resource='django-polls',
    icon_name='login',
    text='create superuser',
)

# Add a button to run tests
cmd_button('django-polls:test',
    argv=['sh', '-c', pod_exec_script],
    env=[
        'resource=django-polls',
        'command=python manage.py test',
    ],
    resource='django-polls',
    icon_name='check_circle',
    text='run app tests',
)
```

When you run `tilt up` for the first time, Tilt will read .env file, set the environment variables and create secrets in the cluster for the Django app, PostgreSQL and minIO, then install Helm charts for PostgreSQL (with simple [adminer database management](https://www.adminer.org/)), MinIO (with init buckets) and deploy the Django application (migrations and collectstatic will be run automatically) using previously created secrets to the local Kubernetes cluster. This will take a few minutes. Subsequent runs of `tilt up` will be much faster.

```bash
# Start Tilt
tilt up
```

---

### Step 5 - Create a new superuser
From Tilt UI (http://localhost:10350), click from the left sidebar "django-polls" resource and then click the "create super user" button. This will create a new super user for the Django admin site with password from the `DJANGO_SUPERUSER_PASSWORD` environment variable.

![Tilt UI Create superuser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c5k56mek4wllr4ur3dc7.png)

---

### Step 6 - Access the Django admin site
You can access the Django admin site at https://localhost:8000/admin. Login using the username and password from the `DJANGO_SUPERUSER_USERNAME` and `DJANGO_SUPERUSER_PASSWORD` environment variables.

![Django admin site](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0978fu6ug863u107yjq.png)

---

### Step 7 - Access the MinIO Console
You can access the MinIO Console at https://localhost:9001. Login using the username and password from the `MINIO_ROOT_USER` and `MINIO_ROOT_PASSWORD` environment variables.

![MinIO Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqks87q4qm7vksy95hhs.png)

---

### Step 8 - Access the Adminer PostgreSQL database management
You can access the Adminer PostgreSQL database management at https://localhost:8080. Login using the username and password from the `POSTGRESQL_USER` and `POSTGRESQL_PASSWORD` environment variables.

![Adminer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xyd4w6nm0yry7qtzszlc.png)

---

### Step 9 - Stop Tilt
Tilt will remove the Helm charts, secrets and the Django application from the local Kubernetes cluster.
```bash
tilt down
```