Serverless Django on Zeit Now 2.0

Updated: January 6th, 2021Published: October 9th, 2019
Time to read: 5 min

I've recently been exploring alternative hosting providers since sadly my preferred platform, DigitalOcean, does not at the time of writing provide support for serverless or "lambda" functions without the need to manage the underlying server.

In my exploration I've stumbled upon Zeit (now going by the name Vercel), which is in essence a wrapper around AWS and GCP. Zeit's UI is not nearly as slick as Netlify's, but alas, Netlify does not support the Python language for serverless functions.

My goal was to install the Django framework as a serverless function. The solution is not immediately obvious, since unfortunately, Zeit Now 2.0's sparse and outdated documentation about serverless Python at the time of writing consists of mostly broken links and references to non-existent sample code. Zeit's Python examples tend to be geared towards the Flask framework as well. One might note that Zeit's Python support is beta at the moment, so things will probably improve in the future, but nonetheless, there are quite a few gotchas to this setup which I will attempt to dissect in this post.

Enter headless Django

One might argue that Django is overkill for a lambda function, and I agree to some extent. On the other hand, I find myself often reimplementing Django features when working with Flask. I've settled on a middle road where I use Django solely for backend functionality, i.e. via a GraphQL or REST API, and discard all of Django's functionality that relates to generating and outputting HTML directly to the browser. Most often, I find myself using Django to augment the functionality of a static site, such as one generated with NuxtJS, or some other vue.js-based frontend project.

This type of use might be called headless Django, and in other words discards the following Django functionality;

  • the template system
  • static file serving
  • media file serving
  • locale system
  • form building

Django configuration

The full source code for this minimal example is cloneable at my GitHub account.

Due to the headless nature of this setup, I've stripped most configuration relating to the functionality presented in the previous chapter. Still, some customizations need to be made for the lambda environment.

Even though you wouldn't use Django's static file functionality, you need to have the config present, since Django refuses to start without it. I've simply set mine to a dummy path:

STATIC_URL = '/assets/'

You also need to modify wsgi.py, since Now 2.0 excepts the lambda to be called app, whereas Django by default generates a wsgi.py with application. In other words, replace:

application = get_wsgi_application()

with:

app = get_wsgi_application()

Because of this, you will also need to set the following in Django's config file:

WSGI_APPLICATION = 'config.wsgi.app' # as opposed to 'config.wsgi.application'

If you wish to use Zeit for viewing Django's log, you need to make Django output its log to stdout. One of the easiest ways to accomplish this is to set LOGGING in the config file to a StreamHandler with console output:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
    },
}

Another option would obviously be to ship the logs to a remote server.

If you're not going to use Django's models, you might find it easiest to prevent Django from searching for a database adapter by setting the following in Django's config file:

DATABASES = {}

Installing psycopg2 in a lambda environment is a task on its own, since due to the limited environment, you can't simply install it via pip.

Zeit Now 2.0 configuration

I'm using Zeit's official Python builder (@now/python) for this example. Please note that as of now, your code needs to be compatible with Python 3.6, so you can't write bleeding edge code.

The default maximum size for a lambda bundle is 5 MB, which is too small for Django (the app will not run). The exact limit will vary case-by-case, but I've settled on a limit of 20 MB for now. This is easy enough to set in your now.json config file:

"config": { "maxLambdaSize": "20mb" }

Another inconsistency in Zeit's documentation is related to disabling the CDN. Their documentation refers to a config option for now.json, which apparently has been removed in Now 2.0. The solution is simply to use Django's own cache controls. A reason for disabling the CDN might be because you want to use Cloudflare's CDN instead.

Conclusion

The rest of the configuration is simply a matter of following Django's deployment checklist, as you would with any other Django deployment.

As a sidenote, I find Zeit's choice of sending "magic words" to your e-mail address upon login an awful authentication solution and would much rather see something along the lines of two-factor authentication with an HSM or at least TOTP.

Another interesting project related to serverless Python which you may wish to explore, if using AWS, is Zappa.

Happy coding!


About the author
I'm a millennial digital nomad and a seasoned IT professional with over 20 years of cross-industry experience, ready to help you with supercharging your business. Drop me a note or read more about what I can do for you!

MY FULL CV

This website is only intended to provide a quick overview of what I do. Please drop me a line if you'd like me to send you my full CV, references, certifications or any additional information.

NEWSLETTER

Subscribe now to get notified of blog updates (no more than one email/month). No spam, promise!

Unsubscribe at any time. Signing up implies that you agree to the Terms.
This blog contains affiliate links to third parties. By using this site you agree to the Terms.