DEV Community

Teemu Virta
Teemu Virta

Posted on

How to Deploy a Django Website with Automated CI/CD Pipeline

A step-by-step guide to deploying a Django website to PythonAnywhere with automatic deployment via GitHub Actions.


πŸ“‹ Project Overview

Goal: Create a personal website with:

  • Django backend
  • Custom domain (optional)
  • HTTPS security
  • Automated deployment from VS Code to production

Tech Stack:

  • Django 5.2.8+
  • PythonAnywhere hosting
  • GitHub Actions for CI/CD
  • Custom domain with SSL (optional)

Result: Push code from VS Code β†’ Site updates automatically in production

Example project: This tutorial uses my_website as the project name. Replace it with your own project name throughout.


🎯 Prerequisites

Before starting, you need:

  • Python 3.10+ installed
  • VS Code (or any code editor)
  • GitHub account
  • PythonAnywhere account (Hacker tier for custom domains)
  • Custom domain (optional)

πŸš€ Step-by-Step Guide

Step 1: Create Django Project Locally

# Create project directory (use your own project name)
mkdir my_website
cd my_website

# Create virtual environment
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# Install Django
pip install django

# Create Django project (replace 'my_website' with your project name)
django-admin startproject my_website .
django-admin startapp main_app

# Create requirements.txt
pip freeze > requirements.txt

# Test locally
python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Visit http://127.0.0.1:8000 to verify Django works.

Note: Throughout this tutorial:

  • my_website = your Django project name
  • main_app = your Django app name
  • Replace these with your actual names

Step 2: Setup Git and GitHub

# Initialize Git
git init
git add .
git commit -m "Initial commit"

# Create repository on GitHub
# Then connect local to remote:
git remote add origin https://github.com/YOUR_USERNAME/my_website.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Important: Create a .gitignore file:

*.pyc
__pycache__/
db.sqlite3
venv/
.env
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure Django for Production

Edit my_website/settings.py (replace my_website with your project name):

# Add this at the top
from pathlib import Path
import os  # CRITICAL - don't forget this!

# Configure allowed hosts (replace with your domain)
ALLOWED_HOSTS = [
    'yourdomain.com',           # Your custom domain
    'www.yourdomain.com',       # www version
    'YOUR_USERNAME.pythonanywhere.com'  # PythonAnywhere subdomain
]

# For production, set DEBUG = False (but keep True during setup)
DEBUG = True  # Change to False after everything works

# Add your app to INSTALLED_APPS
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'main_app',  # Your app name here
]

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
Enter fullscreen mode Exit fullscreen mode

Step 4: Deploy to PythonAnywhere

4.1 Setup on PythonAnywhere

  1. Create account at pythonanywhere.com

    • Free tier works for testing
    • Hacker tier ($5/month) needed for custom domains
  2. Open Bash console and clone your repo:

# Replace YOUR_USERNAME and my_website with your values
git clone https://github.com/YOUR_USERNAME/my_website.git
cd my_website
Enter fullscreen mode Exit fullscreen mode
  1. Create virtual environment:
# Replace my_website_env with your preferred name
mkvirtualenv --python=/usr/bin/python3.10 my_website_env
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode
  1. Configure Web App:

    • Go to Web tab
    • Add new web app
    • Choose "Manual configuration"
    • Python 3.10
    • Set source code: /home/YOUR_USERNAME/my_website
    • Set working directory: /home/YOUR_USERNAME/my_website
    • Set virtualenv: /home/YOUR_USERNAME/.virtualenvs/my_website_env
  2. Configure WSGI file (click on WSGI config file link in Web tab):

import os
import sys

# Replace YOUR_USERNAME and my_website with your values
path = '/home/YOUR_USERNAME/my_website'
if path not in sys.path:
    sys.path.append(path)

# Replace 'my_website' with your Django project name
os.environ['DJANGO_SETTINGS_MODULE'] = 'my_website.settings'

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
Enter fullscreen mode Exit fullscreen mode
  1. Reload web app - Click green "Reload" button

Your site should now be live at: YOUR_USERNAME.pythonanywhere.com


Step 5: Setup Custom Domain (Optional)

Note: Requires PythonAnywhere Hacker account ($5/month)

  1. In PythonAnywhere Web tab:

    • Add custom domain: www.yourdomain.com (replace with your domain)
    • Note the CNAME value: webapp-XXXXXX.pythonanywhere.com
  2. In your domain registrar (Namecheap, GoDaddy, Joker.com, etc):

    • Add CNAME record: www β†’ webapp-XXXXXX.pythonanywhere.com
    • Optionally add A record for root domain (check PythonAnywhere docs)
  3. Enable HTTPS:

    • In PythonAnywhere Web tab β†’ HTTPS section
    • Click "Enable HTTPS" or "Renew certificate"

Wait 10-60 minutes for DNS propagation.

If you're not using a custom domain: Skip this step and use YOUR_USERNAME.pythonanywhere.com


Step 6: Setup Automated Deployment

This is where the magic happens - automatic deployment on every Git push!

6.1 Create GitHub Personal Access Token

  1. GitHub β†’ Settings β†’ Developer settings β†’ Personal access tokens β†’ Tokens (classic)
  2. Generate new token (classic)
  3. Select scope: β˜‘οΈ repo (all checkboxes under repo)
  4. Copy the token (starts with ghp_...)

6.2 Configure Git Authentication on PythonAnywhere

cd ~/my_website
git remote set-url origin https://github.com/YOUR_USERNAME/my_website.git
git config credential.helper store
git pull origin main
# Username: YOUR_GITHUB_USERNAME
# Password: PASTE_YOUR_TOKEN_HERE
Enter fullscreen mode Exit fullscreen mode

The token is now saved and git pull will work automatically.

6.3 Create GitHub Actions Workflow

Create .github/workflows/deploy.yml in your project:

name: Deploy to PythonAnywhere

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ssh.pythonanywhere.com
          username: ${{ secrets.PYTHONANYWHERE_USERNAME }}
          password: ${{ secrets.PYTHONANYWHERE_PASSWORD }}
          script: |
            cd ~/my_website
            git pull origin main
            touch /var/www/YOUR_USERNAME_pythonanywhere_com_wsgi.py

      - name: Reload web app via API
        run: |
          curl -X POST \
            https://www.pythonanywhere.com/api/v0/user/${{ secrets.PYTHONANYWHERE_USERNAME }}/webapps/YOUR_USERNAME.pythonanywhere.com/reload/ \
            -H "Authorization: Token ${{ secrets.PYTHONANYWHERE_API_TOKEN }}"
Enter fullscreen mode Exit fullscreen mode

Important replacements in the workflow:

  • Line 17: cd ~/my_website β†’ Use your project directory name
  • Line 19: YOUR_USERNAME_pythonanywhere_com_wsgi.py β†’ Find exact filename in PythonAnywhere /var/www/ directory
  • Line 24: YOUR_USERNAME.pythonanywhere.com β†’ Use your actual PythonAnywhere URL (or custom domain if configured)

6.4 Add GitHub Secrets

Go to: Repository β†’ Settings β†’ Secrets and variables β†’ Actions

Create three secrets:

  1. PYTHONANYWHERE_USERNAME

    • Value: Your PythonAnywhere username
  2. PYTHONANYWHERE_PASSWORD

    • Value: Your PythonAnywhere password
  3. PYTHONANYWHERE_API_TOKEN

    • Get from: PythonAnywhere β†’ Account β†’ API token β†’ Create new
    • Value: The generated token

Step 7: Test Automated Deployment

# Make a small change in your code
# For example, edit index.html or a template file

# Commit and push
git add .
git commit -m "Test automated deployment"
git push origin main
Enter fullscreen mode Exit fullscreen mode

What happens now:

  1. GitHub receives your push
  2. GitHub Actions workflow triggers
  3. Connects to PythonAnywhere via SSH
  4. Runs git pull to get latest code
  5. Reloads web app via API
  6. Your site is updated! (takes ~1-2 minutes)

Check: GitHub β†’ Actions tab to see workflow running


βœ… Final Workflow

Your development workflow is now:

1. Edit code in VS Code
2. Save changes  
3. git add . && git commit -m "message" && git push origin main
4. Wait 1-2 minutes
5. Site is live! πŸŽ‰
Enter fullscreen mode Exit fullscreen mode

Check your live site at:

  • PythonAnywhere subdomain: YOUR_USERNAME.pythonanywhere.com
  • Custom domain (if configured): www.yourdomain.com

πŸ› Common Issues & Solutions

Issue 1: "DisallowedHost" error

Solution: Add your domain to ALLOWED_HOSTS in settings.py:

ALLOWED_HOSTS = [
    'yourdomain.com',
    'www.yourdomain.com',
    'YOUR_USERNAME.pythonanywhere.com'
]
Enter fullscreen mode Exit fullscreen mode

Issue 2: "Permission denied (publickey)" when git pull

Solution: Change Git remote to HTTPS and use Personal Access Token:

git remote set-url origin https://github.com/YOUR_USERNAME/your_project.git
git config credential.helper store
git pull  # Enter username and token
Enter fullscreen mode Exit fullscreen mode

Issue 3: GitHub Actions workflow fails

Solution:

  • Check that all three Secrets are correctly set in GitHub
  • Verify PythonAnywhere API token is valid
  • Check workflow logs in GitHub Actions tab
  • Important: Verify WSGI file path and website URL in deploy.yml match your actual setup

Issue 4: Site doesn't update after push

Solution:

  • Check GitHub Actions - did workflow succeed?
  • SSH into PythonAnywhere and manually run git pull
  • Check error logs in PythonAnywhere Web tab
  • Verify the touch command uses correct WSGI file path

Issue 5: 502 Bad Gateway error

Solution:

  • Check WSGI configuration file has correct project name
  • Verify virtual environment is activated and has dependencies
  • Check error logs in PythonAnywhere

πŸ“Š Project Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   VS Code   β”‚  (Local Development)
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚ git push
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   GitHub    β”‚  (Version Control)
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚ triggers
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GitHub Actions  β”‚  (CI/CD Pipeline)
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚ SSH + API
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PythonAnywhere   β”‚  (Production Server)
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚ serves
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Your Website   β”‚  (Live Site)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

πŸŽ“ Key Learnings

  1. Django configuration matters - import os and ALLOWED_HOSTS are critical
  2. GitHub Actions is powerful - Automate everything!
  3. Personal Access Tokens - Replace password authentication for Git
  4. WSGI configuration - Connects Django to web server
  5. Systematic debugging - Check each layer: local β†’ Git β†’ GitHub β†’ server β†’ live site

You now have a Django website with automated deployment. Every time you push code, your site updates automatically. No manual deployment needed!


Tutorial by Teemu Virta - teemu.tech

Top comments (0)