Deploying Django on Vessl
Vessl detects Django automatically (by the presence of manage.py in your repository root) and pre-fills build and start commands. This guide walks through a production-ready Django deployment with PostgreSQL, static files, and background workers.
Prerequisites
- A server added and provisioned in Vessl
- A Django repository on GitHub
manage.pyat the repository root (or inside the monorepo subdirectory you set as Root Directory)- A
requirements.txt,pyproject.toml, orPipfileso Nixpacks can install your dependencies
What Vessl auto-detects
When Vessl sees manage.py it sets:
| Setting | Auto-filled value |
|---|---|
| Build engine | Nixpacks |
| Port | 8000 |
| Build command | python manage.py collectstatic --noinput |
| Start command | python manage.py runserver 0.0.0.0:${PORT:-8000} |
| Post-deploy command | python manage.py migrate |
runserver is Django's development server — it's single-threaded and not safe for production traffic. Replace the start command with Gunicorn:
gunicorn myproject.wsgi:application --bind 0.0.0.0:${PORT:-8000} --workers 2
Replace myproject with your project's Python module name (the directory containing wsgi.py).
Step 1 — Create the app
- In your project, click New Application.
- Select your Django repository and branch.
- The wizard auto-detects Django — review the pre-filled commands.
- Update the Start command to use Gunicorn (recommended).
- Click Deploy.
Step 2 — Add a database
Django requires a database for migrations and most application data. Open the app → Overview → Connected Services and click Add:
- Click Add add-on → PostgreSQL.
- Choose Deploy new to let Vessl provision a dedicated instance.
- Click Attach.
Vessl injects these variables automatically:
DB_CONNECTION=pgsql
DB_HOST=<internal hostname>
DB_PORT=5432
DB_DATABASE=<db name>
DB_USERNAME=<username>
DB_PASSWORD=<password>
DATABASE_URL=postgresql://<user>:<pass>@<host>:5432/<db>
Django reads DATABASE_URL natively via dj-database-url, or you can map the individual variables in settings.py:
import os
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_DATABASE'],
'USER': os.environ['DB_USERNAME'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': os.environ['DB_HOST'],
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
Or, using dj-database-url (add it to requirements.txt):
import dj_database_url
DATABASES = {
'default': dj_database_url.config(default=os.environ['DATABASE_URL'])
}
The post-deploy command (python manage.py migrate) runs automatically on every deploy.
Step 3 — Static files
Vessl runs python manage.py collectstatic --noinput at build time. Make sure your settings.py has STATIC_ROOT set:
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATIC_URL = '/static/'
To serve collected static files from Django itself (simple, works for most apps), install whitenoise:
pip install whitenoise
MIDDLEWARE = [
'whitenoise.middleware.WhiteNoiseMiddleware',
# … rest of middleware
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Add whitenoise to your requirements.txt, then redeploy.
Step 4 — Add Redis (optional)
For Django Channels (WebSockets), Celery, or cache backends:
- Open the app → Overview → Connected Services → Add → Redis.
- Vessl injects:
REDIS_URL=redis://:<password>@<host>:6379
REDIS_HOST=<internal hostname>
REDIS_PORT=6379
REDIS_PASSWORD=<password>
Update your settings.py to use Redis for cache and/or Channels:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': os.environ['REDIS_URL'],
}
}
# Django Channels
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [os.environ['REDIS_URL']],
},
}
}
Step 5 — Celery workers
If you use Celery for background tasks, add a daemon from the Daemons tab:
- Command:
celery -A myproject worker --loglevel=info - Restart policy:
always
For Celery Beat (periodic tasks), add a second daemon:
- Command:
celery -A myproject beat --loglevel=info - Restart policy:
always
Replace myproject with your Django project module name. Both daemons share the same environment variables as your web app, including REDIS_URL for the broker.
Step 6 — Monorepo / subdirectory setup
If manage.py lives in a subdirectory (e.g. backend/), set the Root Directory field to backend in the wizard. All commands run relative to that path.
Environment variables reference
| Variable | Example | Notes |
|---|---|---|
SECRET_KEY | django-insecure-… | Set a strong random value in production |
DEBUG | False | Always False in production |
ALLOWED_HOSTS | your-app.vessl.sh | Comma-separated list of allowed hostnames |
DJANGO_SETTINGS_MODULE | myproject.settings | If you have multiple settings files |
Common issues
DisallowedHost error
Django rejects requests to unrecognized hostnames. Add your Vessl subdomain (and custom domain, if any) to ALLOWED_HOSTS:
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
Then set ALLOWED_HOSTS=your-app.vessl.sh in the Variables tab.
collectstatic fails: "No module named …"
The build command runs in the same Python environment as your app. Make sure all packages referenced in settings.py (e.g. storages, whitenoise) are in requirements.txt.
ModuleNotFoundError in post-deploy command
Post-deploy commands run inside the deployed container using a login shell, so your virtual environment is active. If you still see import errors, make sure the package is in requirements.txt and not just installed locally.
Static files return 404 after deploy
Make sure collectstatic ran (it's in the build command by default) and WhiteNoise is added to MIDDLEWARE before django.middleware.common.CommonMiddleware.
Database connection refused The database container may still be starting on first attach. The post-deploy command retries automatically, but if the migration still fails, trigger a manual redeploy from the Deployments tab.