If you've spent any time on r/django asking "how do I host my app on Coolify?" you've probably noticed the answers are short, scattered and end with "you'll figure it out." Coolify itself is great for Django, the framework is just enough of an outlier that nobody's written the proper guide. The Nixpacks build pack handles Python well enough, but Django's static files, media uploads and the WSGI server choice all need decisions that other frameworks don't ask of you.
This guide is the missing tutorial. We'll deploy a real Django app on Coolify with Postgres, Gunicorn, static file serving via WhiteNoise, persistent media uploads, Celery for background tasks and a production settings split. Written against Coolify v4.0.0 and Django 4.2/5.0/5.1.
If Coolify isn't running yet, the LumaDock Coolify VPS ships with v4.0.0 pre-installed, the dashboard is on port 8000 the moment provisioning finishes. The getting started guide covers initial dashboard access and securing the install.
What a production-shaped Django setup looks like
The Django docs are very good at "how to write Django" and pretty thin on "how to run Django in production." Here's the mental model we're shooting for.
The web tier runs Gunicorn (a WSGI server) with a few worker processes, each handling concurrent requests. Behind Gunicorn, Coolify's Traefik reverse proxy handles HTTPS and routes traffic to the container. Static files (CSS, JS, admin assets) are served by Django itself via WhiteNoise middleware, no separate nginx needed. Media files (user uploads) live on a persistent Docker volume or in S3-compatible storage. Postgres is the primary database. Redis acts as the cache and the Celery broker. Celery worker containers handle background jobs.
That sounds like a lot of moving parts. It is. The good news is each part is one Coolify resource, so you click through and end up with a clean setup.
Step 1, prepare your Django project
A few things need to be in place in your repo before Coolify can deploy it.
Pin your Python version
Add a .python-version file at the repo root containing your Python version (e.g. 3.12) or a runtime.txt file with python-3.12.5. Coolify's Nixpacks reads either of these and picks the right Python for your build.
Use a requirements file or pyproject.toml
Coolify detects requirements.txt at the repo root and runs pip install -r requirements.txt automatically. If you're using Poetry, a pyproject.toml with poetry.lock works. If you're using uv (which is increasingly common in 2026), Coolify's Nixpacks supports it too via pyproject.toml with uv.lock.
Your requirements should include at minimum:
Django>=5.0,<5.2
gunicorn
psycopg[binary]
whitenoise
redis
celery
django-redis
python-decouple # or environs or python-dotenv
Pin versions appropriately for your project. The psycopg[binary] package is the modern Postgres driver for Django (Django 4.2+ supports it natively, fall back to psycopg2-binary for older versions).
Add a production settings file
The cleanest pattern is to split settings.py into settings/base.py, settings/development.py and settings/production.py, with environment-specific imports. Set DJANGO_SETTINGS_MODULE as a Coolify environment variable to yourapp.settings.production.
The production settings file should at minimum read everything sensitive from environment variables:
import os
from .base import *
DEBUG = False
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',')
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': os.environ['DB_HOST'],
'PORT': os.environ.get('DB_PORT', '5432'),
'CONN_MAX_AGE': 600,
}
}
# Static files via WhiteNoise
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
] + MIDDLEWARE
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Media files on persistent volume
MEDIA_ROOT = '/data/media'
MEDIA_URL = '/media/'
# Redis cache and Celery
REDIS_HOST = os.environ['REDIS_HOST']
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': f'redis://{REDIS_HOST}:6379/1',
}
}
CELERY_BROKER_URL = f'redis://{REDIS_HOST}:6379/0'
CELERY_RESULT_BACKEND = f'redis://{REDIS_HOST}:6379/2'
# Trust the proxy headers from Traefik
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
The SECURE_PROXY_SSL_HEADER bit is critical. Without it, Django thinks every request is HTTP because Traefik handles HTTPS termination upstream, which causes infinite redirect loops if you have SECURE_SSL_REDIRECT on.
Add an entrypoint script (optional but cleaner)
For Django apps that need migrations, collectstatic and superuser creation on first deploy, an entrypoint script is cleaner than stuffing everything into the start command. Create entrypoint.sh at the repo root:
#!/bin/sh
set -e
python manage.py migrate --noinput
python manage.py collectstatic --noinput --clear
exec gunicorn yourapp.wsgi:application --bind 0.0.0.0:8000 --workers 3 --threads 2 --timeout 60
Make it executable (chmod +x entrypoint.sh) and commit it. We'll set Coolify to run this as the start command.
Step 2, set up the project and connect your repo
From the Coolify dashboard, set up a Sources entry for GitHub if needed (Sources → Add → GitHub App, authorize and install on the repo's org or account). The Coolify GitHub guide covers the App setup in detail. Create a project (call it your app's name) and inside the project click Add a new resource → Private Repository (with GitHub App). Pick your repo, pick the production branch.
Coolify scans the repo, sees Python and the requirements file, picks the Nixpacks Python build pack. The default start command Nixpacks generates for Django won't be quite right (it tries to detect WSGI but often guesses the wrong module path), so we'll override it.
Step 3, add Postgres
Inside your project, click Add a new resource → Database → PostgreSQL. Pick Postgres 16 (or match whatever version you've been developing against). Accept the auto-generated credentials, click Start.
Note the resource name (something like postgres-database-abc123). This is the internal Docker network hostname your Django app will use to reach the database.
Step 4, add Redis
Same project, click Add a new resource → Database → Redis. Default settings are fine. Click Start. Note the resource name. Redis serves as both cache and the Celery broker, one resource handles both with different DB numbers (we set this up in the production settings, DB 0 for Celery broker, DB 1 for Django cache, DB 2 for Celery results).
Step 5, set environment variables
Open your Django application in Coolify, go to Environment Variables, click Bulk Edit. Paste:
DJANGO_SETTINGS_MODULE=yourapp.settings.production
DJANGO_SECRET_KEY=generate-a-real-random-50-char-string
DJANGO_ALLOWED_HOSTS=app.example.com,www.app.example.com
DEBUG=False
DB_NAME=your-postgres-db-name
DB_USER=your-postgres-user
DB_PASSWORD=your-generated-postgres-password
DB_HOST=postgres-database-abc123
DB_PORT=5432
REDIS_HOST=redis-database-xyz789
# If you use S3 for media files
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
AWS_STORAGE_BUCKET_NAME=your-bucket
AWS_S3_REGION_NAME=auto
AWS_S3_ENDPOINT_URL=https://your-r2-or-b2-endpoint
Substitute real values. Generate DJANGO_SECRET_KEY locally with python -c "import secrets; print(secrets.token_urlsafe(50))" and paste the result. Don't commit it.
For multi-line values (private keys), uncheck Is build variable on those entries to avoid Dockerfile parser issues at build time.
Step 6, set the start command
In the application's General tab, set Start Command to:
./entrypoint.sh
This runs the script we wrote in step 1, which handles migrations, collectstatic and starts Gunicorn. The Gunicorn process binds to 0.0.0.0:8000 inside the container and Coolify's Traefik routes external traffic to that port.
If you'd rather not use an entrypoint script, you can put the same commands in Coolify's Pre-deployment Command (migrations and collectstatic) and set the start command directly to gunicorn yourapp.wsgi:application --bind 0.0.0.0:8000 --workers 3 --threads 2 --timeout 60. Either approach works, the entrypoint script is just easier to version-control.
Set the Ports Exposes field to 8000 so Traefik knows which port to route to.
Step 7, set up persistent storage for media files
If your Django app accepts user uploads (anything using FileField or ImageField) you need persistent storage. The container's filesystem resets on every deploy, so without a volume, uploads vanish.
In your application's settings find Storages. Add a persistent storage entry with mount path /data/media and a name like django-media. Save. Coolify creates a Docker volume on the host and mounts it into the container at that path. This matches the MEDIA_ROOT we set in production settings.
For larger setups, prefer S3-compatible storage. Set DEFAULT_FILE_STORAGE in your production settings to a custom backend pointing at Cloudflare R2, Backblaze B2, Wasabi or self-hosted MinIO. The django-storages package makes this a few-line config change. Your uploads land outside the VPS, your VPS disk doesn't fill up and uploads survive even a full server replacement. For most production apps this is the right call, persistent volumes are easier to set up but harder to scale.
Step 8, deploy and verify
Click Deploy. Coolify pulls your repo, runs the Nixpacks build (pip install, then any other build steps), runs your entrypoint script (migrations + collectstatic + Gunicorn), the container starts and Traefik picks it up.
The first build takes 3-5 minutes because pip downloads everything fresh. Subsequent builds use the layer cache and complete in 60-90 seconds for typical apps. Watch the Deployments tab for build errors.
Once deployed, hit the Coolify-generated subdomain. Verify:
- The home page loads (no
DisallowedHosterror fromALLOWED_HOSTS) - Static files load (CSS, admin styles, no broken pages)
- Database queries work (Django admin login, list views)
- File uploads succeed and persist after a restart
- Sessions persist (you stay logged into admin)
If collectstatic fails during deploy, check that STATIC_ROOT in your settings matches what WhiteNoise expects (usually BASE_DIR / 'staticfiles'). If admin styles are missing, WhiteNoise isn't serving them, double-check the middleware order (WhiteNoise must come right after SecurityMiddleware, before everything else).
Step 9, run Celery as a separate worker
If your app has any background tasks (sending emails, processing uploads, scheduled imports), Celery is the standard answer. Run it as a separate Coolify resource so it doesn't share lifecycle with the web tier.
Inside your project, click Add a new resource → Private Repository (with GitHub App). Pick the same repo and branch as your web app. In the General tab set the start command to:
celery -A yourapp worker --loglevel=info --concurrency=4
Add the same environment variables as the web app (Coolify lets you copy env vars between resources). Skip the pre-deployment command, this resource doesn't need migrations to run.
Deploy. Now background tasks run in their own container that doesn't restart on web deploys, which means jobs in flight aren't interrupted.
If you also need Celery Beat (scheduled tasks), create another Coolify resource the same way with start command celery -A yourapp beat --loglevel=info. Beat is a single-process scheduler and you only ever want one instance of it running, so don't scale this resource beyond one container.
Step 10, add a custom domain and verify HTTPS
Open the web app in Coolify, go to Domains, add your real domain. Save. Update the A record at your DNS provider to point at your VPS IP.
Coolify's Traefik issues a Let's Encrypt cert within a couple of minutes once DNS resolves. Watch the application's Logs tab for ACME success. If something fails, the Coolify SSL guide covers wildcard certs, custom certs and the common ACME failure modes.
Update your DJANGO_ALLOWED_HOSTS environment variable to include the new production domain (and any www variant). Redeploy the application so the new value takes effect. Without this, Django returns 400 errors for the production domain because it doesn't recognize the host.
Performance tuning, what actually matters
Tune Gunicorn workers
The default of 3 workers + 2 threads in our entrypoint is fine for most apps. The general rule for sync workers is (2 × CPU cores) + 1. On a 2 vCPU VPS that's 5 workers. Each worker uses 50-100 MB of memory, so 5 workers is 250-500 MB just for Django, which is a meaningful chunk of a 4 GB VPS. Tune accordingly.
For high-concurrency apps, switch from sync workers to gevent workers (--worker-class=gevent) which handle thousands of concurrent connections per worker process. The trade-off is that any synchronous database call blocks the worker, so you need to be disciplined about not blocking the event loop.
Use Redis caching aggressively
The Django ORM is slow on its own. The fix is to cache view responses (cache_page decorator), template fragments ({% cache %}) and querysets (cache.get_or_set). The Redis instance you already spun up handles this, just make sure your CACHES setting points at it.
Enable database connection pooling
The CONN_MAX_AGE: 600 setting in our production settings keeps Postgres connections alive for 10 minutes between requests. Without it, every request opens a new connection, which adds 5-15 ms of latency per request and chews through Postgres's connection limit. For very high-concurrency apps, use PgBouncer as a connection pooler, deployed as another Coolify resource.
Common errors and how to fix them
DisallowedHost: Invalid HTTP_HOST header
Your domain isn't in ALLOWED_HOSTS. Update the DJANGO_ALLOWED_HOSTS environment variable in Coolify to include your production domain (comma-separated for multiple). Don't use ['*'] in production, that's a security risk.
500 errors with no useful logs
Your DEBUG setting is False (correctly) but you don't have a logging configuration that surfaces errors. Add Django's default logging config to your production settings, pointing at stdout so Docker captures the output and Coolify shows it in the Logs tab. Use Django's logging docs as a starting point.
Static files return 404
Either WhiteNoise isn't installed (check requirements.txt and the middleware order in settings) or collectstatic didn't run (check the deploy logs for the collectstatic step) or the STATIC_ROOT path mismatch between settings and WhiteNoise. The middleware must be right after SecurityMiddleware, the storage backend must be set to CompressedManifestStaticFilesStorage and collectstatic --noinput --clear must run as part of the deploy.
Database connection refused
DB_HOST is wrong. Use the Postgres resource name from Coolify (visible in the resource's General tab), not localhost. Also confirm the Postgres container is actually running, sometimes it crashes on first start due to a credential mismatch.
Media uploads vanish after deploy
You don't have persistent storage mounted on /data/media (or wherever your MEDIA_ROOT points). See step 7. Or move uploads to S3-compatible storage entirely with django-storages.
Celery tasks aren't running
The Celery worker resource isn't deployed or its start command is wrong or it's connecting to the wrong Redis instance. Check the worker's Logs tab. The worker should log "ready" once started and "Task received" lines when there are jobs to process.
SSL works, app loads, but POST requests get 403 CSRF errors
Add your production domain to CSRF_TRUSTED_ORIGINS in your production settings. Django 4.0+ requires explicit CSRF origins for non-form-submitted requests. The format includes the protocol: ['https://app.example.com'].
Build fails with psycopg or pip install errors
The psycopg2-binary package needs build tools that Nixpacks may not include by default. Use psycopg[binary] (the v3 driver) instead, which ships pre-built wheels for most platforms. If you're stuck on psycopg2 for compatibility reasons, switch to a Dockerfile build with the right system dependencies installed.
Where to go from here
Once your Django app is shipping, set up automated database backups (the database backups guide handles Postgres backups to S3-compatible storage), monitoring (the Coolify monitoring guide works with Django through the django-prometheus exporter) and security hardening (the production server security guide covers SSH, firewall and Fail2ban patterns that apply regardless of runtime).

