Django Deployment & DevOps
Downloaded from PrepIQ (https://prepiq.online)
Q1. Why can't you use Django's runserver in production, and what should you use instead?
Django's development server (manage.py runserver) is explicitly single-threaded by default, unoptimized for performance, and lacks the security hardening, connection handling, and process management needed to serve real traffic — the Django documentation itself warns it must never be used in production.
Production deployments use a dedicated WSGI application server such as Gunicorn or uWSGI to run the Django application with multiple worker processes handling requests concurrently, typically placed behind a reverse proxy like Nginx that handles TLS termination, serves static files efficiently, and buffers slow client connections before they reach the application workers.
gunicorn myproject.wsgi:application --workers 4 --bind 0.0.0.0:8000
Q2. What is the difference between WSGI and ASGI, and when do you need ASGI for a Django project?
WSGI (Web Server Gateway Interface) is the traditional synchronous interface between a Python web application and its server: one worker handles one request at a time, blocking until it completes, which is simple and battle-tested for standard request/response HTTP apps. ASGI (Asynchronous Server Gateway Interface) is its successor, supporting asynchronous request handling, long-lived connections, and protocols beyond plain HTTP.
ASGI is required specifically when a Django project uses Django Channels for WebSockets or other persistent connections (live chat, real-time notifications, live dashboards), or when using async views/middleware to concurrently fan out to multiple external services. A typical CRUD-only Django app with no real-time features has no strong need for ASGI and can stay on WSGI with Gunicorn.
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns)),
})
Q3. How does collectstatic work and how should static files be served in production?
During development, Django's runserver automatically serves static files (CSS, JS, images) from each app's static/ directory, but this behavior is disabled when DEBUG=False since Django itself is not meant to serve static assets efficiently in production. manage.py collectstatic walks every app's static directory and copies all files into a single location defined by STATIC_ROOT.
That consolidated directory is then served directly by Nginx (or another web server) bypassing the Django/Gunicorn application process entirely, which is far faster since a dedicated web server is optimized for serving files. A common alternative is WhiteNoise, a middleware that lets Gunicorn serve compressed, cache-friendly static files directly without needing a separate Nginx static-file configuration.
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
python manage.py collectstatic --noinput
Q4. How do you manage environment-specific settings and secrets in Django (django-environ, .env files)?
Hardcoding secrets (database passwords, API keys, SECRET_KEY) directly in settings.py is a serious security risk, especially if the file is committed to version control, and makes it impossible to use different values across development, staging, and production without editing code.
The standard pattern is to read configuration from environment variables at runtime, using a library like django-environ or python-decouple to parse a local .env file (excluded from git via .gitignore) during development, while production environments inject real environment variables directly through the hosting platform — the application code never changes between environments, only the values it reads.
import environ
env = environ.Env()
environ.Env.read_env()
SECRET_KEY = env('SECRET_KEY')
DEBUG = env.bool('DEBUG', default=False)
DATABASES = {'default': env.db()}
Q5. How do you dockerize a Django application, and what does a typical Dockerfile look like?
Dockerizing Django packages the application, its exact Python dependencies, and system libraries into a single portable image that runs identically across a developer's laptop, CI pipeline, and production servers, eliminating "works on my machine" environment drift.
A typical Dockerfile starts from a slim Python base image, installs dependencies from requirements.txt (cached as a separate layer so code changes don't force a full dependency reinstall), copies the application code, runs collectstatic, and starts Gunicorn instead of runserver. This is usually paired with docker-compose for local development to also run PostgreSQL and Redis as separate linked containers.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
Q6. What are Django database migrations and how do you safely run them in a zero-downtime deployment?
Migrations are Django's version-controlled way of evolving the database schema to match model changes over time, generated with makemigrations and applied with migrate; each migration file is a Python module describing schema operations that Django can apply forward or, in many cases, roll back.
In a zero-downtime deployment with multiple running instances, migrations must be backward-compatible during the rollout window: old code and new code briefly run side-by-side against the same database, so a migration that drops a column the old code still reads will break the old instances before they're replaced. The safe pattern is splitting risky changes into multiple deploys — add the new column as nullable first, deploy code that writes to both old and new fields, backfill data, then only remove the old field in a later deploy once no running code depends on it.
python manage.py makemigrations
python manage.py migrate --plan
Q7. What Django settings must be changed for production (DEBUG, ALLOWED_HOSTS, SECRET_KEY, security headers)?
DEBUG must be set to False in production; leaving it True exposes detailed stack traces, local variable values, and settings to any visitor who triggers an error, a serious information disclosure vulnerability. ALLOWED_HOSTS must list the actual production domain(s) — Django rejects requests with a mismatched Host header when set correctly, protecting against HTTP Host header attacks.
SECRET_KEY (used for signing sessions, CSRF tokens, and password reset links) must be a unique, unpredictable value pulled from an environment variable, never the default or a value committed to source control. Additional production hardening includes SECURE_SSL_REDIRECT to force HTTPS, SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE to prevent cookies from being sent over plain HTTP, and SECURE_HSTS_SECONDS to enforce HTTPS via browser policy.
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
Q8. How do you set up a CI/CD pipeline for a Django project (testing, linting, and automated deployment)?
A CI/CD pipeline (using GitHub Actions, GitLab CI, or Jenkins) automatically runs on every push or pull request, catching problems before they reach production instead of relying on manual review alone. A typical Django pipeline runs linting (flake8, black --check) and static analysis first since they're fast and catch style/syntax issues cheaply, then runs the full test suite against a real database service container, and finally checks test coverage against a minimum threshold.
On a successful merge to the main branch, the CD (continuous deployment) stage builds a new Docker image, pushes it to a registry, and triggers a deployment — often using a rolling or blue-green strategy so the new version is gradually shifted into traffic while the old version keeps serving requests until the new one is confirmed healthy, minimizing risk if the new deploy has a bug.
jobs:
test:
services:
postgres:
image: postgres:16
steps:
- run: pip install -r requirements.txt
- run: flake8 .
- run: python manage.py test
- run: coverage run manage.py test && coverage report --fail-under=80
Q9. How do you handle Django logging and error tracking in production (Sentry, structured logging)?
With DEBUG=False in production, Django no longer shows detailed error pages to users, which is correct for security but means errors are invisible unless explicitly captured somewhere. Django's built-in LOGGING setting configures handlers and loggers to write structured log output (to console, file, or a log aggregation service) at appropriate levels — INFO for normal operation, WARNING for recoverable issues, ERROR for exceptions.
Sentry is the most widely used error-tracking integration: its Django SDK automatically captures unhandled exceptions with a full stack trace, the request that triggered them, and relevant context (user, environment, release version), grouping repeated occurrences of the same error and alerting the team, which is far more actionable than scanning raw log files after the fact.
import sentry_sdk
sentry_sdk.init(
dsn=env('SENTRY_DSN'),
traces_sample_rate=0.1,
environment=env('ENVIRONMENT', default='production'),
)
Q10. How do you scale a Django application horizontally, and what state must move out of the app servers?
Horizontal scaling means running multiple identical Django application instances behind a load balancer instead of making one server bigger, allowing traffic to be distributed and instances to be added or removed based on load. For this to work correctly, application servers must be stateless — any request could be routed to any instance, so nothing critical can be stored only in one instance's local memory or disk.
This means sessions must be stored in a shared backend (database or Redis, not the default local-memory cache backend) rather than per-instance memory, uploaded media files must go to shared object storage (S3 or equivalent) rather than local disk, and any in-process cache must be replaced with a shared cache like Redis so all instances see consistent cached data. Sticky sessions at the load balancer are sometimes used as a workaround but reintroduce the same scaling and failover problems statelessness is meant to avoid.
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
CACHES = {'default': {'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://redis:6379/1'}}
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
Django Deployment & DevOps
Why can't you use Django's runserver in production, and what should you use instead?
Django's development server (manage.py runserver) is explicitly single-threaded by default, unoptimized for pe...
What is the difference between WSGI and ASGI, and when do you need ASGI for a Django project?
WSGI (Web Server Gateway Interface) is the traditional synchronous interface between a Python web application...
How does collectstatic work and how should static files be served in production?
During development, Django's runserver automatically serves static files (CSS, JS, images) from each app's sta...
How do you manage environment-specific settings and secrets in Django (django-environ, .env files)?
Hardcoding secrets (database passwords, API keys, SECRET_KEY) directly in settings.py is a serious security ri...
How do you dockerize a Django application, and what does a typical Dockerfile look like?
Dockerizing Django packages the application, its exact Python dependencies, and system libraries into a single...
What are Django database migrations and how do you safely run them in a zero-downtime deployment?
Migrations are Django's version-controlled way of evolving the database schema to match model changes over tim...
What Django settings must be changed for production (DEBUG, ALLOWED_HOSTS, SECRET_KEY, security headers)?
DEBUG must be set to False in production; leaving it True exposes detailed stack traces, local variable values...
How do you set up a CI/CD pipeline for a Django project (testing, linting, and automated deployment)?
A CI/CD pipeline (using GitHub Actions, GitLab CI, or Jenkins) automatically runs on every push or pull reques...
How do you handle Django logging and error tracking in production (Sentry, structured logging)?
With DEBUG=False in production, Django no longer shows detailed error pages to users, which is correct for sec...
How do you scale a Django application horizontally, and what state must move out of the app servers?
Horizontal scaling means running multiple identical Django application instances behind a load balancer instea...