Developer Tools AWS

Django on AWS User Guide

| Product: Django on AWS

Overview

This image runs Django 5.2 LTS in an isolated Python 3.12 virtual environment behind gunicorn and nginx, backed by PostgreSQL 16 on the same instance. The Django application code and the PostgreSQL data directory each live on a dedicated, independently resizable EBS volume. A cloudimg starter Django project is preinstalled at /opt/django/cloudimg, ready for the customer to customize or replace with their own application.

The Django SECRET_KEY, the PostgreSQL role password and a Django admin superuser are generated on the first boot of every deployed instance. Two instances launched from the same Amazon Machine Image never share secrets. The rotated credentials are written to /stage/scripts/django-credentials.log with mode 0600 so that only root can read them.

ALLOWED_HOSTS is populated with the instance public IPv4 address on first boot, retrieved from the Amazon EC2 Instance Metadata Service (IMDSv2). Deployments behind an Application Load Balancer or a custom domain need to add the public hostname to DJANGO_ALLOWED_HOSTS in /etc/django/cloudimg.env and restart gunicorn.

Prerequisites

Before you deploy this image you need:

  • An Amazon Web Services account where you can launch EC2 instances
  • IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
  • An EC2 key pair in the target Region for SSH access to the instance
  • A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network and inbound port 80 from the networks your visitors will use
  • The AWS CLI (version 2) installed locally if you plan to deploy from the command line

Step 1: Launch the Instance from the AWS Marketplace

Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images choose AWS Marketplace AMIs and search for Django by cloudimg. Select the cloudimg listing and choose Select, then Continue on the subscription summary.

Pick an instance type of m5.large or larger for production workloads. Choose your EC2 key pair under Key pair (login). Under Network settings select your VPC and subnet, and either create or select a security group that allows inbound port 22 from your management network and inbound port 80 from the networks your visitors use. Leave the root volume at the default size and let the dedicated PostgreSQL and Django application volumes attach as configured by the AMI.

Select Launch instance. First boot initialisation takes approximately one minute after the instance state becomes Running and the status checks pass.

Step 2: Launch the Instance from the AWS CLI

The following block launches an instance from the cloudimg Django Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens ports 22 and 80 as described above.

aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <key-name> \
  --subnet-id <subnet-id> \
  --security-group-ids <security-group-id> \
  --block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":12,"VolumeType":"gp3"}},{"DeviceName":"/dev/sdf","Ebs":{"VolumeSize":20,"VolumeType":"gp3"}},{"DeviceName":"/dev/sdg","Ebs":{"VolumeSize":10,"VolumeType":"gp3"}}]' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=django-app-01}]'

The command prints a JSON document on success. Note the instance ID, then retrieve its public address once it is running with aws ec2 describe-instances --instance-ids <instance-id> --query "Reservations[].Instances[].PublicIpAddress" --output text.

Step 3: Connect and Retrieve Initial Credentials

Connect over SSH with the key pair you selected and the public IP address from step 2. The SSH login user depends on the operating system of the AMI variant you launched:

AMI variant SSH login user
Django on Ubuntu 24.04 ubuntu
ssh -i <path-to-key>.pem ubuntu@<public-ip>

The Django admin superuser credentials and the PostgreSQL role password are generated on first boot and written to /stage/scripts/django-credentials.log with mode 0600. Read it with sudo:

sudo cat /stage/scripts/django-credentials.log

The output looks like:

ubuntu@ip-172-31-84-135:~$ sudo cat /stage/scripts/django-credentials.log
# Django 5.2 LTS - Per-VM Credentials
# Generated: Sat May 23 07:49:07 UTC 2026
DJANGO_URL=http://34.205.72.255/
DJANGO_ADMIN_URL=http://34.205.72.255/admin/
DJANGO_ADMIN_USER=cloudimg
DJANGO_ADMIN_EMAIL=cloudimg@localhost
DJANGO_ADMIN_PASSWORD=f44c64f6ff2726276dae2bd2ad96b1e2
DJANGO_DB_NAME=cloudimg
DJANGO_DB_USER=cloudimg
DJANGO_DB_PASSWORD=8a086d3fdfb2aad49510f99dc452e471
DJANGO_SECRET_KEY=Q3hVc7Tcg1n6IxYWXLLZ8X2T1xRrIVKK6Q92eGmZWDRgypIIDQ

Store these credentials in your password manager. The values above are illustrative; your instance generates its own.

Step 4: Verify Django Is Serving

Open http://<public-ip>/ in a web browser. The cloudimg starter Django welcome page confirms that gunicorn, nginx and PostgreSQL are running:

cloudimg starter Django welcome page on AWS

Then open http://<public-ip>/admin/login/ to reach the Django admin login form:

Django admin login form on AWS

Sign in with the DJANGO_ADMIN_USER and DJANGO_ADMIN_PASSWORD from /stage/scripts/django-credentials.log. After login the Django admin dashboard lists the built in Authentication and Authorization models:

Django admin dashboard with the rotated cloudimg superuser

You can also verify Django from the command line:

/opt/django/venv/bin/python -c "import django; print(django.get_version())"

The output reads:

ubuntu@ip-172-31-84-135:~$ /opt/django/venv/bin/python -c "import django; print(django.get_version())"
5.2.13

Step 5: Customize or Replace the Starter Project

The cloudimg starter Django project lives at /opt/django/cloudimg, owned by the django system user. The project layout is the standard django-admin startproject layout with a core app added so the admin index is populated:

sudo ls /opt/django/cloudimg
ubuntu@ip-172-31-84-135:~$ sudo ls /opt/django/cloudimg
cloudimg  core  db.sqlite3  manage.py  media  static

Drop your own Django application in the same directory and re-run migrations. The recommended workflow is:

# 1. Stop gunicorn so your app can write to the project tree
sudo systemctl stop gunicorn

# 2. Replace or update the project under /opt/django/cloudimg
#    The settings.py SECRET_KEY, ALLOWED_HOSTS and DATABASE values are read
#    from environment variables — keep that pattern in your settings module.

# 3. Re-run migrations + collect static, owned by the django user
sudo -u django -E /opt/django/venv/bin/python /opt/django/cloudimg/manage.py migrate
sudo -u django -E /opt/django/venv/bin/python /opt/django/cloudimg/manage.py collectstatic --noinput

# 4. Restart gunicorn
sudo systemctl restart gunicorn

The gunicorn unit file is at /etc/systemd/system/gunicorn.service. It loads /etc/django/cloudimg.env (mode 0640, group django) for every environment variable Django needs.

Step 6: Add Your Domain to ALLOWED_HOSTS

On first boot, DJANGO_ALLOWED_HOSTS is set to localhost,127.0.0.1,<public-ip>. To serve the site on a custom domain, edit /etc/django/cloudimg.env, append your hostname, and restart gunicorn:

sudo nano /etc/django/cloudimg.env
# Set: DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,<public-ip>,django.example.com
sudo systemctl restart gunicorn

CSRF_TRUSTED_ORIGINS is derived from ALLOWED_HOSTS in settings.py, so adding the hostname to DJANGO_ALLOWED_HOSTS automatically trusts both the http:// and https:// origins for the same name.

Step 7: Increase gunicorn Workers for Higher Concurrency

The default gunicorn worker count is 3, which suits an m5.large. For larger instance types or higher request rates raise GUNICORN_WORKERS in /etc/django/cloudimg.env:

sudo sed -i 's|^GUNICORN_WORKERS=.*|GUNICORN_WORKERS=8|' /etc/django/cloudimg.env
sudo systemctl restart gunicorn

A reasonable rule of thumb is (2 * vCPU) + 1 workers; tune from there based on your application's I/O profile.

Step 8: Enable HTTPS

The image ships without TLS. The two recommended patterns are:

Terminate TLS at an AWS load balancer. Put an Application Load Balancer in front of the instance, attach an ACM certificate, listen on 443, and forward to the instance on port 80. The Django application sees X-Forwarded-Proto: https from the load balancer, which the bundled nginx site passes through to gunicorn, and Django generates https:// URLs accordingly.

Use certbot for Let's Encrypt directly on the instance. Install certbot and the nginx plugin and request a certificate:

sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d django.example.com

certbot edits the nginx vhost in place to listen on 443 with the issued certificate and sets up automatic renewal via a systemd timer.

Step 9: PostgreSQL Operations

The PostgreSQL data directory lives on the dedicated EBS volume mounted at /var/lib/postgresql. To take an application consistent snapshot you can stop gunicorn briefly, snapshot the EBS volume, then resume:

sudo systemctl stop gunicorn
# Issue: aws ec2 create-snapshot --volume-id <db-volume-id>
sudo systemctl start gunicorn

To connect to the database with the cloudimg role:

sudo -u postgres psql -d cloudimg

Or, for the cloudimg role with its rotated password:

sudo grep ^DJANGO_DB_PASSWORD= /etc/django/cloudimg.env | cut -d= -f2- | sudo PGPASSWORD="$(cat)" psql -h 127.0.0.1 -U cloudimg cloudimg

The PostgreSQL data volume can be resized via the AWS Console or CLI without service interruption, followed by sudo resize2fs $(findmnt -no SOURCE /var/lib/postgresql).

Step 10: Routine Maintenance

  • OS updatessudo apt-get update && sudo apt-get -y dist-upgrade. Reboot if the kernel changed.
  • Django and dependency upgrades — Django 5.2 LTS is supported through April 2028. To upgrade dependencies inside the cloudimg starter project's virtualenv: sudo -u django /opt/django/venv/bin/pip install --upgrade Django==5.2.<minor> gunicorn psycopg, then sudo systemctl restart gunicorn.
  • PostgreSQL upgrades — PostgreSQL 16 is the bundled major version. Major version upgrades require pg_upgrade and a fresh data directory; the recommended path is to snapshot the database, provision a new instance, restore, and cut over.
  • Backups — schedule EBS snapshots of the /var/lib/postgresql data volume and the /opt/django application volume independently. The OS root volume is reproducible from the AMI itself and does not require backup.

Screenshots

Django welcome page

The cloudimg starter Django project welcome page served by gunicorn behind nginx on port 80.

Django admin login

The Django admin login form at /admin/, with the per-instance superuser provisioned at first boot.

Django admin dashboard

The Django admin dashboard after logging in with the rotated cloudimg superuser credentials.


Support

cloudimg provides 24/7 technical support for this image by email and chat. Contact support for help with Django deployment, gunicorn and nginx tuning, PostgreSQL configuration, TLS termination and framework upgrades.