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
mkdir my-django-appcd my-django-apppython3 -m venv venvsource venv/bin/activate # On Windows: venv\Scripts\activateStep 2: Install Django and Dependencies
pip install django psycopg2-binary python-decouple gunicorn whitenoiseKey packages:
django: The web frameworkpsycopg2-binary: PostgreSQL database adapterpython-decouple: Environment variable managementgunicorn: Production WSGI serverwhitenoise: Static file serving
Step 3: Create Your Django Project
django-admin startproject config .This creates a project named config with the following structure:
config/ __init__.py settings.py urls.py asgi.py wsgi.pymanage.pyStep 4: Update Settings for Production
Edit config/settings.py to add environment variable support:
import osfrom decouple import config
# Django settingsSECRET_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 configurationDATABASES = { '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 configurationSTATIC_URL = '/static/'STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Media files configurationMEDIA_URL = '/media/'MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Security settingsSECURE_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 = TrueSECURE_CONTENT_SECURITY_POLICY = { "default-src": ("'self'",),}
# Logging configurationLOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'root': { 'handlers': ['console'], 'level': 'INFO', },}Step 5: Create Sample Models
Create an app:
python manage.py startapp blogEdit blog/models.py:
from django.db import modelsfrom 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_404from django.views.generic import ListView, DetailView, CreateViewfrom django.contrib.auth.mixins import LoginRequiredMixinfrom .models import Post, Commentfrom .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 pathfrom . 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 adminfrom django.urls import path, includefrom django.conf import settingsfrom django.conf.urls.static import staticfrom django.views.generic import TemplateViewfrom 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
pip freeze > requirements.txtYour requirements.txt should contain:
Django==4.2.8psycopg2-binary==2.9.9python-decouple==3.8gunicorn==21.2.0whitenoise==6.6.0djangorestframework==3.14.0Step 8: Test Locally
Create a .env file for local development:
SECRET_KEY=your-secret-key-hereDEBUG=TrueALLOWED_HOSTS=localhost,127.0.0.1DB_NAME=django_localDB_USER=postgresDB_PASSWORD=postgresDB_HOST=localhostDB_PORT=5432SECURE_SSL_REDIRECT=FalseSESSION_COOKIE_SECURE=FalseCSRF_COOKIE_SECURE=FalseRun migrations and start the development server:
python manage.py migratepython manage.py createsuperuserpython manage.py runserverTest 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
- Initialize a Git repository and commit your code:
git initgit add .git commit -m "Initial Django app commit"- Create a
.gitignorefile:
venv/__pycache__/*.pyc*.pyo*.egg-info/.env.DS_Storestaticfiles/*.sqlite3- Push to GitHub:
git remote add origin https://github.com/YOUR_USERNAME/my-django-app.gitgit branch -M maingit push -u origin mainDeploy to Klutch.sh
-
Log in to Klutch.sh dashboard.
-
Click “Create a new project” and provide a project name.
-
Inside your project, click “Create a new app”.
-
Repository Configuration:
- Select your GitHub repository containing the Django app
- Select the branch to deploy (typically
main)
-
Traffic Settings:
- Select “HTTP” as the traffic type
-
Port Configuration:
- Set the internal port to 8000 (the port the Django app listens on)
-
Environment Variables: Set the following environment variables in the Klutch.sh dashboard:
SECRET_KEY: Your Django secret key (generate withpython manage.py shellthenfrom django.core.management.utils import get_random_secret_key; print(get_random_secret_key()))DEBUG: Set toFalsefor productionALLOWED_HOSTS: Your production domain (e.g.,example-app.klutch.sh)DB_HOST: Your PostgreSQL host (e.g.,postgres-app.klutch.sh)DB_NAME: Database nameDB_USER: Database usernameDB_PASSWORD: Database passwordPYTHONUNBUFFERED: Set to1to ensure Python output is logged immediately
-
Build and Start Commands: If you need to customize the build or start command, set these environment variables:
BUILD_COMMAND: Default runspip 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 --noinputSTART_COMMAND: Default isgunicorn config.wsgi:application --bind 0.0.0.0:$PORT
-
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)
-
Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.
-
Once deployment completes, your app will be accessible at
example-app.klutch.sh.
Verifying the Deployment
Test your deployed app:
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 stageFROM python:3.11-slim as builder
WORKDIR /app
# Install system dependenciesRUN 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 dependenciesCOPY requirements.txt .RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stageFROM python:3.11-slim
WORKDIR /app
# Install runtime dependenciesRUN apt-get update && apt-get install -y --no-install-recommends \ libpq5 \ && rm -rf /var/lib/apt/lists/*
# Copy Python dependencies from builderCOPY --from=builder /root/.local /root/.local
# Set PATH to use pip from builderENV PATH=/root/.local/bin:$PATHENV PYTHONUNBUFFERED=1
# Copy application codeCOPY . .
# Create non-root user for securityRUN useradd -m -u 1000 django_user && \ chown -R django_user:django_user /app
USER django_user
# Collect static filesRUN python manage.py collectstatic --noinput || true
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD curl -f http://localhost:8000/health/ || exit 1
# Expose portEXPOSE 8000
# Run migrations and start the applicationCMD ["sh", "-c", "python manage.py migrate && gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --timeout 120"]Deploy the Dockerfile Version
- Push your code with the Dockerfile to GitHub:
git add Dockerfilegit commit -m "Add Dockerfile for custom build"git push-
Log in to Klutch.sh dashboard.
-
- 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”
-
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:
- Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
- Get the connection details from the PostgreSQL dashboard
- Set the following environment variables in your Django app:
DB_HOST: PostgreSQL hostDB_NAME: Database nameDB_USER: Database userDB_PASSWORD: Database passwordDB_PORT: Database port (usually 5432)
Example PostgreSQL connection string:
postgresql://user:password@my-postgres-app.klutch.sh:5432/django_dbMySQL 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.0Environment Variables and Configuration
Essential Environment Variables
Configure these variables in the Klutch.sh dashboard:
| Variable | Description | Example |
|---|---|---|
SECRET_KEY | Django secret key for cryptographic signing | Auto-generate with Django utilities |
DEBUG | Enable debug mode (set to False in production) | False |
ALLOWED_HOSTS | Comma-separated list of allowed hosts | example-app.klutch.sh,www.example.com |
DB_HOST | Database server hostname | postgres-app.klutch.sh |
DB_NAME | Database name | django_production |
DB_USER | Database username | django_user |
DB_PASSWORD | Database password | Generated secure password |
DB_PORT | Database port | 5432 |
PYTHONUNBUFFERED | Enable unbuffered logging | 1 |
SECURE_SSL_REDIRECT | Redirect HTTP to HTTPS | True |
Customization Environment Variables (Nixpacks)
For Nixpacks deployments, customize build and runtime behavior:
| Variable | Purpose | Example |
|---|---|---|
BUILD_COMMAND | Runs during build | pip install -r requirements.txt && python manage.py migrate && python manage.py collectstatic --noinput |
START_COMMAND | Command to start app | gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers 4 |
Static Files and Media Uploads
Static Files Configuration
- Update
settings.py:
STATIC_URL = '/static/'STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'- Collect static files:
python manage.py collectstatic --noinput- Update
BUILD_COMMANDto include static file collection:
BUILD_COMMAND=pip install -r requirements.txt && python manage.py migrate && python manage.py collectstatic --noinputMedia Uploads with Persistent Storage
- Update
settings.py:
MEDIA_URL = '/media/'MEDIA_ROOT = os.path.join(BASE_DIR, 'media')- Add media serving in
config/urls.py:
from django.conf import settingsfrom django.conf.urls.static import static
if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)- On Klutch.sh, add persistent storage:
- Mount path:
/app/media - Size:
50 GB(adjust based on expected uploads)
- Mount path:
Persistent Storage for Database and Files
Adding Persistent Volume
- In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
- Click “Add Volume”
- Set the mount path:
/app/media(for media uploads) or/app/data(for application data) - Set the size based on your needs (e.g., 50 GB for media, 10 GB for logs)
- Save and redeploy
Custom Domains
To serve your Django application from a custom domain:
- In the Klutch.sh app dashboard, navigate to “Custom Domains”
- Click “Add Custom Domain”
- Enter your domain (e.g.,
myapp.example.com) - Follow the DNS configuration instructions provided
- Update Django’s
ALLOWED_HOSTSwith your custom domain
Example DNS configuration:
myapp.example.com CNAME example-app.klutch.shUpdate environment variable:
ALLOWED_HOSTS=myapp.example.com,www.myapp.example.comRunning 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 --noinputAlternatively, run migrations manually:
- Connect to your app container (via Klutch.sh CLI or SSH)
- 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
- Check application logs in Klutch.sh dashboard
- Monitor response times and error rates
- 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
- Never commit secrets: Use environment variables for sensitive data
- Set DEBUG=False: Always disable debug mode in production
- Use HTTPS: Set
SECURE_SSL_REDIRECT=Truein production - Secure cookies: Enable
SESSION_COOKIE_SECUREandCSRF_COOKIE_SECURE - CSRF protection: Django includes CSRF middleware by default
- SQL injection prevention: Always use ORM queries, avoid raw SQL
- XSS protection: Use template auto-escaping (enabled by default)
- Content Security Policy: Configure CSP headers
- Rate limiting: Implement rate limiting for API endpoints
- Security headers: Set security headers (X-Frame-Options, X-Content-Type-Options, etc.)
Example security configuration in settings.py:
SECURE_SSL_REDIRECT = TrueSESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = TrueSECURE_BROWSER_XSS_FILTER = TrueSECURE_CONTENT_SECURITY_POLICY = { "default-src": ("'self'",),}X_FRAME_OPTIONS = 'DENY'SECURE_HSTS_SECONDS = 31536000 # 1 yearSECURE_HSTS_INCLUDE_SUBDOMAINS = TrueSECURE_HSTS_PRELOAD = TrueTroubleshooting
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_PASSWORDare 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_ROOTis set correctly - Check
STATIC_URLmatches 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 showmigrationsto 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 shellthenfrom django.core.management.utils import get_random_secret_key; print(get_random_secret_key()) - Set
SECRET_KEYenvironment 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_HOSTSenvironment 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
-
Use a Production WSGI Server: Gunicorn with multiple workers
gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --timeout 120 -
Implement Caching: Use Redis or Memcached for session and cache storage
CACHES = {'default': {'BACKEND': 'django_redis.cache.RedisCache','LOCATION': 'redis://redis-host:6379/1',}} -
Enable Database Connection Pooling: Use PgBouncer or similar
DATABASES = {'default': {'CONN_MAX_AGE': 600, # Connection pooling}} -
Asynchronous Task Processing: Use Celery for background tasks
CELERY_BROKER_URL = 'redis://redis-host:6379/0'CELERY_RESULT_BACKEND = 'redis://redis-host:6379/1' -
Version Your Dependencies: Pin exact versions
Django==4.2.8psycopg2-binary==2.9.9 -
Implement Comprehensive Logging: Log application events
logger = logging.getLogger(__name__)logger.info("Task completed successfully") -
Use Middleware for Security: Include security middleware
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','whitenoise.middleware.WhiteNoiseMiddleware',...] -
Regular Backups: Backup your database regularly
Terminal window pg_dump -h $DB_HOST -U $DB_USER $DB_NAME > backup.sql -
Monitor Performance: Track query performance and response times
from django.db import connectionprint(connection.queries) # In development only -
Automate Testing: Run tests in CI/CD pipeline
Terminal window python manage.py test
Resources
- Django Official Documentation
- Django Models Documentation
- Django Settings Documentation
- Django Admin Interface
- Django Migrations
- Gunicorn WSGI Server
- PostgreSQL Documentation
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.