Time to share our app with the world. In this final part of the Django series, we’ll be talking about deployment. Not just talking, actually, we’ll deploy our application to production.
There are a couple deployment options as far as Django applications are concerned. Some of them have free tiers with time or functional limitations. We’re going to use PythonAnywhere. It takes care of the infrastructure (servers, load balancers, reverse proxies, etc.), which is great help when we only get started.
The limitations change from time to time, so you better check them out before you deploy your app. At the time of this writing, for example, there is no Postgres support in the free plan, so we’re going to stick with the default SQLite database, which is fine for our purposes.
But before we publish our website, we must get it ready to publish.
Table of Contents
Before Publishing
So far, we’ve been developing our website using some settings that were meant to facilitate this process. But for production, we need some other settings. Let’s make all the necessary changes then.
SECRET_KEY and DEBUG
The two settings that definitely require some changes are SECRET_KEY and DEBUG. We’ll get them from environment variables if they are defined, and if not, we’ll use the values from the .env file in the root or the default values in the configuration file.
In order to read environment variables from a file, we’ll install the python-dotenv library:
pip install python-dotenv
Next, in the /portfolio/settings.py file, right below BASE_DIR
, add the following code to support environment variables from the .env file:
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Support env variables from .env file if defined
import os
from dotenv import load_dotenv
env_path = load_dotenv(os.path.join(BASE_DIR, '.env'))
load_dotenv(env_path)
# SECURITY WARNING: keep the secret key used in production secret!
...
We have to disable the original SECRET_KEY
and add the new one:
# SECURITY WARNING: keep the secret key used in production secret!
#SECRET_KEY = 'django-insecure-0_jt6vq)a09@j13#zri(skyc49&a0xrp4w6x7xh=t9q!+j$u%c'
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY',
'django-insecure-0_jt6vq)a09@j13#zri(skyc49&a0xrp4w6x7xh=t9q!+j$u%c')
We also have to change the DEBUG
setting below:
# SECURITY WARNING: don't run with debug turned on in production!
#DEBUG = True
DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'
HTTP Server and Database
Next, let’s install gunicorn
, which is a Python HTTP server:
pip install gunicorn
As far as database configuration is concerned, we’ll install dj-database-url
to extract the database configuration from an environment variable:
pip install dj-database-url
Now we need to modify the database configuration in the /portfolio/settings.py file. Add the following code at the end of the file:
import dj_database_url
if 'DATABASE_URL' in os.environ:
DATABASES['default'] = dj_database_url.config(
conn_max_age=500,
conn_health_checks=True,
)
This configuration will be used if the environment variable is set. Otherwise, it will use the default SQLite database.
In case a Postgres database should be used, Django needs psycopg2, so let’s install it too:
pip install psycopg2-binary
Static Files
We must handle serving static files in production. We use the collectstatic
tool to copy the static files to the location specified in the STATIC_ROOT
path. Make sure your settings match the following:
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
# The absolute path to the directory where collectstatic will collect static files for deployment.
STATIC_ROOT = BASE_DIR / 'staticfiles'
# The URL to use when referring to static files (where they will be served from)
STATIC_URL = '/static/'
We’ll use the WhiteNoise library to actually serve the files, so let’s install it:
pip install whitenoise
We have to add it to the MIDDLEWARE
list in settings.py:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
...
]
Requirements
For the hosting services to know which dependencies to install, we have to add a requirements.txt file to the root. In the repo root, let’s run the following command:
pip freeze > requirements.txt
The file contains all the dependencies that need to be installed:
asgiref==3.8.1
dj-database-url==2.3.0
Django==5.1.5
gunicorn==23.0.0
packaging==24.2
pillow==11.1.0
psycopg2-binary==2.9.10
python-dotenv==1.0.1
sqlparse==0.5.3
typing_extensions==4.12.2
tzdata==2025.1
whitenoise==6.9.0
We’re ready to publish the website now.
PythonAnywhere Account
Without further ado, let’s create a PythonAnywhere account.
Go to https://www.pythonanywhere.com/pricing/ and hit the Create a Beginner account button:

Fill in the form and hit Register:

You’ll be sent a confirmation email:

Go check your inbox and confirm your email.
Next, optionally, you can take a quick tour to learn how the site works:

After that, you’ll be logged in to the dashboard:

Source Code
Now, we can fetch our source code from Github. But first, we have set up a virtual environment. There are a couple steps to take.
Click on Consoles to open the Console management screen:

Click on Bash to create a new console:

The console will launch automatically. Type in the following command to create the virtual environment:
mkvirtualenv --python=python3.10 env_portfolio
Here’s what it looks like in the console:

In the next step, you have to clone the repo from Github. You must name the new folder after your PythonAnywhere username. For example, if my username is prosperocoder, the new folder must be named prosperocoder.pythonanywhere.com. Here’s how I’m cloning the repo in the console:
git clone https://github.com/prospero-apps/django_portfolio.git prosperocoder.pythonanywhere.com
Navigate to the new folder:
cd prosperocoder.pythonanywhere.com
Here it is in the console:

Next, let’s install the dependencies using the requirements.txt file:
pip install -r requirements.txt
This will install all the required libraries in the virtual environment on the host.
Next, let’s configure an SQLite database on the host:
python manage.py migrate
Let’s collect the static files:
python manage.py collectstatic --no-input
To access the site, we have to create a superuser:
python manage.py createsuperuser
Take note of the username, email address and password that you enter. You’re going to need them.
With the sources on the host, we must set up the web application now.
Web App Setup
If you are still in the console, expand the menu in the top right corner and navigate back to the dashboard:

Go to the Web section and hit the Add a new web app button:

In the free tier you can’t do much about your domain name:

So, just click Next. In the next screen, select the Manual configuration option:

In the Select a Python version screen, select Python 3.10:

In the next screen, hit the Next button:

All done. Your application has been created. You can use the green Reload button after you make any further changes. Also, as stated below, the app will run for three months. After this period of time, you have to click the yellow Run until 3 months from today button if you want to use it longer:

Scroll down to the Code section. Click the link to the WSGI configuration file:

Replace the code in the file with this (naturally, use your own username instead of prosperocoder):
import os
import sys
path = '/home/prosperocoder/prosperocoder.pythonanywhere.com/portfolio'
if path not in sys.path:
sys.path.append(path)
os.environ['DJANGO_SETTINGS_MODULE'] = 'portfolio.settings'
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
Save the file:

Go back to the dashboard and in the Web section scroll down to the Virtualenv section:

Click the link Enter path to a virtualenv, if desired and enter the path to the virtual environment we created:
/home/prosperocoder/.virtualenvs/env_portfolio
Naturally, replace your username and the name of the virtual environment if it’s different. Now you should see it:

Next, scroll down to the Static files section:

Click Enter URL and enter the location where the static files were copied (\static_files\
):

Scroll up to the top of the Web section and hit the Reload button. Next, click the link to the page:

You should see a disallowed host error.
Go to your Github project and add the host to ALLOWED_HOSTS
in the settings.py file:
ALLOWED_HOSTS = ['prosperocoder.pythonanywhere.com', 'localhost']
The app uses CSRF protection, so add this line right below:
CSRF_TRUSTED_ORIGINS = ['https://prosperocoder.pythonanywhere.com']
Push the changes to Github. Next, go to the Bash console on PythonAnywhere that you used before and pull the changes there:
git pull origin main
The last thing we have to take care of is creating the .env file from which DJANGO_DEBUG
, DJANGO_SECRET_KEY
and DATABASE_URL
will be read.
In the Bash console, you can set the values in the .env file using the echo command. To set DJANGO_DEBUG
, type the following:
echo "DJANGO_DEBUG=False" >> .env
Go back to the dashboard and in the Web section, reload the application and click the link again. Now you should navigate to the website:

This is your live website. The only problem is, you don’t see the stuff you entered through the admin site to the database. This is because we’re using a different SQLite database in production and it’s empty. We could enter all the data again through the admin site, or, which seems preferable, we can copy the database file.
Database Copy
In order to copy the database file, we have to find it first. Here we can see it in Visual Studio Code:

So, it’s inside the portfolio folder. Here it is in the File Explorer:

We have to upload this file to PythonAnywhere.
To do that, go to the Files section (A) and navigate to your project folder (B). You should now see the new db.sqlite3 file (C). This is the one with the empty database, so let’s delete it (D):

Hit the Upload a file button (A) and upload the file (B):

If you now go to the Web section, reload the application and click the link, all the data from the database should be there:

We can now use the admin site to manage our stuff. We can add projects, categories, etc. We can edit them or delete them if we want. Our website is live!
Conclusion
In this final part of the Django series we deployed our little app to production. But there’s much more to Django than this. We haven’t touched on topics like sessions, authentication and authorization or forms. We haven’t discussed testing Django applications. But for now, it will do. There’s much more to explore in the future.