Introduction
You built a Django app on localhost:8000 — now you want the world to see it. The problem? Most "easy" hosts either cost money, hide a credit card behind the free tier, or make you wrestle with servers you don't understand.
Render is one of the few platforms that lets you deploy a real Django app — with a PostgreSQL database — completely free, straight from your GitHub repo. No credit card needed to start, and every push to main redeploys automatically.
This guide walks you through the whole thing: preparing your project, configuring production settings, adding gunicorn, serving static files, wiring up a free database, and going live. By the end your app will be running on a public https:// URL.
Heads up on the free tier: Render's free web services spin down after ~15 minutes of inactivity, so the first request after idle takes ~30–50 seconds to wake up. The free PostgreSQL database expires after 90 days. It's perfect for portfolios, demos, and side projects — not production traffic.
What You'll Need
- A working Django project pushed to a GitHub repository
- A free Render account (sign up with GitHub)
- Python 3.10+ locally
- About 15 minutes
Step 1 — Get Your Project Production-Ready
A few packages turn your local app into something a server can run.
pip install gunicorn whitenoise dj-database-url psycopg2-binary
| Package | Why you need it |
|---|---|
gunicorn |
Production WSGI server (replaces runserver) |
whitenoise |
Serves static files without needing Nginx |
dj-database-url |
Parses Render's DATABASE_URL into Django settings |
psycopg2-binary |
PostgreSQL driver |
Now freeze everything into a requirements.txt so Render knows what to install:
pip freeze > requirements.txt
Step 2 — Configure settings.py for Production
Never hardcode secrets or run with DEBUG = True in production. Pull config from environment variables instead.
import os
import dj_database_url
# SECURITY
SECRET_KEY = os.environ.get("SECRET_KEY", "unsafe-dev-key")
DEBUG = os.environ.get("DEBUG", "False") == "True"
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
# Render provides the external hostname here
RENDER_HOST = os.environ.get("RENDER_EXTERNAL_HOSTNAME")
if RENDER_HOST:
ALLOWED_HOSTS.append(RENDER_HOST)
Add WhiteNoise middleware
Insert WhiteNoise right after the security middleware:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # add this line
"django.contrib.sessions.middleware.SessionMiddleware",
# ... the rest stays the same
]
Static files config
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
STORAGES = {
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
Database config
This one line uses your local SQLite by default but switches to Render's PostgreSQL automatically when DATABASE_URL is present:
DATABASES = {
"default": dj_database_url.config(
default=f"sqlite:///{BASE_DIR / 'db.sqlite3'}",
conn_max_age=600,
)
}
Step 3 — Add a Build Script
Render needs to know how to build your app. Create a file named build.sh in your project root:
#!/usr/bin/env bash
set -o errexit # exit on any error
pip install -r requirements.txt
python manage.py collectstatic --no-input
python manage.py migrate
Make it executable so Render can run it:
chmod +x build.sh
git add build.sh
Step 4 — Push Everything to GitHub
Make sure your .gitignore keeps secrets and junk out of the repo:
*.pyc
__pycache__/
db.sqlite3
.env
staticfiles/
Then commit and push:
git add .
git commit -m "Prepare Django app for Render deployment"
git push origin main
Step 5 — Create the Web Service on Render
- Log in to Render and click New → Web Service.
- Connect your GitHub account and select your repository.
- Fill in the settings:
| Field | Value |
|---|---|
| Name | my-django-app (becomes part of your URL) |
| Region | Closest to your users |
| Branch | main |
| Runtime | Python 3 |
| Build Command | ./build.sh |
| Start Command | gunicorn myproject.wsgi:application |
| Instance Type | Free |
Replace
myprojectin the start command with the folder that contains yourwsgi.py(the same name as your Django project).
Don't click Create just yet — add your environment variables first.
Step 6 — Add Environment Variables
Scroll to the Environment section and add:
| Key | Value |
|---|---|
SECRET_KEY |
A long random string (generate one below) |
DEBUG |
False |
PYTHON_VERSION |
3.12.0 |
Generate a strong secret key locally:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
Paste the output as the value for SECRET_KEY.
Step 7 — Add a Free PostgreSQL Database
SQLite won't survive on Render — its filesystem is wiped on every deploy. Use a real database instead.
- Click New → PostgreSQL.
- Give it a name, pick the Free plan, and create it.
- Open the database, copy its Internal Database URL.
- Go back to your web service → Environment → add:
| Key | Value |
|---|---|
DATABASE_URL |
(paste the Internal Database URL) |
Because of the dj-database-url line from Step 2, Django picks this up automatically — no further code changes needed.
Step 8 — Deploy
Click Create Web Service (or Manual Deploy → Deploy latest commit). Render now:
- Clones your repo
- Runs
build.sh— installs packages, collects static files, runs migrations - Boots gunicorn
Watch the Logs tab in real time. When you see something like:
==> Your service is live 🎉
[INFO] Booting worker with pid: 42
your app is live at https://my-django-app.onrender.com.
Step 9 — Create Your Admin User
You can't run createsuperuser interactively on Render, so use Render's Shell tab (under your service) and run:
python manage.py createsuperuser
Then visit https://my-django-app.onrender.com/admin/ and log in.
Auto-Deploys: Push to Deploy
By default, Render redeploys every time you push to main. Your workflow is now just:
git add .
git commit -m "Add new feature"
git push origin main
Render detects the push, reruns build.sh, and ships the update — zero manual steps.
Troubleshooting Common Errors
| Error | Cause & Fix |
|---|---|
DisallowedHost / Bad Request (400) |
RENDER_EXTERNAL_HOSTNAME not added to ALLOWED_HOSTS — recheck Step 2 |
| CSS/JS not loading | WhiteNoise missing from middleware, or collectstatic didn't run |
ModuleNotFoundError: gunicorn |
gunicorn not in requirements.txt — run pip freeze again |
| App won't start | Wrong project name in the start command — check myproject.wsgi |
| DB connection refused | DATABASE_URL missing or using the External instead of Internal URL |
| First request hangs ~30s | Normal — free instances sleep after 15 min of inactivity |
Free Tier Limits to Remember
- Web service sleeps after 15 minutes idle (cold starts take ~30–50s).
- Free PostgreSQL expires after 90 days — back up your data or upgrade before then.
- 750 free instance hours/month shared across your free services.
- No persistent disk — always use the database for anything you need to keep.
A common trick for demos: use a free uptime monitor (like UptimeRobot) to ping your URL every 10 minutes so it never sleeps.
Final Thought
Deploying Django used to mean provisioning a VPS, configuring Nginx, and writing systemd files at midnight. Render collapses all of that into a build script and a few environment variables — and it's free enough to host every side project in your portfolio.
Get your first app live, share the URL, and the next deploy is just a git push away. That's the whole point: spend your time building features, not babysitting servers.