Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 64

Deploying to PaaS: Heroku & Render

Recommended resources

Hello! Welcome to the final module on deploying your Django application.

In the last two lessons, you prepared your application for the real world by managing its dependencies with requirements.txt and configuring static file serving with WhiteNoise. You're on the last stretch before your project goes live!

Today's lesson focuses on the final preparations your codebase needs before it can be deployed to a Platform-as-a-Service (PaaS). A PaaS like Render or Heroku manages the underlying server infrastructure for you, allowing you to focus on your code. Our goal is to make your application robust, secure, and ready for this kind of environment.

By the end of this lesson, you will have prepared your application for deployment by configuring a production-ready web server, setting up for a professional-grade database, securing sensitive settings, and creating a build script to automate deployment tasks.

Let's get your application ready for launch!

1. Production vs. Development Servers: Introducing Gunicorn

So far, you've been running your project with the command python manage.py runserver. This is Django's built-in development server. It's fantastic for development because it's simple and automatically reloads when you change your code.

However, the development server is not suitable for a live, production environment. It's not designed to handle multiple users, it's not very secure, and it's not efficient. For production, we need a dedicated Web Server Gateway Interface (WSGI) server. A popular and robust choice for Django is Gunicorn.

Python Web Application Deployment Architecture
This diagram shows how a WSGI server like Gunicorn acts as the bridge between a web server (which is handled by the PaaS) and your Python application. It manages multiple processes to handle incoming user requests efficiently.

Let's install Gunicorn and add it to our project's dependencies.

  1. Make sure your virtual environment is activated.
  2. Install Gunicorn:
    pip install gunicorn
    
  3. Update your requirements.txt file immediately:
    pip freeze > requirements.txt
    

When we deploy, we will tell the PaaS to start our application using a command like gunicorn your_project.wsgi:application. This command tells Gunicorn to run the application object found in your project's wsgi.py file.


2. Preparing for a Production Database

Just like the development server, the default SQLite database is not ideal for production. It's a single file on your server, which isn't robust enough for handling many simultaneous users and can lead to data integrity issues.

The standard database for production Django applications is PostgreSQL. When we deploy to a service like Render, we'll create a managed PostgreSQL database in the cloud. Our Django application needs the right tools to connect to it.

We need two packages for this:

  • psycopg2-binary: A "driver" that allows Python to communicate with a PostgreSQL database.
  • dj-database-url: A small utility that lets us configure our database connection from a single URL string, which is how PaaS providers supply database credentials.

Let's install them now.

  1. Run the following install command:
    pip install psycopg2-binary dj-database-url
    
  2. Once again, update your dependencies:
    pip freeze > requirements.txt
    

With these packages installed, our application is now capable of connecting to a PostgreSQL database once we provide it with the right credentials.


3. Securing Settings with Environment Variables

This is the most critical part of preparing for deployment. Your settings.py file currently contains sensitive information like your SECRET_KEY and settings that should be different in production, like DEBUG = True. Hardcoding these values is insecure and inflexible.

The best practice is to store these values in environment variables. Your local machine will have one set of variables (e.g., DEBUG=True), and the production server will have another (DEBUG=False).

We'll use a package called python-decouple to make reading these variables easy.

Deploy Django Project & Postgresql Database to Render for Free | 2025

The following video provides an excellent walkthrough of setting up environment variables. It explains why we need to hide sensitive data and demonstrates how to use python-decouple to achieve this.

Watch the segment from 00:11:26 to 00:20:27. Focus on these key steps: Installing python-decouple. Creating a .env file to store secrets locally. Creating a .gitignore file to ensure the .env file is never committed to your repository. Modifying settings.py to pull values for SECRET_KEY, DEBUG, ALLOWED_HOSTS, and DATABASE_URL from the environment.

Let's follow the steps shown in the video to prepare your project.

Step 1: Install python-decouple

pip install python-decouple
pip freeze > requirements.txt

Step 2: Create .env and .gitignore

  1. In the root directory of your project (the same level as manage.py), create a file named .env.

  2. Add your SECRET_KEY and other development settings to this file. Do not use quotes.

    # .env
    SECRET_KEY=your-django-secret-key-goes-here-dont-share-it
    DEBUG=True
    DATABASE_URL=sqlite:///db.sqlite3
    ALLOWED_HOSTS=127.0.0.1,localhost
    

    Note: The DATABASE_URL for SQLite points to the db.sqlite3 file in your root directory.

  3. In the same root directory, create a file named .gitignore (if you don't already have one). Add .env to it to prevent your secrets from ever being uploaded to GitHub.

    # .gitignore
    .env
    # other files to ignore...
    

Step 3: Update settings.py

Now, let's modify settings.py to read from these environment variables.

# settings.py
import os
from pathlib import Path
from decouple import config
import dj_database_url # Add this import

# ... (BASE_DIR should already be defined)

# Read the secret key from the environment variable
SECRET_KEY = config('SECRET_KEY')

# Read the debug setting, defaulting to False if not found.
# The `cast=bool` converts the string 'True' or 'False' to a boolean.
DEBUG = config('DEBUG', default=False, cast=bool)

# Read allowed hosts from a comma-separated string
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='127.0.0.1').split(',')

# ... (your INSTALLED_APPS, MIDDLEWARE, etc.)

# Database configuration
# This will use the DATABASE_URL from your .env file locally,
# and the one provided by Render in production.
DATABASES = {
    'default': dj_database_url.parse(config('DATABASE_URL'))
}

# ... (rest of your settings)

Your application is now securely configured. It will use your local .env file for development and will read its settings from the PaaS environment in production.

Test your understanding!

Why is it important to add the .env file to .gitignore? What would happen if you accidentally committed it to a public GitHub repository?

Show answer

Adding .env to .gitignore prevents your secret keys, database passwords, and other sensitive credentials from being stored in your version control history. If you accidentally committed it to a public repository, anyone could see your SECRET_KEY, which would allow them to potentially forge sessions and compromise your application's security. They might also get access to your production database credentials.


4. Creating a Build Script

When you deploy your code, the PaaS needs to run a few commands to set everything up. These commands typically include:

  1. Installing all the Python packages from requirements.txt.
  2. Running collectstatic to gather all your static files for WhiteNoise.
  3. Running migrate to apply any database schema changes.

We can automate this by creating a simple shell script. Render specifically looks for a build.sh file.

Deploy a Django App on Render

The official Render documentation explains the need for a build script. We will follow its recommendation to create one.

Read the section titled 'Create a build script'. This short section gives you the exact content for the build.sh file and the command to make it executable.

As described in the documentation, create a new file named build.sh in your project's root directory and add the following content:

#!/usr/bin/env bash
# exit on error
set -o errexit

pip install -r requirements.txt

python manage.py collectstatic --no-input
python manage.py migrate
  • set -o errexit: This is a safety measure. It tells the script to exit immediately if any command fails.
  • pip install ...: Installs your dependencies.
  • collectstatic --no-input: Collects static files without asking for user confirmation.
  • migrate: Applies database migrations.

Finally, make the script executable by running this command in your terminal:

chmod +x build.sh

Now, your project contains all the instructions needed for a PaaS to build and run it automatically.


Conclusion

Excellent work! Your application is now fully prepared for deployment. You've taken it from a development-centric setup to a secure, robust, and platform-ready project.

Key Takeaways:

  • The Django development server is for development only; Gunicorn is a production-grade WSGI server for handling live traffic.
  • SQLite is not suitable for production; you've prepared your app to use PostgreSQL by installing the necessary driver (psycopg2-binary) and helper (dj-database-url).
  • Sensitive data and environment-specific settings must be stored in environment variables, not in code. python-decouple helps manage this securely.
  • A build.sh script automates the setup process on the deployment server, ensuring dependencies are installed and database migrations are run.

Preview of the Next Lesson:

You've done all the prep work. In the next and final lesson of this module, we will perform the actual deployment. You'll create a GitHub repository, set up your services on Render, push your code, and watch your application go live on the internet. Get ready to see your project in the wild