Elastic Beanstalk Multicontainers + Docker + Nginx + Gunicorn + Django + Postgres

This is a guide on how to deploy a multicontainer Docker web application on Amazon Elastic Beanstalk. The web application will be a basic Django hello world, but has a completely functioning Postgres database. This post is geared towards beginners.

Motivation

Learning web development is challenging, and a lot of it is just trying out what works. But it helps to understand the big picture.

The Big Picture

Web Client <=> Elastic Beanstalk Load Balancer <=> Nginx <=> Gunicorn <=> uWSGI <=> Django

Install Docker

Set up the project directory.

mkdir myproject
cd myproject

Create a virtual environment, and activate.

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

Create requirements.txt:

Django==2.0.7
gunicorn==19.6.0
psycopg2==2.7.5
django-admin.py startproject myproject app

In app/myproject/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}
ALLOWED_HOSTS = ['app']
pip install docker-compose

config/app/Dockerfile

# Creating image based on official python3 image
FROM python:3

# Your contacts, so people blame you afterwards
MAINTAINER Pavel Gasanov <pogasanov@gmail.com>

# Sets dumping log messages directly to stream instead of buffering
ENV PYTHONUNBUFFERED 1

# Creating and putting configurations
RUN mkdir /config
ADD config/app /config/

# Installing all python dependencies
RUN pip install -r /config/requirements.txt

# Open port 8000 to outside world
EXPOSE 8000

# When container starts, this script will be executed.
# Note that it is NOT executed during building
CMD ["sh", "/config/on-container-start.sh"]

# Creating and putting application inside container
# and setting it to working directory (meaning it is going to be default)
RUN mkdir /app
WORKDIR /app
ADD app /app/

config/app/on-container-start.sh

# Create migrations based on django models
python manage.py makemigrations

# Migrate created migrations to database
python manage.py migrate

# Start gunicorn server at port 8000 and keep an eye for app code changes
# If changes occur, kill worker and start a new one
gunicorn --reload myproject.wsgi:application -b 0.0.0.0:8000

config/nginx/app.conf

# define group app
upstream app {
  # balancing by ip
  ip_hash;

  # define server app
  server app:8000;
}

# portal
server {
  # all requests proxies to app
  location / {
        proxy_pass http://app/;
    }

  # only respond to port 8000
  listen 8000;

  # domain localhost
  server_name localhost;
}

docker-compose.yml

# File structure version
version: '3'

services:
  # Database based on official postgres image
  db:
    image: postgres
    hostname: db

  # Our django application
  # Build from remote dockerfile
  # Connect local app folder with image folder, so changes will be pushed to image instantly
  # Open port 8000
  app:
    build:
      context: .
      dockerfile: config/app/Dockerfile
    hostname: app
    volumes:
      - ./app:/app
    expose:
      - "8000"
    depends_on:
      - db

  # Web server based on official nginx image
  # Connect external 8000 (which you can access from browser)
  # with internal port 8000(which will be linked to app port 8000 in configs)
  # Connect local nginx configuration with image configuration
  nginx:
    image: nginx
    hostname: nginx
    ports:
      - "8000:8000"
    volumes:
      - ./config/nginx:/etc/nginx/conf.d
    depends_on:
      - app

Then you can do docker-compose up

docker build -t raymondhfeng/myproject -f config/app/Dockerfile .
docker login
docker push raymondhfeng/myproject
pip install awsebcli

Dockerrun.aws.json

{
 "AWSEBDockerrunVersion": "1",
 "Image": {
   "Name": "pogasanov/myproject:latest",
   "Update": "true"
 },
 "Ports": [
   {
     "ContainerPort": "8000"
   }
 ]
}

will need iam with these permissions

AmazonS3FullAccess AWSCodeDeployFullAccess AWSElasticBeanstalkFullAccess AWSCodeDeployRole AWSElasticBeanstalkService

AWSElasticBeanstalkMultiContainer

eb init
eb create

be careful, dont lose track of your usage of amazon ec2 instances

References:

  • https://github.com/micahhausler/container-transform
  • https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
  • https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/single-container-docker-configuration.html
  • https://pogasanov.ru/posts/django-docker-aws
    • https://github.com/pogasanov/django-docker-aws
  • https://docs.docker.com/storage/volumes/
  • https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_docker_ecs.html
  • https://docs.docker.com/compose/networking/
  • https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-django.html
  • https://www.sean-lan.com/2016/09/15/django-uwsgi-nginx/
  • https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-18-04
  • https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/java-se-nginx.html
  • https://console.aws.amazon.com/vpc/home?region=us-east-1
Written on January 14, 2021