JitCoder

Django Deployment on AWS EC2 with GitHub Actions, Gunicorn, Nginx & Secure .env Setup

Deploying a Django project to AWS EC2 can become confusing when issues like DisallowedHost, GitHub Actions not running, .env files not loading, or Gunicorn using the wrong environment start appearing.

In this guide, I’ll explain the complete professional production setup for deploying a Django project using:

  • AWS EC2
  • GitHub Actions (CI/CD)
  • Gunicorn
  • Nginx
  • .env.production
  • Secure .gitignore
  • Proper ALLOWED_HOSTS
  • Production-ready gunicorn.service

This is the exact setup used in real production projects.


Project Structure (Correct Setup)

Your project should look like this:

codify/

├── .github/

│   └── workflows/

│       └── deploy.yml

├── codify/

│   ├── account/

│   ├── contest/

│   ├── judge/

│   ├── manage.py

│   └── settings.py

├── frontend/

└── README.md

Important Note

GitHub Actions only works if your workflow file is here:

.github/workflows/deploy.yml

Wrong Location

codify/.github/workflows/

If your workflow is inside the wrong folder, GitHub Actions will never run.


Local vs Production Environment Files

A professional Django deployment uses separate environment files.

Local Development

.env.local

Used for:

  • Local laptop development
  • Testing
  • Debugging

Example:

DEBUG=True

ALLOWED_HOSTS=127.0.0.1,localhost

DB_NAME=local_db

DB_USER=postgres

DB_PASSWORD=local_password


Production Server

.env.production

Used for:

  • AWS EC2 deployment
  • Live production server

Example:

ENVIRONMENT=production

DEBUG=False

ALLOWED_HOSTS=backend.jitcoder.in,api2.jitcoder.in,3.238.247.231,127.0.0.1,localhost

CORS_ALLOWED_ORIGINS=https://jitcoder.in,https://www.jitcoder.in

CSRF_TRUSTED_ORIGINS=https://jitcoder.in,https://www.jitcoder.in

FRONTEND_URL=https://jitcoder.in

DATABASE_URL=your_database_url

REDIS_URL=redis://127.0.0.1:6379/0

EMAIL_USER=your_email@gmail.com

EMAIL_PASS=your_password


Correct .gitignore Setup

Never push secrets to GitHub.

Use this:

# Environment variables

.env

.env.*

!.env.example

This means:

Ignored

.env

.env.local

.env.production

.env.test

Not Ignored

.env.example

This is the best professional practice.


Why .env.local Still Exists After .gitignore

Many developers get confused here.

Important Rule

.gitignore does NOT remove already tracked files.

It only prevents new files from being added.

If you pushed .env.local before adding .gitignore, Git will continue tracking it.


How to Check If Git Still Tracks It

Run:

git ls-files | findstr .env

Example output:

codify/.env.example

codify/.env.local

codify/.env.production

This means Git is still tracking those files.


Proper Fix for Already Tracked .env Files

Remove them from Git tracking without deleting local files:

git rm –cached codify/.env.local

git rm –cached codify/.env.production

git rm –cached frontend/my-app/.env.production

git commit -m “Remove env files from git tracking”

git push origin main

Now Git will stop tracking them.

The files remain safe on your machine and server.


Correct settings.py for Dynamic Environment Loading

Use this setup:

from pathlib import Path

import os

from dotenv import load_dotenv

BASE_DIR = Path(__file__).resolve().parent.parent

ENVIRONMENT = os.getenv(“ENVIRONMENT”, “local”)

env_file = (

   BASE_DIR / “.env.production”

   if ENVIRONMENT == “production”

   else BASE_DIR / “.env.local”

)

if env_file.exists():

   load_dotenv(env_file)

   print(f”Loaded env file: {env_file}”)


Correct ALLOWED_HOSTS Setup

Use:

ALLOWED_HOSTS = os.getenv(

   “ALLOWED_HOSTS”,

   “127.0.0.1,localhost”

).split(“,”)

This avoids the common Django error:

DisallowedHost

Invalid HTTP_HOST header


Why DisallowedHost Happens

Example error:

Invalid HTTP_HOST header: ‘api2.jitcoder.in’

This happens because:

api2.jitcoder.in

is missing from:

ALLOWED_HOSTS

Fix:

ALLOWED_HOSTS=backend.jitcoder.in,api2.jitcoder.in,3.238.247.231,127.0.0.1,localhost


Gunicorn Using Wrong .env File

This is one of the most common production problems.

Even when .env.production exists, Gunicorn may still load:

.env.local

because ENVIRONMENT=production is missing from gunicorn.service.


Correct gunicorn.service

File:

/etc/systemd/system/gunicorn.service

Use:

[Unit]

Description=gunicorn daemon

After=network.target

[Service]

User=ubuntu

Group=www-data

WorkingDirectory=/home/ubuntu/jitcoder/codify

Environment=”ENVIRONMENT=production”

ExecStart=/home/ubuntu/jitcoder/venv/bin/gunicorn \

         –workers 3 \

         –bind unix:/home/ubuntu/jitcoder/gunicorn.sock \

         codify.wsgi:application

[Install]

WantedBy=multi-user.target


Very Important After Editing

Always run:

sudo systemctl daemon-reload

sudo systemctl restart gunicorn

sudo systemctl restart nginx

Without daemon-reload, changes won’t apply.


How to Verify Which .env File Django Uses

Best command:

sudo journalctl -u gunicorn –no-pager | tail -50

Correct output:

Loaded env file: /home/ubuntu/jitcoder/codify/.env.production

Wrong output:

Loaded env file: /home/ubuntu/jitcoder/codify/.env.local

This is the most reliable method.


How .env.production Reaches AWS Server

Since .env.production is ignored by Git:

git pull

will NOT download it.

That is correct.

You create it manually once on EC2:

cd ~/jitcoder/codify

nano .env.production

Paste your production values.

Save it.

That file stays permanently on the server.

Future git pull updates only code, not secrets.

This is exactly how production should work.


GitHub Actions Verification

Go to:

GitHub Repository → Actions Tab

You should see:

  • Success
  • Failed
  • Running

This confirms CI/CD status.


Verify Deployment from EC2

Run:

cd ~/jitcoder/codify

git log –oneline -5

If the latest pushed commit appears:

Your GitHub Actions deployment is working.


Final Professional Setup

GitHub

.env.example

only

Local PC

.env.local

private

AWS EC2

.env.production

private


Final Rule

Never Edit

settings.py

directly on the server

Always Edit

.env.production

for production changes

This is the professional and scalable way to deploy Django applications.


Conclusion

A proper Django production deployment is not just about making the website run.

It is about:

  • security
  • maintainability
  • CI/CD automation
  • clean architecture
  • production safety

Once you follow this structure, deployments become reliable, scalable, and professional.

This setup is exactly how serious Django projects are deployed in production.

Leave a Comment

Your email address will not be published. Required fields are marked *