How To Deploy a Django App to Render.com

I pottered around a bit with Django today, blasting through the tutorial. However the deployment section was rather impenetrable for someone who doesn't even know what WSGI stands for.

I figured I should try deploying it to Render.com to learn both.

Table of Contents

TL;DR

Deploy to Render

Steps

The official Render tutorial for Django uses PostGres, which means the web server and database takes up your two free deployments. It also had issues when I tried it. So I wanted to try a "from scratch" Django project, from the official tutorial, using SQLite.

  1. Going through the whole tutorial will get you something like https://github.com/sw-yx/django-quick-start. I modified mysite a bit to add a nicer landing page. Commit it to a GitHub repo.
  2. Go to your Render dashboard and start a new web service.
  3. Make sure to configure the necessary commands and env vars:
  • DJANGO_SECRET_KEY: something strong. can generate with echo "$(openssl rand -base64 32)"
  • Build command: ./build.sh - this script, not from the tutorial, installs Python's requirements.txt - i'm not totally sure if this is needed, I copied it from https://github.com/render-examples/django-quick-start/ so I assume it is
  • Start command: cd mysite && gunicorn mysite.wsgi:application. Note the cd mysite is just because of the filestructure of my project I had set up. A "professional" project would presumably be a little flatter. You will note the weird syntax of mysite.wsgi:application - application is the variable where the WSGI callable is stored.

I had previously tried and failed to use Render's Django Quick Start repo, so if you had DJANGO_SETTINGS_MODULE set to config.settings.production, Django will be looking for a file that doesn't exist and fail nastily. Delete it.

With that, your Django app should be up and running. Mine is deployed at: https://django-test-9g3f.onrender.com/ and you can see the stateful voting app in action at https://django-test-9g3f.onrender.com/polls. https://django-test-9g3f.onrender.com/admin also works.

Render.yaml

IAAC is important for scaling/reproducability, and a nice one click deploy experience. The docs aren't fully fleshed out yet but fortunately there is a decent sample YAML file published. I was also able to find plenty of examples by searching GitHub.

services: 
- type: web
  name: djangotutorial
  env: python
  buildCommand: "./build.sh"        # ensure it's a string
  startCommand: cd mysite && gunicorn mysite.wsgi:application
  repo: https://github.com/sw-yx/django-quick-start.git # optional
  # plan: standard # optional
  healthCheckPath: /
  # autoDeploy: false             # optional
  envVars:
  - key: DJANGO_SECRET_KEY
    generateValue: true       # will generate a base64-encoded 256-bit secret

Most "Deploy to Render" buttons take the referer url to determine which repo to deploy, but you can customize this by just linking directly to the dashboard with the right path variables e.g. https://dashboard.render.com/iac/new?repoOwner=sw-yx&repoName=django-quick-start&branch=master&provider=GITHUB

Failures and Todos

You'll observe that all static assets fail to load in production, despite it working in local development. I reckon this is some misconfiguration of the static assets finding that I did. The official example has other static file finding strategies i have yet to explore.

Misc

I made this dump of Render's env vars, which I figured I could use in my application code:

{
  KUBERNETES_SERVICE_PORT_HTTPS: '443',
  PIPENV_VENV_IN_PROJECT: 'true',
  KUBERNETES_SERVICE_PORT: '443',
  BLACK: '\x1b[30m',
  RENDER_SERVICE_CONTEXT_ROOT: '/opt/render/project/src',
  PIPENV_QUIET: 'true',
  HOSTNAME: 'srv-bosg45n8jd5vhm4jst80-77f6f7f849-v7gcc',
  IS_PULL_REQUEST: 'false',
  USER_RUN_COMMAND: 'cd mysite && gunicorn mysite.wsgi:application',
  PYTHON_VERSION: '3.7.6',
  NPM_CONFIG_CACHE: '/opt/render/.cache',
  RENDER_NODE_INSTALLED: 'true',
  DJANGO_SECRET_KEY: 'REDACTED',
  ENTER_STANDOUT: '\x1b[7m',
  BLUE: '\x1b[34m',
  WHITE: '\x1b[37m',
  NODE_VERBOSE: 'false',
  CYAN: '\x1b[36m',
  YARN_CACHE_FOLDER: '/opt/render/.cache',
  RENDER_EXTERNAL_HOSTNAME: 'django-test-9g3f.onrender.com',
  RENDER_PRE_RUN_COMMAND: 'source /opt/render/project/src/.venv/bin/activate',
  RENDER_GIT_REPO_SLUG: 'sw-yx/django-quick-start',
  PWD: '/opt/render/project/src/mysite',
  RENDER_ROOT: '/opt/render',
  RENDER: 'true',
  DEFAULT_NODE_VERSION: '12.13.0',
  PORT: '10000',
  NODE_ENV: 'production',
  PIPENV_YES: 'true',
  YELLOW: '\x1b[33m',
  RESET: '\x1b(B\x1b[m',
  GUNICORN_CMD_ARGS: '--preload --access-logfile - --bind=0.0.0.0:10000',
  NPM_CONFIG_DEVDIR: '/opt/render/.cache',
  PIPENV_CACHE_DIR: '/opt/render/.cache',
  HOME: '/opt/render',
  RENDER_EXTERNAL_URL: 'https://django-test-9g3f.onrender.com',
  LANG: 'C.UTF-8',
  KUBERNETES_PORT_443_TCP: 'tcp://10.131.0.1:443',
  VIRTUAL_ENV: '/opt/render/project/src/.venv',
  RENDER_PM_DIR: '/opt/render/project/src',
  RENDER_SERVICE_TYPE: 'web',
  VIRTUAL_ENV_DISABLE_PROMPT: 'true',
  GPG_KEY: '0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D',
  TMPDIR: '/tmp',
  FORWARDED_ALLOW_IPS: '*',
  MAGENTA: '\x1b[35m',
  PIP_CACHE_DIR: '/opt/render/.cache',
  BOLD: '\x1b[1m',
  XDG_CACHE_HOME: '/opt/render/.cache',
  NODES_ROOT: '/opt/render/project/nodes',
  TERM: 'xterm-256color',
  RENDER_INTERNAL_IP: '10.104.65.37',
  NPM_CONFIG_LOGLEVEL: 'error',
  RENDER_GIT_BRANCH: 'master',
  RENDER_GIT_COMMIT: 'f9f9d37069642bfd32c762d00c6e4e74ce329a65',
  VENV_ROOT: '/opt/render/project/src/.venv',
  RENDER_SERVICE_NAME: 'django-test-9g3f',
  SHLVL: '0',
  KUBERNETES_PORT_443_TCP_PROTO: 'tcp',
  PYTHON_PIP_VERSION: '20.0.2',
  KUBERNETES_PORT_443_TCP_ADDR: '10.131.0.1',
  RENDER_ENV: 'python-3',
  RENDER_INTERNAL_HOSTNAME:
    'srv-bosg45n8jd5vhm4jst80.usr-bosfgpn8jd5vhm4jsorg.svc.cluster.local',
  RENDER_DISCOVERY_SERVICE: 'django-test-9g3f-discovery',
  RENDER_DIR: 'render',
  WEB_CONCURRENCY: '4',
  RED: '\x1b[31m',
  RENDER_PROJECT_DIR: 'project',
  PYTHON_GET_PIP_SHA256:
    'da288fc002d0bb2b90f6fbabc91048c1fa18d567ad067ee713c6e331d3a32b45',
  EXIT_STANDOUT: '\x1b[27m',
  KUBERNETES_SERVICE_HOST: '10.131.0.1',
  LC_ALL: 'C.UTF-8',
  KUBERNETES_PORT: 'tcp://10.131.0.1:443',
  KUBERNETES_PORT_443_TCP_PORT: '443',
  PYTHON_GET_PIP_URL:
    'https://github.com/pypa/get-pip/raw/42ad3426cb1ef05863521d7988d5f7fec0c99560/get-pip.py',
  PATH:
    '/opt/render/project/src/.venv/bin:/opt/render/project/src/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
  RENDER_PM_ROOT: '/home/render',
  GREEN: '\x1b[32m',
  RENDER_POD_NAME: 'srv-bosg45n8jd5vhm4jst80-77f6f7f849-v7gcc',
  NODE_VERSION: '',
  NODE_MODULES_CACHE: 'true',
  RENDER_PROJECT_ROOT: '/opt/render/project',
  DEBIAN_FRONTEND: 'noninteractive',
  OLDPWD: '/opt/render/project/src',
  RENDER_SRC_ROOT: '/opt/render/project/src',
  _: '/opt/render/project/src/.venv/bin/gunicorn',
  SERVER_SOFTWARE: 'gunicorn/20.0.4',
  DJANGO_SETTINGS_MODULE: 'mysite.settings'
}

Webmentions

Failed to load...