Welcome back to part 4 of the series! Previously we dived into some details of building the backend. In this post we will cover the usage of Elastic Beanstalk (EB), an AWS service that provides an easy way to deploy Django web applications.

It takes care of provisioning, load balancing and setting up a whole host of other things. You can think of it as an alternative to Heroku.


Table of Contents

Pre-requisite Knowledge

You can read/watch a whole bunch of tutorials about AWS, but EB is likely not one of the first services mentioned in introductory tutorials. Despite this, I came to quite like it.

I spent a bit of time trying to configure logging for the application but couldn’t quite get it to work. Eventually I just didn’t have enough time left to figure it out.

That being said, its a good way to git push -u deploy-instantly without too much hassle.

A basic understanding of AWS makes EB easier to follow - it’s is fairly independent of others services. You can add CloudWatch (logging) and RDS (database) services to it but thats totally optional.

Caveats

The main caveat to Elastic Beanstalk is that the documentation isn’t updated. We’re told to use the latest version, but much of the documentation is actually for the older version.

The logging documentation I followed didn’t quite work for me. There were also some other guides I read through but had to tweak to make it work.

Deployment Overview

At a high level, we build the React code and package it inside the backend code. Then we render the / root as a “template” inside Django.

Building the React

Inside frontend/package.json we add the following command:

{
...
"scripts": {
		...
    "relocate:prod": "env-cmd -f .env.prod react-scripts build && rm -rf ../backend/build && mv -f build ../backend && mkdir -p ../backend/fe_assets/ && cp public/*.png ../backend/fe_assets/",
  },
	...
}

Here we build the codebase for deployment using env-cmd to ensure that we set the right environment variables (see part 2).

Then we move these build files into backend. The last part is copying any public images across so that they are rendered in the application.

backend/
├─ content/
  ├─ views.py
  ├─ ...
├─ Makefile
├─ build/ (from frontend)
  ├─ index.html
  ├─ css/
  ├─ js/
├─ app_name/
  ├─ settings/
    ├─ base.py
    ├─ dev.py
    ├─ ...

Configuring Django

So now the build files are integrated into the backend, we need to point the Django root to the React root.

# /backend/app_name/urls.py

from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, re_path, include
from django.shortcuts import render

def render_react(request):
    # Note to reader: This index.html is in /build!
    return render(request, "index.html")

urlpatterns = [
    path("admin/", admin.site.urls),
		...
    path("api/app_user/", include("app_user.urls")),
    path("api/content/", include("content.urls")),

    # Serving React as static index.html
    re_path(r"^$", render_react),
    re_path(r"^(?:.*)/?$", render_react),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

And set Django settings to read from the /build path.

# /backend/app_name/settings.py/prod.py
TEMPLATES = [
  {
    "BACKEND": "django.template.backends.django.DjangoTemplates",
    # Tell Django where to find Reacts index.html file
    "DIRS": [os.path.join(BASE_DIR, "build")],
    "APP_DIRS": True,
    "OPTIONS": {
      "context_processors": [
      "django.template.context_processors.debug",
      "django.template.context_processors.request",
      "django.contrib.auth.context_processors.auth",
      "django.contrib.messages.context_processors.messages",
      ],
    },
  },
]

STATICFILES_DIRS = [
  # Tell Django where to look for React's static files (css, js)
  # TEMPLATES already tells Django where to find the index.html
  os.path.join(BASE_DIR, "build/static"),
  os.path.join(BASE_DIR, "fe_assets"),
]

So now you can check if things work locally by running

python3 manage.py runserver --settings="app_name.settings.prod"

Deploying to Elastic Beanstalk

Now that we’re ready to go, we can configure our repo for deployment to elastic beanstalk.

Before you start, make sure you register for an AWS account and create a role that allows you to deploy to Elastic Beanstalk, and setup your IAM permissions on your computer.

If you haven’t worked with IAM before, it might be good to watch a few tutorials to familiarise yourself.

Before you cdeploy settings up an SSH connection to your EB environment is very useful for connecting to the prod EC2 to debug post-deployment.

Once you set this stuff up, you can

# 1) Create the artifact
cd ./frontend && CI=false npm run-script relocate:prod && cd ../backend

# 2) Initiate the app
eb init --platform python-3.7 app-name --region ap-southeast-2

# 3) Create the environment
eb create app-name-env

# 4) Deploy! The artifact specified will be sent.
eb deploy

# 5) If you need to rebuild the environment
aws elasticbeanstalk rebuild-environment --environment-name app-name-env --region ap-southeast-2

From there, EB will build the environment automatically and provide a URL for you to check the site!

Bitbucket Pipelines

For the project, we actually used bitbucket pipelines as our CI/CD to deploy to dev and prod.

The deployment is defined in a yaml file and is similar in flow to what you would see in Github actions.

Don’t forget to add a domain name to the website!

Trailer

In the closing chapter, we’ll discuss the key takeaways from this project and whether I would recommend this tech stack.

Stay tuned for the final part!

UPDATE: You can read the final part here