🏠 Home
Connecting Django to Postgres and AWS RDS: A Step-by-Step Guide

Django + Postgres + AWS RDS: A Step-by-Step Guide

Introduction

Embark on a journey to seamlessly integrate Django, Postgres, and AWS RDS in our latest step-by-step guide. This tutorial is tailored for developers looking to leverage the robustness of Django with the reliability of Postgres and the scalability of AWS RDS. Discover how to set up a dynamic web application environment that harnesses the power of these technologies, enhancing your development and deployment workflow.

What You Will Learn

By the end of this tutorial, you will understand:

Prerequisites

Before starting the tutorial, make sure you have the following prerequisites installed and set up on your computer:

  1. Python 3.10+ (we’ll use 3.12):
  1. AWS:
  1. Terraform CLI:

Step 1: Setting Up Your Django Project

Now, let’s run Python 3.12 to install Django and create a project:

python3.12 -m pip install django
python3.12 -m django startproject django-rds
cd django-rds

Now, within this folder, we should have the following files:

# Command: tree .
.
├── manage.py
└── django-rds
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

We now have a proper Django project!

Step 2: Setting Up Your Vitual Environment

With our Django project, let’s setup a virtual environment and install the necessary dependencies!

A virtual environment is useful because it allows you to manage separate dependencies for different projects, preventing conflicts between package versions and ensuring consistency across development and production setups.

Let’s create and run a Python 3.12 virtual environment now:

python3.12 -m venv my-venv
source my-venv/bin/activate

You should now see (my-venv) in your terminal. This means your virtual environment is up and running!

Let’s also install the necessary dependencies and save them to requirements.txt.

pip install --upgrade pip
pip install django psycopg2-binary
pip freeze > requirements.txt

Now we should have a file called requirements.txt which lists out needed dependencies.

Step 3: Running the local database

Now that our Django Project is configured, lets run Django with our local database and an admin user.

./manage.py migrate
./manage.py createsuperuser # follow the steps
./manage.py runserver

We can go to http://127.0.0.1:8000/admin to test our user was written into our db.sqlite3 database.

Step 4: Setting up Terraform

Instead of clicking around the AWS console, we’re going to define our environment with Terraform and run apply to create this blueprint in the cloud.

First, lets get your AWS credentials to start using Terraform on your machine.

Head to the AWS Console > Your Account > Security Credentials to create a new key-pair.

Security Creds

Then click on “Create access key” and follow the steps.

We’ll set the keys as local environment variables. We can do so with the following commands:

MacOS or Linux

export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"

Windows

set AWS_ACCESS_KEY_ID=your_access_key
set AWS_SECRET_ACCESS_KEY=your_secret_key

Step 5: Writing the Terraform Modules

Create a main.tf file in the root of your Django project.

Below are all the terraform modules and an explanation for each part:

# Define AWS provider and set the region for resource provisioning
provider "aws" {
  region = "us-east-1"
}

# Create a Virtual Private Cloud to isolate the infrastructure
resource "aws_vpc" "default" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "DjangoRDSVPC"
  }
}

# Internet Gateway to allow internet access to the VPC
resource "aws_internet_gateway" "default" {
  vpc_id = aws_vpc.default.id
  tags = {
    Name = "DjangoRDSInternetGateway"
  }
}

# Route table for controlling traffic leaving the VPC
resource "aws_route_table" "default" {
  vpc_id = aws_vpc.default.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.default.id
  }
  tags = {
    Name = "DjangoRDSRouteTable"
  }
}

# Subnet within VPC for resource allocation, in availability zone us-east-1a
resource "aws_subnet" "subnet1" {
  vpc_id                  = aws_vpc.default.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "us-east-1a"
  tags = {
    Name = "DjangoRDSSubnet1"
  }
}

# Another subnet for redundancy, in availability zone us-east-1b
resource "aws_subnet" "subnet2" {
  vpc_id                  = aws_vpc.default.id
  cidr_block              = "10.0.2.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "us-east-1b"
  tags = {
    Name = "DjangoRDSSubnet2"
  }
}

# Associate subnets with route table for internet access
resource "aws_route_table_association" "a" {
  subnet_id      = aws_subnet.subnet1.id
  route_table_id = aws_route_table.default.id
}

resource "aws_route_table_association" "b" {
  subnet_id      = aws_subnet.subnet2.id
  route_table_id = aws_route_table.default.id
}

# DB subnet group for RDS instances, using the created subnets
resource "aws_db_subnet_group" "default" {
  name       = "docker-django-subnet"
  subnet_ids = [aws_subnet.subnet1.id, aws_subnet.subnet2.id]
  tags = {
    Name = "My DB Subnet Group"
  }
}

# Security group for RDS, allows PostgreSQL traffic
resource "aws_security_group" "default" {
  vpc_id = aws_vpc.default.id
  name        = "DjangoRDSSecurityGroup"
  description = "Allow all inbound traffic for RDS"
  ingress {
    from_port   = 5432
    to_port     = 5432
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "allow_all"
  }
}

# Define variable for RDS password to avoid hardcoding secrets
variable "db_password" {
  description = "The password for the database"
  type        = string
  sensitive   = true
}

# RDS instance for Django backend, publicly accessible
resource "aws_db_instance" "default" {
  allocated_storage      = 20
  storage_type           = "gp2"
  engine                 = "postgres"
  engine_version         = "16.1"
  instance_class         = "db.t3.micro"
  identifier             = "my-django-rds"
  db_name                = "DjangoRDSDB"
  username               = "adam"
  password               = var.db_password
  db_subnet_group_name   = aws_db_subnet_group.default.name
  vpc_security_group_ids = [aws_security_group.default.id]
  skip_final_snapshot    = true
  publicly_accessible    = true
  multi_az               = false
  tags = {
    Name = "DjangoRDSRDS"
  }
}

Be sure to set an environment variable for a secure db_password:

export TF_VAR_db_password="your_secure_password"

In order to use this terraform code, we have the following commands:

terraform plan # previews what resources will be created
terraform apply # applies the changes in the cloud (Create, Update, Destroy)
terraform destroy # destroys all resources created by these terraform files

Once we run terraform apply we should see a publicly accessible RDS instance. Search for “RDS” in the AWS console and go to “Databases”.

You should see a database called “my-django-rds-inctance”.

Security Creds

Then you can see the hostname and all relevant info for that Database.

Security Creds

Step 6: Connect Django to the RDS instance

Now that we have a Django Project and a RDS instance, we need to connect the two.

We’re able to do this by updating DATABASES in settings.py. Let’s do this by switching the databases value to the following map:

import os\

...

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME':     os.getenv("DB_NAME", None),     # name from main.tf
        'USER':     os.getenv("DB_USER_NM", None),  # username from main.tf
        "PASSWORD": os.getenv("DB_USER_PW", None),  # TF_VAR_db_password
        "HOST":     os.getenv("DB_HOST", None),     # RDS hostname from above image
        "PORT":     5432,                           # default PostgreSQL port
    }
}

To pull these values in properly, we need to add them as environment variables and re-activate the virtual environment.

Open up my-venv/bin/activate and add the following code at the end:

export DB_NAME=DjangoRDSDB # from main.tf
export DB_USER_NM=adam # from main.tf
export DB_USER_PW=my_password # TF_VAR_db_password
export DB_HOST=terraform-2024030800.cb2u.us-east-1.rds.amazonaws.com # RDS hostname from above image

How you can reactivate the virtual environment by running deactivate then source my-venv/bin/activate.

Now when we rerun Django with ./manage.py runserver we can go to http://127.0.0.1:8000/admin to test our user was written into AWS’s RDS database.

Next steps:

Next, we should learn how to split up Django into “local” and “production” mode and set up production Django inside Docker. Follow a tutorial on that here.

After that, we should learn to deploy this image and database into AWS as a hosted service. Follow a tutorial on that here.