DEV Community

Vadym Khodak
Vadym Khodak

Posted on • Updated on • Originally published at vadymkhodak.com

Deploying a Django project on AWS Lambda using Serverless (Part 1)

BLUF

As a follow-up to a post where we looked at the most common questions about Django in the Cloud, now, I'd like to help you deploy your Django App on Amazon Web Services and make you more independent from other developers like DevOps and CloudOps Engineers. There are many options for doing that but I'd like to show one of them and I hope that in the end you will be able to deploy your Django App on AWS Lambda using Serverless.

I was motivated by Daniil Bratchenko's article Don’t Let Software Vendors Dictate Your Business Processes to start writing this blog post.

It is so hard to find Software that will fit all your business processes as all companies are unique. This is why many companies have decided to set up dedicated teams building Software for their specific business processes and needs. From my personal point of view, Django App on AWS Lambda using Serverless is a good solution for cases like that.

Also, you can use this approach for prototyping your projects running them at their early stage.

There are a few advantages and disadvantages of using this approach.

Advantages of using AWS Lambdas:

  • cost (AWS Lambda is cheaper comparing to AWS EC2);
  • simplicity in running and maintaining;
  • scalability;
  • quick deployment.

the disadvantages:

  • AWS Lambda requires some extra time to run your App;
  • size limit for deployment package;
  • API Gateway limitation (30-sec timeout, 6 Mb response body size);
  • it might cost more than AWS EC2 if there are too many requests.

Prepare AWS infrastructure

Probably, you are aware of a variety of AWS services required for web applications. In order to deploy a Django project on AWS Lambdas you should prepare your AWS infrastructure.
There is a list of AWS services I use for my Django project:

  1. Lambdas to run our wsgi application
  2. API Gateway to handle HTTP request and send them to Lambdas
  3. S3 buckets for Lambda deployments and storing static files
  4. CloudFront distribution for serving static files from S3 bucket
  5. RDS for a database (I use Postgres)
  6. VPC with subnets
  7. EC2 for Security Groups
  8. IAM for roles and policies
  9. CloudWatch for logs

AWS Lambdas, API Gateway will be created automatically by Serverless. I will try to walk you though the process of creating all the necessary AWS resources in my following blog posts.

Create a Django project

Django startproject command allows us to create a simple Django project, in addition to that, there are some great Cookiecutter projects that can help you start your project easily (For example Cookiecutter Django). I use default django-admin startproject cli command in this example.

pip install django
django-admin startproject django_aws_lambda
Enter fullscreen mode Exit fullscreen mode

Configure requirements

There are many options to store your project requirements, for example requirements.txt, Pipfile, pyproject.toml. You can one of these options. I'm using requirements.txt here.

  • create requirements.txt file in a root directory of the project
  • add the following libraries to requirements.txt file:
boto3==1.17.17
Collectfast==2.2.0
Django==3.1.7
django-environ==0.4.5
psycopg2-binary==2.8.6
Werkzeug==1.0.1
Enter fullscreen mode Exit fullscreen mode
  • create and activate virtual environments

Choose your preferred tool for managing virtual environments (like conda, pyenv, virtualenv, etc.)

  • install requirements
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Create hello Django app

  • create app using startapp Django command
python manage.py startapp hello
Enter fullscreen mode Exit fullscreen mode
  • create templates folder
mkdir templates
Enter fullscreen mode Exit fullscreen mode
  • create index.html file in templates folder with the following lines:
{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Greeting</title>
</head>
<body>
<div>
  <h1>Hello {{ name }}</h1>
<img src="{% static 'django.jpeg' %}" alt="Django" style="width: 20%">
</div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
  • create folder static in the root directory of the project
mkdir static
Enter fullscreen mode Exit fullscreen mode
  • add an image file to static folder, for example django.jpeg

  • update hello/views.py

from django.shortcuts import render


# Create your views here.
def hello(request, resource=None):

    return render(request, "index.html", {"name": resource or 'World'})

Enter fullscreen mode Exit fullscreen mode

Configure environments variables:

  • create .env file in the root directory of the project
  • configure the following variables:
STAGE='production'
DB_HOST=<your database host>
DB_USER=<your database user name>
DB_PASSWORD=<your database password>
DB_NAME=<your database name>
DJANGO_SECRET_KEY=<some django secret key>
AWS_S3_CDN_DOMAIN=<your Cloud Front distribution, like: `<distribution id>.cloudfront.net`>
AWS_S3_REGION_NAME=<your AWS region>
AWS_STORAGE_BUCKET_NAME=<AWS s3 bucket for static files with punlic policies>
DEPLOYMENT_BUCKET=<AWS s3 bucket for deployment>
AWS_KEY_ID=<your AWS Key Id>
AWS_SECRET=<your AWS Secret>
DJANGO_ADMIN_URL=<Django admin url>
DJANGO_ALLOWED_HOSTS=<list of allowed hosts separated by coma>
Enter fullscreen mode Exit fullscreen mode

Create configuration for local development and production

  • update settings.py in django_aws_lambda folder with the following lines:
"""
Django settings for django_aws_lambda project.

Generated by 'django-admin startproject' using Django 1.11.29.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

from pathlib import Path

import environ

ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
env = environ.Env()
READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=True)
if READ_DOT_ENV_FILE:
    env.read_env(str(ROOT_DIR / '.env'))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('DJANGO_SECRET_KEY', default='<some-secured-key>')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['127.0.0.1', 'localhost'])
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hello',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'django_aws_lambda.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            str(ROOT_DIR / 'templates'),
            str(ROOT_DIR / 'staticfiles'),
        ],
        'OPTIONS': {
            'loaders': [
                'django.template.loaders.filesystem.Loader',
                'django.template.loaders.app_directories.Loader',
            ],
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'django_aws_lambda.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': ROOT_DIR / "db.sqlite3",
    }
}


# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_ROOT = str(ROOT_DIR / 'staticfiles')
STATIC_URL = '/static/'

STATICFILES_DIRS = [str(ROOT_DIR / 'static')]
STATICFILES_FINDERS = [
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
MEDIA_ROOT = str(ROOT_DIR / 'media')
MEDIA_URL = '/media/'
ADMIN_URL = env('DJANGO_ADMIN_URL')
Enter fullscreen mode Exit fullscreen mode
  • create local.py and production.py files inside django_aws_lambda folder on the same level as settings.py
  • add the following lines to local.py:
from .settings import *  # noqa


DEBUG = True

Enter fullscreen mode Exit fullscreen mode
  • add the following lines to production.py:
from .settings import *  # noqa

DEBUG = False
DATABASES["default"] = {
    'ENGINE': 'django.db.backends.postgresql',
    'NAME': env("DB_NAME"),
    'USER': env("DB_USER"),
    'PASSWORD': env("DB_PASSWORD"),
    'HOST': env("DB_HOST"),
    'PORT': '5432',
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True  # noqa F405
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60)  # noqa F405
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=False)
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 60
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True)
SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True)
SECURE_CONTENT_TYPE_NOSNIFF = env.bool("DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True)
INSTALLED_APPS += ["storages"]  # noqa F405
AWS_KEY = env("AWS_KEY_ID")
AWS_SECRET = env("AWS_SECRET")
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
AWS_QUERYSTRING_AUTH = False
_AWS_EXPIRY = 60 * 60 * 24 * 7
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate"}
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", default=None)
AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None)
aws_s3_domain = AWS_S3_CUSTOM_DOMAIN or f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
STATICFILES_STORAGE = "django_aws_lambda.utils.StaticRootS3Boto3Storage"
COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy"
STATIC_URL = f"https://{aws_s3_domain}/static/"
DEFAULT_FILE_STORAGE = "django_aws_lambda.utils.MediaRootS3Boto3Storage"
MEDIA_URL = f"https://{aws_s3_domain}/media/"
MEDIAFILES_LOCATION = "/media"

STATICFILES_LOCATION = "/static"
TEMPLATES[-1]["OPTIONS"]["loaders"] = [  # type: ignore[index] # noqa F405
    (
        "django.template.loaders.cached.Loader",
        [
            "django.template.loaders.filesystem.Loader",
            "django.template.loaders.app_directories.Loader",
        ],
    )
]
Enter fullscreen mode Exit fullscreen mode
  • update wsgi.py file in django_aws_lambda folder with the following lines:
"""
WSGI config for django_aws_lambda project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
"""

"""
WSGI config for django_aws_lambda project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_aws_lambda.production')

application = get_wsgi_application()

Enter fullscreen mode Exit fullscreen mode
  • update urls.py file in django_aws_lambda folder with the following lines:
"""django_aws_lambda URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

from hello.views import hello

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', hello),
    path('<path:resource>', hello),
]

Enter fullscreen mode Exit fullscreen mode
  • update manage.py with the following lines:
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_aws_lambda.production')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

Enter fullscreen mode Exit fullscreen mode
  • create a folder utils inside django_aws_lambda
  • create storages.py file inside utils folder with the following lines:
from storages.backends.s3boto3 import S3Boto3Storage


class StaticRootS3Boto3Storage(S3Boto3Storage):
    location = "static"
    default_acl = "public-read"


class MediaRootS3Boto3Storage(S3Boto3Storage):
    location = "media"
    file_overwrite = False
Enter fullscreen mode Exit fullscreen mode

Run Django project locally

  • set environment variable with a path to Django local configuration file
export DJANGO_SETTINGS_MODULE=django_aws_lambda.local
Enter fullscreen mode Exit fullscreen mode
  • migrate database changes
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode
  • create a superuser in the database
python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

Then provide a username, user email, password, and confirm the password

  • collect static files
python manage.py collectstatic
Enter fullscreen mode Exit fullscreen mode
  • run server locally
python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Create serverless configuration

  • initialize npm:
npm init
Enter fullscreen mode Exit fullscreen mode
  • install serverless
npm install -g serverless
Enter fullscreen mode Exit fullscreen mode
  • install serverless plugins
npm install -P serverless-dotenv-plugin
npm install -P serverless-prune-plugin
npm install -P serverless-python-requirements
npm install -P serverless-wsgi
Enter fullscreen mode Exit fullscreen mode
  • create serverless.yaml file with the following configuration:
service: django-aws-lambda

plugins:
  - serverless-dotenv-plugin
  - serverless-prune-plugin
  - serverless-python-requirements
  - serverless-wsgi
useDotenv: true

custom:
  dotenv:
    logging: false
  pythonRequirements:
    dockerizePip: non-linux
    zip: true
    fileName: requirements.txt
  stage: ${env:STAGE}
  wsgi:
    app: django_aws_lambda.wsgi.application
    packRequirements: false
  prune:
    automatic: true
    number: 3

functions:
  - app:
      handler: wsgi_handler.handler
      events:
        - http: ANY /
        - http: ANY /{proxy+}
      timeout: 30

provider:
  name: aws
  role: arn:aws:iam::<role_id>:role/<role_name>
  profile: <your-profile-name>  # make sure that you configured aws profile using `aws configure --profile <your-profile-name>`
  region: us-east-1
  runtime: python3.8
  versionFunctions: false
  stage: ${env:STAGE}
  timeout: 60
  vpc:
    securityGroupIds:
      - <your-security-group-id>
      - <your-security-group-id>
    subnetIds:
      - <your-subnet-id>
      - <your-subnet-id>
  deploymentBucket:
    name: ${env:DEPLOYMENT_BUCKET}
  apiGateway:
    shouldStartNameWithService: true
  lambdaHashingVersion: 20201221

package:
  individually:
    true
  exclude:
    - .env
    - .git/**
    - .github/**
    - .serverless/**
    - static/**
    - .cache/**
    - .pytest_cache/**
    - node_modules/**
Enter fullscreen mode Exit fullscreen mode

Use Docker for deploying your Django project to AWS Lambda using Serverless

  • run Amazon Linux 2 docker image:
docker run -it -v $(pwd):/root/src/ -v /Users/<your_user>/.aws:/root/.aws amazonlinux:latest bash
Enter fullscreen mode Exit fullscreen mode
  • install the necessary Unix dependencies:
yum install sudo -y
sudo yum install -y gcc openssl-devel bzip2-devel libffi-devel wget tar sqlite-devel gcc-c++ make
Enter fullscreen mode Exit fullscreen mode
  • install node.js version 14:
curl -sL https://rpm.nodesource.com/setup_14.x | sudo -E bash - 
sudo yum install -y nodejs
Enter fullscreen mode Exit fullscreen mode
  • install Python 3.8.7:
cd /opt
sudo wget https://www.python.org/ftp/python/3.8.7/Python-3.8.7.tgz
sudo tar xzf Python-3.8.7.tgz
cd Python-3.8.7
sudo ./configure --enable-optimizations
sudo make altinstall
sudo rm -f /opt/Python-3.8.7.tgz
Enter fullscreen mode Exit fullscreen mode
  • create python and pip aliases:
alias python='python3.8'
alias pip='pip3.8'
Enter fullscreen mode Exit fullscreen mode
  • update pip and setuptools:
pip install --upgrade pip setuptools
Enter fullscreen mode Exit fullscreen mode
  • install serverless:
npm install -g serverless
Enter fullscreen mode Exit fullscreen mode
  • move to project directory
cd /root/src/
Enter fullscreen mode Exit fullscreen mode
  • install requirements inside docker container:
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode
  • set environment variable with a path to django production configuration file
export DJANGO_SETTINGS_MODULE=django_aws_lambda.production
Enter fullscreen mode Exit fullscreen mode
  • migrate database changes
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode
  • create a superuser in the database
python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

Then provide a username, user email, password, and confirm the password

  • collect static files to AWS S3 bucket
python manage.py collectstatic
Enter fullscreen mode Exit fullscreen mode

If you get NoCredentialsError from botocore you should add to environment variables AWS_PROFILE:

export AWS_PROFILE=<your-aws-profile-name>
Enter fullscreen mode Exit fullscreen mode
  • install serverless packages from package.json
npm install
Enter fullscreen mode Exit fullscreen mode
  • deploy your Django project to AWS Lambda using Serverless
serverless deploy -s production
Enter fullscreen mode Exit fullscreen mode

Your response will look like that:

Serverless: Adding Python requirements helper to ....
Serverless: Generated requirements from /root/src/requirements.txt in /root/src/.serverless/requirements.txt...
Serverless: Installing requirements from /root/.cache/serverless-python-requirements/ ...
Serverless: Using download cache directory /root/.cache/serverless-python-requirements/downloadCacheslspyc
Serverless: Running ...
Serverless: Zipping required Python packages for ....
Serverless: Using Python specified in "runtime": python3.8
Serverless: Packaging Python WSGI handler...
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Removing Python requirements helper from ....
Serverless: Injecting required Python packages to package...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service app.zip file to S3 (60.48 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..........
Serverless: Stack update finished...
Service Information
service: <your-serverless-service-name>
stage: production
region: <your-aws-region>
stack: <your-serverless-service-name>-pronduction
resources: 8
api keys:
  None
endpoints:
  ANY - https://<some-id>.execute-api.<your-aws-region>.amazonaws.com/production
  ANY - https://<some-id>.execute-api.<your-aws-region>.amazonaws.com/production/{proxy+}
functions:
  app: <your-serverless-service-name>-production-app
layers:
  None
Serverless: Prune: Running post-deployment pruning
Serverless: Prune: Querying for deployed function versions
Serverless: Prune: <your-serverless-service-name>-production-app has 3 additional versions published and 0 aliases, 0 versions selected for deletion
Serverless: Prune: Pruning complete.
Serverless: Removing old service artifacts from S3...

**************************************************************************************************************************************
Serverless: Announcing Metrics, CI/CD, Secrets and more built into Serverless Framework. Run "serverless login" to activate for free..
**************************************************************************************************************************************
Enter fullscreen mode Exit fullscreen mode

Now, your Django Project will be available at this URL:
https://<some-id>.execute-api.<your-aws-region>.amazonaws.com/production
image

Congratulations!

Here is a link to a GitHub repository with the code shown in this blog post.

If you want to learn more about Django projects on AWS Lambdas follow me on Twitter (@vadim_khodak) I plan to write a post showing how to create all the necessary AWS resources for this Django project, how to add React.js client to the Django project and more.

Latest comments (20)

Collapse
 
kenny1323 profile image
kenny1323 • Edited

Next time you can create everything inside the container.
pull image001
docker run container001 from image001
docker exec -it container001 bash

now you are inside the bash shell of the container001

apt install python django python-env git
mkdir project001
git clone http//project001.git
etc..

Collapse
 
andoromain profile image
Andoromain • Edited

Hi Vadym, i have an error when i want to execute my lambda function
{
"errorMessage": "Unable to import django-aws-lambda.wsgi.application",
"errorType": "Exception",
"stackTrace": [
" File \"/var/lang/lib/python3.8/imp.py\", line 234, in load_module\n return load_source(name, filename, file)\n",
" File \"/var/lang/lib/python3.8/imp.py\", line 171, in load_source\n module = _load(spec)\n",
" File \"\", line 702, in _load\n",
" File \"\", line 671, in _load_unlocked\n",
" File \"\", line 843, in exec_module\n",
" File \"\", line 219, in _call_with_frames_removed\n",
" File \"/var/task/wsgi_handler.py\", line 115, in \n wsgi_app = import_app(config)\n",
" File \"/var/task/wsgi_handler.py\", line 48, in import_app\n raise Exception(\"Unable to import {}\".format(config[\"app\"]))\n"
]
}

Could you help me?

Collapse
 
vaddimart profile image
Vadym Khodak

Unfortunately, I don't have enough time right now since I'm in Ukraine Army fighting against the russians.
I can advice you just try to start from scratch. Everything should work.
Sorry that I'm not really helpful.

Collapse
 
changchingchung profile image
John

If my understanding is correct, Lambda's main use is to map one API endpoint to one Lambda function.

In your example, you put the whole Django APP in it and map any http request to it, which means no matter what API you call, it will only and always invoke this Lambda Django APP to respond.

Does this lose the point of one API, one Lambda function?

Collapse
 
vaddimart profile image
Vadym Khodak

Yes, you understand this correctly.
Here is one Lambda - wsgi server which is Django App.
As I mentioned in my posts this is probably not the best solution for production app with many connections which require a quick response.
But it can for many other scenarios like MVP, app without many connections and great performance.
If you wanted to make your API as a one Lambda function I would recommend you to look at AWS Chalice library.
I'm sorry for such short answer.
I am not able to make it deeper and better because I'm on the front line of Ukrainian - russian war.

Collapse
 
alexbellin profile image
alexbellin

I'm not well versed on docker, but I found most of the other sections very thorough.

  • How do we get the requirements.txt and package.json files onto the docker container?
  • what about the source code files?

Otherwise, many thanks for this great tutorial

Collapse
 
vaddimart profile image
Vadym Khodak

Thank you for the comment.
Unfortunately, I'm not able to help you right now since I had to join the Ukraine Army to defend my country.
I'll answer all questions after our victory!
Slava Ukraine!

Collapse
 
alexbellin profile image
alexbellin

After a bit on research, I found that I needed to be at the root directory when running the docker run command. The -v command seems to copy the src to /root/src (seperated by colon)

Collapse
 
sumankanukollu profile image
sumankanukollu

I enjoyed reading this article.

Small correction is required in "production.py" file like mentioned in below lines: (Because our StaticRootS3Boto3Storage and MediaRootS3Boto3Storage classes placed in storages.py file

STATICFILES_STORAGE = "django_aws_lambda.utils.storages.StaticRootS3Boto3Storage"
DEFAULT_FILE_STORAGE = "django_aws_lambda.utils.storages.MediaRootS3Boto3Storage"
Enter fullscreen mode Exit fullscreen mode
Collapse
 
radek profile image
RadekTester

Great article. I really liked it!

Collapse
 
a5s_s7r profile image
A5S S7R

Hi Vadim,

I found this very interesting tutorial, as I am looking for an easy way to host a Django app with low traffic.

Just got curious about your approach of setting up the docker container.
Maybe I missed some context, as I am pre first coffee.
Is there a reason you did't create a Dockerfile with FROM amazonlinux:latest?

Thanks for the great tutorial!

Collapse
 
vaddimart profile image
Vadym Khodak

Hi Andreas,
Thank you for the comment.
Creating a docker container is not a required step, but I had issues deploying serverless apps to AWS Lambdas from MacOS.
It is better to deploy serverless app from the same OS as AWS Lambda runtime.
Based on AWS documentation, it is Amazon Linux 2 for Python 3.8.

Let me know if you have any additional questions

Collapse
 
kostjapalovic profile image
Kostja Appliku.com

Great article. Grew my curiosity about serverless framework.

Okay, Question here.

How it compares with Zappa? Have you tried it? I’ve built one big project with it (in terms of amount of code, no production usage so far).

Thoughts about Zappa:

  • Easy to deploy
  • Easy to get SSLed custom domain
  • convenient json configuration
  • Had to suffer a bit to make WeasyPrint (for generating PDFs) work, had to create an additional Layer with required files.
  • loved their @task decorator to make async functions
  • scheduling of function execution in json files works pretty cool too.

With non-production amount of traffic it is kind of slow at times. For example, when I work alone and test the app and there is an async task (I click something that causes email sending in @task function - it feels like it doesn’t go async. Or it take a while for another instance to spin up (watching cloudwatch logs in terminal).

Launch of that app is soon, so I am thrilled to see how all this stuff will behave.

But with initial learning curve, before I see any benefits of scaling, I think that could be better off with small ec2 instance.

Collapse
 
vaddimart profile image
Vadym Khodak

Hi Kostja,

I didn't have experience with Zappa. I tried it one time.
Zappa/Serverless is just a one of ways to deploy Django App on AWS infrastructure.
I think for some cases it will be better to use Zappa for other casses it is better to use Serverless or something else.

The same thing is about using EC2 instances vs AWS Lambdas.

About using nice json format, from my personal point of view yaml is more human readble format than json. But it is just my personal opinion. ("Many people many minds")

I have a couple of small projects (based on trafic) where Django is deployed on AWS Lambdas and it works well.

Regarding async task, I may be wrong, I think that if it will not work as async function if you call it from sync function. Django is not async framework, as far as I know.

Thank you for your comment here. I appreciate it.

Collapse
 
kostjapalovic profile image
Kostja Appliku.com

Hey. Thanks for the response.

I don't honestly care about json vs yaml. I mean i will not go in a holywar abou it :D but probably yaml is more human readable.

Anyway, the whole question was to make sure if i am missing out on not looking hard into the Serverless framework, maybe it is somehow superior to Zappa or anything like that.

From what I understand it is not and both are just possible options, if I understood you right. Thanks again.

Now about async stuff.

If a function is wrapped in a Zappa @task, then when it is called it checks if it is called in lambda environment. in this case it invokes a lambda function goes on.

So this invocation on cold dev/staging environment(when there are no concurrent requests, for example when only one me playing with the app) it takes quite a while to invoke it. But it feels that it gets faster once at least 2-3 people work with the app in parallel.

Anyway. Thanks again for the post!
Great work!

Thread Thread
 
vaddimart profile image
Vadym Khodak

Thank you for the feedback.

In Serverless you also can call another Lambda function from Django Lambda. But I think it is a bit more complex than just @task decorator. There are a lot of plugins for Serverless framework. One of them serverless-plugin-warmup. It can help you to keep warm your lambdas.

Collapse
 
pedrozan profile image
Pedro Henrique Schleder

The django app will live inside the lambda? Will it have to spin up on every equest?

Collapse
 
vaddimart profile image
Vadym Khodak

Great question! Thank you for asking!
I wasn't able to find a right answer.
But here is a link to some article about related topic
acloudguru.com/blog/engineering/ho...!

As I mentioned in the beginning of the post it is not the best solution for applications with many requests and with necessity to respond really fast.

Also, there is a serverless plugin that can help to warmup your lambda (serverless-plugin-warmup).

Collapse
 
pedrozan profile image
Pedro Henrique Schleder

Thanks for answering, I will look into it. Forgot to say I really liked the idea and the tutorial 😁

Thread Thread
 
vaddimart profile image
Vadym Khodak

My pleasure, happy to help