Skip to main content

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.py at the repository root (or inside the monorepo subdirectory you set as Root Directory)
  • A requirements.txt, pyproject.toml, or Pipfile so Nixpacks can install your dependencies

What Vessl auto-detects

When Vessl sees manage.py it sets:

SettingAuto-filled value
Build engineNixpacks
Port8000
Build commandpython manage.py collectstatic --noinput
Start commandpython manage.py runserver 0.0.0.0:${PORT:-8000}
Post-deploy commandpython manage.py migrate
Use Gunicorn for production

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

  1. In your project, click New Application.
  2. Select your Django repository and branch.
  3. The wizard auto-detects Django — review the pre-filled commands.
  4. Update the Start command to use Gunicorn (recommended).
  5. 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:

  1. Click Add add-on → PostgreSQL.
  2. Choose Deploy new to let Vessl provision a dedicated instance.
  3. 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:

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):

settings.py
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:

settings.py
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
settings.py
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:

  1. Open the app → Overview → Connected Services → Add → Redis.
  2. 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:

settings.py
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

VariableExampleNotes
SECRET_KEYdjango-insecure-…Set a strong random value in production
DEBUGFalseAlways False in production
ALLOWED_HOSTSyour-app.vessl.shComma-separated list of allowed hostnames
DJANGO_SETTINGS_MODULEmyproject.settingsIf 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.