Containers

Deploy and scale Django applications on AWS App Runner

AWS App Runner is a fully managed container application service that lets you build, deploy, and run containerized web applications and API services without prior infrastructure or container experience. You can start with your source code or a container image, and AWS App Runner manages all infrastructure including servers, networking, and load balancing for your application.

Django is an open-source web application framework written in Python. Thanks to built-in features such as an object-relational mapper (ORM), URL routing, authentication system, templating system, and more, it is a popular choice for developing web applications and APIs.

AWS App Runner makes it easy to deploy Django applications directly from a source code repository or container image. AWS App Runner provisions resources, automatically scales the number of containers up or down to meet the needs of your application, and load balances traffic to ensure high availability.

Thanks to its integration with Amazon CloudWatch, AWS App Runner provides detailed metrics and logs without manual setup. AWS App Runner can communicate with other AWS services running in an Amazon Virtual Private Cloud (Amazon VPC) such as managed databases, caches, queues, and more.

This post shows how to deploy and scale a Django web application on AWS App Runner and how to securely connect to a managed database with Amazon Relational Database Service (Amazon RDS) for PostgreSQL.

Solution overview

Architecture diagram showing GitHub, AWS App Runner, AWS Secrets Manager, AWS PrivateLink, and Amazon RDS.

The solution you are going to set up as part of this walkthrough comprises the following elements as shown in the architecture diagram:

  • An AWS App Runner service running your Django application in an AWS-managed VPC.
  • An RDS for PostgreSQL database instance running in your own customer-managed VPC. App Runner privately connects to the RDS instance using AWS PrivateLink.
  • AWS Secrets Manager to securely store and access the database secret from App Runner.
  • A GitHub repository from which the Django application source code will be deployed.

Walkthrough

Prerequisites

Setting up a sample Django project

To demonstrate the deployment process, we’re going to set up a sample Django project. As a first step, create a project directory and set up a Python virtual environment using the venv module:

mkdir django-apprunner
cd django-apprunner
python3 -m venv .venv

Next, activate the virtual environment and install Django:

source .venv/bin/activate
pip install django==4.2.2

From now on, make sure the virtual environment is activated when following this walkthrough.

Now you can start a new Django project using the django-admin command-line utility:

django-admin startproject myproject
cd myproject

At this point, your folder structure should look as follows:

.
└── django-apprunner
    ├── .venv
    └── myproject
        ├── manage.py
        └── myproject
            ├── __init__.py
            ├── asgi.py
            ├── settings.py
            ├── urls.py
            └── wsgi.py

Let’s test if our application runs correctly:

python manage.py runserver

If you visit http://127.0.0.1:8000 in a web browser, then you’ll see the default Django welcome screen:

Firefox web browser showing the default Django welcome screen: The install worked successfully! Congratulations!

Preparing the application for deployment

While runserver is great for local development, it’s not suitable for production environments due to performance and security limitations. Instead, Django recommends using a WSGI server such as gunicorn. Similarly, we can’t rely on runserver to serve our static files in production. Here, we can opt for WhiteNoise as a simple yet scalable option for serving static files.

Let’s install these two packages in our virtual environment:

pip install gunicorn==20.1.0 whitenoise==6.4.0

Output all installed packages in a requirements file:

pip freeze > requirements.txt

Before you can deploy to AWS App Runner, you need to make some changes to the Django settings.py file located in django-apprunner/myproject/myproject/settings.py.

First we need to update the ALLOWED_HOSTS to allow AWS App Runner to serve the Django application:

ALLOWED_HOSTS = [".awsapprunner.com"]

Next, update and add the STATIC_URL and STATIC_ROOT settings for AWS App Runner and define WhiteNoise as staticfiles storage by adjusting the STORAGES setting:

STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"

STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    },
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

Finally, add WhiteNoise to the middleware list:

MIDDLEWARE = [
    # ...
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ...
]

Deploying to AWS App Runner

Configuring the deployment

AWS App Runner enables you to deploy from a GitHub repository or via a Docker image. In this walkthrough, you’ll use the code-based deployment from GitHub.

In case of AWS App Runner code-based deployments, you can define deployment configuration in the AWS Management Console or using a configuration file in your source code repository. When choosing the configuration file, any changes to the deployment options are tracked similarly to how changes to the source code are tracked.

Create an apprunner.yaml file in the django-apprunner/myproject directory to use the configuration file approach:

version: 1.0
runtime: python3
build:
  commands:
    build:
      - pip install -r requirements.txt
run:
  runtime-version: 3.8.16
  command: sh startup.sh
  network:
    port: 8000

To start our application, AWS App Runner must run a number of commands, such as collectstatic for serving static files and gunicorn for starting the WSGI server. Create a startup.sh file in the django-apprunner/myproject directory to list these commands:

#!/bin/bash
python manage.py collectstatic && gunicorn --workers 2 myproject.wsgi

Create a .gitignore file in the django-apprunner/myproject directory:

*.log
*.pot
*.pyc
__pycache__
db.sqlite3
media
staticfiles

Initialize a new Git repository in the django-apprunner/myproject directory and push it to GitHub. Follow this guide for instructions on working with GitHub.

Creating and deploying the AWS App Runner service

  1. Navigate to the AWS App Runner service in the AWS Management Console and choose Create an App Runner service.
  2. For Source, choose Source code repository.
  3. Under Connect to GitHub, choose Add new and follow the instructions to connect to your GitHub account.
  4. For Deployment settings, choose Automatic.
  5. Choose Next.
  6. For Build settings, choose Use a configuration file. AWS App Runner automatically picks up the apprunner.yaml file stored in our repository.
  7. Choose Next.
  8. For Service name, enter django-apprunner.
  9. Leave the remaining settings as per default for now and choose Next.
  10. Review the configuration and choose Create & deploy.

AWS App Runner will now pull your application source code from GitHub to build a container image and deploy it.

Observe the AWS App Runner event logs to track the status of the deployment. For detailed logs, select View in CloudWatch next to the App Runner event logs and Application logs. Once your service reaches the status Running, choose the default domain for your service to see the deployed website.

Newly created django-apprunner service in the AWS App Runner console.

Connecting a PostgreSQL database with Amazon RDS

If your Django application persists data to a database, then you can choose one of the database backends supported by Django. Per default, Django uses the SQLite database, which stores its data directly on disk. However, as AWS App Runner doesn’t guarantee state persistence beyond the duration of processing a single incoming web request, you need to host a database separately, for instance PostgreSQL.

With Amazon RDS, you can deploy a scalable PostgreSQL database in minutes with cost-efficient and resizable hardware capacity. Amazon RDS manages complex and time-consuming administrative tasks such as PostgreSQL software installation and upgrades, storage management, replication for high availability and read throughput, and backups for disaster recovery.

Setting up an Amazon RDS for PostgreSQL database

  1. Go to the Amazon RDS console, choose Create database.
  2. For Choose a database creation method, choose Easy create.
  3. For Engine type, select PostgreSQL.
  4. For DB instance size, select Free tier. Learn more about the RDS Free Tier here.
  5. For DB instance identifier, input django-apprunner-db.
  6. Leave the initial username as postgres and set an initial password.
  7. Expand View default settings for Easy create and confirm the settings are the same as on the following image:

Amazon RDS default quick create configuration

8. Choose Create database.

Preparing the PostgreSQL database for Django

To connect to your PostgreSQL database from the Django application, you should create a new database user and database just for Django. To do so, start at Amazon Elastic Compute Cloud (Amazon EC2) instance or AWS Cloud9 instance with an Amazon Linux 2023 AMI in the default VPC and with the default security group (the same you used for the database instance).

SSH into the instance, install PostgreSQL and connect to your Amazon RDS instance:

sudo yum install postgresql15 -y
psql -h <Your RDS endpoint> -p 5432 -U postgres -W

Create a new django database and user. Make sure to replace <Secure password> with an actual password:

CREATE DATABASE django;
CREATE USER django WITH PASSWORD '<Secure password>';
ALTER ROLE django SET client_encoding TO 'utf8';
ALTER ROLE django SET default_transaction_isolation TO 'read committed';
ALTER ROLE django SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE django TO django;

While still in the psql shell, switch to the newly created Django database and update permissions for the public schema with the following commands. You’ll be asked to re-enter the password of the postgres user:

\connect django
GRANT ALL ON SCHEMA public TO django;

Your database is now ready to be used with Django.

Configuring the Django sample application for PostgreSQL

Install the psycopg package as Python database adapter for PostgreSQL. Also, install dj-database-url so that you can easily set up the database connection form an environment variable:

pip install psycopg2-binary==2.9.6 dj-database-url==1.3.0

Update the requirements file to include these new dependencies:

pip freeze > requirements.txt

Let’s update the settings.py to use PostgreSQL as well. First, adapt the imports as follows:

from pathlib import Path
import json
import dj_database_url
from os import environ

Next, replace the DATABASES setting so that SQLite can be used in development, but PostgreSQL is used if a DATABASE_SECRET is passed as environment variable:

if "DATABASE_SECRET" in environ:
    database_secret = environ.get("DATABASE_SECRET")
    db_url = json.loads(database_secret)["DATABASE_URL"]
    DATABASES = {"default": dj_database_url.parse(db_url)}
else:
    DATABASES = {"default": dj_database_url.parse("sqlite:///db.sqlite3")}

Finally, instruct AWS App Runner to run database migrations each time your application starts by editing startup.sh:

#!/bin/bash
python manage.py migrate && python manage.py collectstatic && gunicorn --workers 2 myproject.wsgi

Securely store the database secret in AWS Secrets Manager

AWS Secrets Manager helps you manage, retrieve, and rotate database credentials, API keys, and other secrets throughout their lifecycles. AWS App Runner allows us to inject secrets from AWS Secrets Manager during application runtime as environment variables. Follow this guide to create a new secret in AWS Secrets Manager.

For the secret’s key, input DATABASE_URL. For the value, define the database URL following the schema supported by dj-database-url as follows:

postgres://django:<Secure password>@<RDS endpoint>/django

AWS Secrets Manager relies on AWS IAM to secure access to secrets. Therefore, you need to provide AWS App Runner the necessary permissions to access your newly created secret. AWS App Runner uses an instance role to provide permissions to AWS service actions that your service’s compute instances need. Follow this guide to create a new AWS IAM role in the AWS Management Console.

Add a trust policy that declares the AWS App Runner service principal tasks.apprunner.amazonaws.com as a trusted entity to the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "tasks.apprunner.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

As a best practice, limit the permissions afforded by the role to only the secret you created in AWS Secrets Manager identified by its Amazon Resource Name (ARN):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "<Secret ARN>"
        }
    ]
}

Updating the AWS App Runner configuration

  1. Select your django-apprunner service in the AWS App Runner console. Choose the Configuration
  2. Next to Configure service, choose Edit.
  3. Under Security, for Instance role, select the django-apprunner-role AWS IAM role you created in the previous step.
  4. Under Networking, for Outgoing network traffic, create a new VPC connector by choosing Add new.
  5. Select the default VPC and its subnets as well as the default security group. See Manage VPC access for details.
  6. Choose Save changes.

Your AWS App Runner service enters the Operation in progress state while the new configuration is being applied.

Once the service is back in the Running state, you can update apprunner.yaml to reference the secret stored in AWS Secrets Manager via its ARN (i.e., replace the ARN with your own value):

version: 1.0
runtime: python3
build:
  commands:
    build:
      - pip install -r requirements.txt
run:
  runtime-version: 3.8.16
  command: sh startup.sh
  network:
    port: 8000
  secrets:
    - name: DATABASE_SECRET
      value-from: "arn:aws:secretsmanager:eu-west-1:111122223333:secret:my-django-database-secret-kh2vEL"

Finally, push all updates to GitHub. Because you have automated deployments from GitHub enabled, AWS App Runner redeploys your service and your Django application connects to the PostgreSQL database.

Considerations for scaling Django on AWS App Runner

You have successfully deployed your Django application to an autoscaling compute layer with AWS App Runner and connected to a managed PostgreSQL database with Amazon RDS for PostgreSQL. Before beginning production use, consult the Django deployment checklist for guidance on further security and performance best practices that you must set up.

In addition, consider using Amazon Simple Storage Service (Amazon S3) for hosting static and media files, for example via the django-storages package. A content delivery network such as Amazon CloudFront enables you to securely deliver static content and media files with low latency and high transfer speeds to your users.

AWS App Runner automatically scales compute resources for your AWS App Runner service up and down based on its autoscaling configuration. This configuration defines the minimum and maximum number of provisioned instances for your service as the min size and max size. AWS App Runner increases and decreases the number of instances within this range based on the maximum number of concurrent requests per instance, the max concurrency. When the number of concurrent requests exceeds this limit, AWS App Runner scales up the service.

You can adjust the per-instance CPU and memory configuration for your AWS App Runner service from 0.25 vCPUs and 0.5 GB of RAM to 4 vCPUs and 12 GB of RAM, respectively. To run your application efficiently on AWS App Runner, adjust both the CPU and memory configuration and the max concurrency to fit your workload’s requirements and use load testing to validate your setup.

AWS App Runner uses AWS Fargate as the underlying compute engine to deploy instances of your application. Depending on how high you want to set the max size for your service, you need to adjust the Fargate On-Demand vCPU resource count quota via the Service Quotas console.

Cleaning up

To avoid incurring charges, delete any resources you created as part of this walkthrough that you no longer need:

  1. Delete the AWS App Runner service by selecting the django-apprunner service in the AWS App Runner console. Choose Actions and Delete service. Confirm deletion.
  2. Delete the Amazon RDS database instance by selecting the django-apprunner-db instance in the Amazon RDS console. Choose Actions and Delete. Confirm deletion.
  3. Delete the AWS Secrets Manager secret by selecting the my-django-database-secret secret in the AWS Secrets Manager console. Choose Actions and Delete secret. Confirm deletion.
  4. Delete the AWS App Runner IAM role by selecting the django-apprunner-role role in the IAM console. Choose Actions and Delete role. Confirm deletion.

Conclusion

In this post, we showed you how to deploy and scale a Django web application on AWS App Runner and how to securely connect to a managed database with Amazon RDS for PostgreSQL. AWS App Runner streamlines Django deployments from source code or container images by provisioning resources, auto-scaling containers to meet demand, and load balancing for high availability.

To learn more about managing AWS App Runner services, take a look at the AWS App Runner Developer Guide.

App Runner is constantly evolving based on your feedback. Consult the AWS App Runner roadmap on GitHub to see what we’re working on and keep an eye on the AWS Containers blog for news around AWS App Runner.