Skip to content

Deploying a Django App

What is Django?

Django is a high-level, open-source Python web framework that emphasizes rapid development, clean pragmatic design, and maintainability. Django follows the Model-View-Template (MVT) architectural pattern and includes comprehensive built-in features for building production-grade applications.

Key features include:

  • Object-Relational Mapping (ORM) for database abstraction
  • Automatic admin interface generation
  • URL routing and request handling
  • Template engine with template inheritance
  • Built-in authentication and permission system
  • Form handling and validation
  • Security features (CSRF protection, SQL injection prevention, XSS protection)
  • Session management and user authentication
  • Middleware support for request/response processing
  • Management commands for administrative tasks
  • Signals for decoupled event handling
  • Caching framework with multiple backends
  • Internationalization (i18n) and localization (l10n)
  • RESTful API support with Django REST Framework
  • Comprehensive testing framework
  • ORM support for PostgreSQL, MySQL, SQLite, and other databases

Django is ideal for building content management systems, e-commerce platforms, social networks, data-intensive applications, and any scalable web application requiring robust database abstraction and security.

Prerequisites

Before deploying a Django application to Klutch.sh, ensure you have:

  • Python 3.9+ installed on your local machine
  • pip or conda for dependency management
  • Git and a GitHub account
  • A Klutch.sh account with dashboard access
  • A database server (PostgreSQL recommended; can be deployed separately on Klutch.sh)
  • Basic understanding of Django project structure

Getting Started with Django

Step 1: Create Your Project Directory and Virtual Environment

Terminal window
mkdir my-django-app
cd my-django-app
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate

Step 2: Install Django and Dependencies

Terminal window
pip install django psycopg2-binary python-decouple gunicorn whitenoise

Key packages:

  • django: The web framework
  • psycopg2-binary: PostgreSQL database adapter
  • python-decouple: Environment variable management
  • gunicorn: Production WSGI server
  • whitenoise: Static file serving

Step 3: Create Your Django Project

Terminal window
django-admin startproject config .

This creates a project named config with the following structure:

config/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
manage.py

Step 4: Update Settings for Production

Edit config/settings.py to add environment variable support:

import os
from decouple import config
# Django settings
SECRET_KEY = config('SECRET_KEY', default='dev-secret-key-change-in-production')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',')
# Database configuration
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME', default='django_db'),
'USER': config('DB_USER', default='postgres'),
'PASSWORD': config('DB_PASSWORD', default=''),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
}
}
# Static files configuration
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Media files configuration
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Security settings
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=True, cast=bool)
SESSION_COOKIE_SECURE = config('SESSION_COOKIE_SECURE', default=True, cast=bool)
CSRF_COOKIE_SECURE = config('CSRF_COOKIE_SECURE', default=True, cast=bool)
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_SECURITY_POLICY = {
"default-src": ("'self'",),
}
# Logging configuration
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'root': {
'handlers': ['console'],
'level': 'INFO',
},
}

Step 5: Create Sample Models

Create an app:

Terminal window
python manage.py startapp blog

Edit blog/models.py:

from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=False)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
author = models.CharField(max_length=100)
email = models.EmailField()
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Comment by {self.author} on {self.post.title}"

Step 6: Create Views and URLs

Edit blog/views.py:

from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Post, Comment
from .forms import PostForm
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(published=True).order_by('-created_at')
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def health_check(request):
"""Health check endpoint for monitoring."""
return JsonResponse({
'status': 'healthy',
'service': 'django-app'
})

Create blog/urls.py:

from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
path('post/new/', views.PostCreateView.as_view(), name='post_create'),
]

Update config/urls.py:

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.views.generic import TemplateView
from blog.views import health_check
urlpatterns = [
path('admin/', admin.site.urls),
path('health/', health_check, name='health_check'),
path('blog/', include('blog.urls')),
path('', TemplateView.as_view(template_name='home.html'), name='home'),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Step 7: Create a Requirements File

Terminal window
pip freeze > requirements.txt

Your requirements.txt should contain:

Django==4.2.8
psycopg2-binary==2.9.9
python-decouple==3.8
gunicorn==21.2.0
whitenoise==6.6.0
djangorestframework==3.14.0

Step 8: Test Locally

Create a .env file for local development:

SECRET_KEY=your-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
DB_NAME=django_local
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=localhost
DB_PORT=5432
SECURE_SSL_REDIRECT=False
SESSION_COOKIE_SECURE=False
CSRF_COOKIE_SECURE=False

Run migrations and start the development server:

Terminal window
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

Test health check: curl http://localhost:8000/health/


Deploying Without a Dockerfile

Klutch.sh uses Nixpacks to automatically detect and build your Django application from your source code.

Prepare Your Repository

  1. Initialize a Git repository and commit your code:
Terminal window
git init
git add .
git commit -m "Initial Django app commit"
  1. Create a .gitignore file:
venv/
__pycache__/
*.pyc
*.pyo
*.egg-info/
.env
.DS_Store
staticfiles/
*.sqlite3
  1. Push to GitHub:
Terminal window
git remote add origin https://github.com/YOUR_USERNAME/my-django-app.git
git branch -M main
git push -u origin main

Deploy to Klutch.sh

  1. Log in to Klutch.sh dashboard.

  2. Click “Create a new project” and provide a project name.

  3. Inside your project, click “Create a new app”.

  4. Repository Configuration:

    • Select your GitHub repository containing the Django app
    • Select the branch to deploy (typically main)
  5. Traffic Settings:

    • Select “HTTP” as the traffic type
  6. Port Configuration:

    • Set the internal port to 8000 (the port the Django app listens on)
  7. Environment Variables: Set the following environment variables in the Klutch.sh dashboard:

    • SECRET_KEY: Your Django secret key (generate with python manage.py shell then from django.core.management.utils import get_random_secret_key; print(get_random_secret_key()))
    • DEBUG: Set to False for production
    • ALLOWED_HOSTS: Your production domain (e.g., example-app.klutch.sh)
    • DB_HOST: Your PostgreSQL host (e.g., postgres-app.klutch.sh)
    • DB_NAME: Database name
    • DB_USER: Database username
    • DB_PASSWORD: Database password
    • PYTHONUNBUFFERED: Set to 1 to ensure Python output is logged immediately
  8. Build and Start Commands: If you need to customize the build or start command, set these environment variables:

    • BUILD_COMMAND: Default runs pip install -r requirements.txt, but you can add migration commands:
      BUILD_COMMAND=pip install -r requirements.txt && python manage.py migrate && python manage.py collectstatic --noinput
    • START_COMMAND: Default is gunicorn config.wsgi:application --bind 0.0.0.0:$PORT
  9. Region, Compute, and Instances:

    • Choose your desired region for optimal latency
    • Select compute resources based on your application size (Starter for simple apps, Pro/Premium for high-traffic)
    • Set the number of instances (start with 1-2 for testing)
  10. Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.

  11. Once deployment completes, your app will be accessible at example-app.klutch.sh.

Verifying the Deployment

Test your deployed app:

Terminal window
curl https://example-app.klutch.sh/health/

You should receive:

{
"status": "healthy",
"service": "django-app"
}

Deploying With a Dockerfile

If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.

Create a Multi-Stage Dockerfile

Create a Dockerfile in your project root:

# Build stage
FROM python:3.11-slim as builder
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.11-slim
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
# Copy Python dependencies from builder
COPY --from=builder /root/.local /root/.local
# Set PATH to use pip from builder
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
# Copy application code
COPY . .
# Create non-root user for security
RUN useradd -m -u 1000 django_user && \
chown -R django_user:django_user /app
USER django_user
# Collect static files
RUN python manage.py collectstatic --noinput || true
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8000/health/ || exit 1
# Expose port
EXPOSE 8000
# Run migrations and start the application
CMD ["sh", "-c", "python manage.py migrate && gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --timeout 120"]

Deploy the Dockerfile Version

  1. Push your code with the Dockerfile to GitHub:
Terminal window
git add Dockerfile
git commit -m "Add Dockerfile for custom build"
git push
  1. Log in to Klutch.sh dashboard.

  2. Create a new app:

    • Select your GitHub repository and branch
    • Set traffic type to “HTTP”
    • Set the internal port to 8000
    • Add environment variables (same as Nixpacks deployment)
    • Click “Create”
  3. Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.


Database Configuration

PostgreSQL Setup

PostgreSQL is the recommended database for Django. To use a PostgreSQL instance with Klutch.sh:

  1. Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
  2. Get the connection details from the PostgreSQL dashboard
  3. Set the following environment variables in your Django app:
    • DB_HOST: PostgreSQL host
    • DB_NAME: Database name
    • DB_USER: Database user
    • DB_PASSWORD: Database password
    • DB_PORT: Database port (usually 5432)

Example PostgreSQL connection string:

postgresql://user:password@my-postgres-app.klutch.sh:5432/django_db

MySQL Configuration

For MySQL, update settings.py:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWORD'),
'HOST': config('DB_HOST'),
'PORT': config('DB_PORT', default='3306'),
}
}

Add to requirements:

mysqlclient==2.2.0

Environment Variables and Configuration

Essential Environment Variables

Configure these variables in the Klutch.sh dashboard:

VariableDescriptionExample
SECRET_KEYDjango secret key for cryptographic signingAuto-generate with Django utilities
DEBUGEnable debug mode (set to False in production)False
ALLOWED_HOSTSComma-separated list of allowed hostsexample-app.klutch.sh,www.example.com
DB_HOSTDatabase server hostnamepostgres-app.klutch.sh
DB_NAMEDatabase namedjango_production
DB_USERDatabase usernamedjango_user
DB_PASSWORDDatabase passwordGenerated secure password
DB_PORTDatabase port5432
PYTHONUNBUFFEREDEnable unbuffered logging1
SECURE_SSL_REDIRECTRedirect HTTP to HTTPSTrue

Customization Environment Variables (Nixpacks)

For Nixpacks deployments, customize build and runtime behavior:

VariablePurposeExample
BUILD_COMMANDRuns during buildpip install -r requirements.txt && python manage.py migrate && python manage.py collectstatic --noinput
START_COMMANDCommand to start appgunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 4

Static Files and Media Uploads

Static Files Configuration

  1. Update settings.py:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
  1. Collect static files:
Terminal window
python manage.py collectstatic --noinput
  1. Update BUILD_COMMAND to include static file collection:
BUILD_COMMAND=pip install -r requirements.txt && python manage.py migrate && python manage.py collectstatic --noinput

Media Uploads with Persistent Storage

  1. Update settings.py:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
  1. Add media serving in config/urls.py:
from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  1. On Klutch.sh, add persistent storage:
    • Mount path: /app/media
    • Size: 50 GB (adjust based on expected uploads)

Persistent Storage for Database and Files

Adding Persistent Volume

  1. In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
  2. Click “Add Volume”
  3. Set the mount path: /app/media (for media uploads) or /app/data (for application data)
  4. Set the size based on your needs (e.g., 50 GB for media, 10 GB for logs)
  5. Save and redeploy

Custom Domains

To serve your Django application from a custom domain:

  1. In the Klutch.sh app dashboard, navigate to “Custom Domains”
  2. Click “Add Custom Domain”
  3. Enter your domain (e.g., myapp.example.com)
  4. Follow the DNS configuration instructions provided
  5. Update Django’s ALLOWED_HOSTS with your custom domain

Example DNS configuration:

myapp.example.com CNAME example-app.klutch.sh

Update environment variable:

ALLOWED_HOSTS=myapp.example.com,www.myapp.example.com

Running Database Migrations

Migrations in Nixpacks Deployment

Set the BUILD_COMMAND to include migrations:

BUILD_COMMAND=pip install -r requirements.txt && python manage.py migrate && python manage.py collectstatic --noinput

Alternatively, run migrations manually:

  1. Connect to your app container (via Klutch.sh CLI or SSH)
  2. Run: python manage.py migrate

Migrations in Docker Deployment

The Dockerfile includes migrations in the startup command:

CMD ["sh", "-c", "python manage.py migrate && gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4"]

Monitoring and Logging

Application Logging

Configure logging in settings.py:

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
'file': {
'class': 'logging.FileHandler',
'filename': '/app/logs/django.log',
},
},
'root': {
'handlers': ['console', 'file'],
'level': 'INFO',
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
},
},
}

Performance Monitoring

  1. Check application logs in Klutch.sh dashboard
  2. Monitor response times and error rates
  3. Track database query performance

Health Checks

Add a health check endpoint:

from django.http import JsonResponse
def health_check(request):
return JsonResponse({
'status': 'healthy',
'service': 'django-app'
})

Register in urls.py:

path('health/', health_check, name='health_check'),

Security Best Practices

  1. Never commit secrets: Use environment variables for sensitive data
  2. Set DEBUG=False: Always disable debug mode in production
  3. Use HTTPS: Set SECURE_SSL_REDIRECT=True in production
  4. Secure cookies: Enable SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE
  5. CSRF protection: Django includes CSRF middleware by default
  6. SQL injection prevention: Always use ORM queries, avoid raw SQL
  7. XSS protection: Use template auto-escaping (enabled by default)
  8. Content Security Policy: Configure CSP headers
  9. Rate limiting: Implement rate limiting for API endpoints
  10. Security headers: Set security headers (X-Frame-Options, X-Content-Type-Options, etc.)

Example security configuration in settings.py:

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_SECURITY_POLICY = {
"default-src": ("'self'",),
}
X_FRAME_OPTIONS = 'DENY'
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

Troubleshooting

Issue 1: Database Connection Errors

Problem: Application fails to connect to database with “connection refused” error.

Solution:

  • Verify DB_HOST, DB_NAME, DB_USER, DB_PASSWORD are correct
  • Ensure PostgreSQL instance is running and accessible
  • Check firewall rules allow connection on port 5432
  • Test connection: psql -h $DB_HOST -U $DB_USER -d $DB_NAME

Issue 2: Static Files Not Loading

Problem: CSS, JavaScript, and images return 404 errors.

Solution:

  • Run python manage.py collectstatic --noinput
  • Verify STATIC_ROOT is set correctly
  • Check STATIC_URL matches your URL patterns
  • Ensure whitenoise is installed and enabled

Issue 3: Migration Errors

Problem: Application fails during python manage.py migrate

Solution:

  • Check migrations are properly created: python manage.py makemigrations
  • Review migration files for syntax errors
  • Check database user has permission to create tables
  • Run python manage.py showmigrations to see migration status

Issue 4: Secret Key Issues

Problem: “Secret key not set” error or secret key changes on restart.

Solution:

  • Generate a new secret key: python manage.py shell then from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())
  • Set SECRET_KEY environment variable in Klutch.sh dashboard
  • Keep the same secret key across deployments

Issue 5: ALLOWED_HOSTS Configuration

Problem: “Invalid HTTP_HOST header” error for custom domain.

Solution:

  • Add your domain to ALLOWED_HOSTS environment variable
  • Separate multiple domains with commas: example-app.klutch.sh,myapp.example.com
  • Use wildcard for subdomains: *.example.com (use cautiously)

Best Practices for Production Deployment

  1. Use a Production WSGI Server: Gunicorn with multiple workers

    gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --timeout 120
  2. Implement Caching: Use Redis or Memcached for session and cache storage

    CACHES = {
    'default': {
    'BACKEND': 'django_redis.cache.RedisCache',
    'LOCATION': 'redis://redis-host:6379/1',
    }
    }
  3. Enable Database Connection Pooling: Use PgBouncer or similar

    DATABASES = {
    'default': {
    'CONN_MAX_AGE': 600, # Connection pooling
    }
    }
  4. Asynchronous Task Processing: Use Celery for background tasks

    CELERY_BROKER_URL = 'redis://redis-host:6379/0'
    CELERY_RESULT_BACKEND = 'redis://redis-host:6379/1'
  5. Version Your Dependencies: Pin exact versions

    Django==4.2.8
    psycopg2-binary==2.9.9
  6. Implement Comprehensive Logging: Log application events

    logger = logging.getLogger(__name__)
    logger.info("Task completed successfully")
  7. Use Middleware for Security: Include security middleware

    MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    ...
    ]
  8. Regular Backups: Backup your database regularly

    Terminal window
    pg_dump -h $DB_HOST -U $DB_USER $DB_NAME > backup.sql
  9. Monitor Performance: Track query performance and response times

    from django.db import connection
    print(connection.queries) # In development only
  10. Automate Testing: Run tests in CI/CD pipeline

    Terminal window
    python manage.py test

Resources


Conclusion

Deploying Django applications to Klutch.sh provides a scalable, reliable platform for running production web applications. Whether you choose Nixpacks for simplicity or Docker for maximum control, Klutch.sh handles the infrastructure while you focus on application development.

Key takeaways:

  • Use Nixpacks for quick deployments with minimal configuration
  • Use Docker for custom build environments and advanced configurations
  • Configure environment variables for database connectivity and Django settings
  • Implement proper security settings (SECRET_KEY, DEBUG=False, HTTPS)
  • Use PostgreSQL for robust database support
  • Configure static files with whitenoise for efficient serving
  • Monitor application health and performance in production
  • Scale workers and instances based on traffic patterns
  • Implement comprehensive logging for debugging and monitoring

For additional help, refer to the Django documentation or Klutch.sh support resources.