Skip to content
Voltar

Django no Render: Descomplicando o Deploy com Blueprints

Publicado:

render.com

render.com

Table of contents

Open Table of contents

Render IaC

Nunca foi tão simples fazer deploy de uma aplicação com alguns cliques. O Render nos possibilita fazer isso com Blueprints. Podemos provisionar todos os serviços e banco de dados necessários para rodar nossa app.

Para isso, na raiz do projeto, crie um arquivo render.yaml. É nele que vamos definir os serviços, banco de dados e variáveis de ambiente. Agora toda parte de configuração inicial que teríamos que fazer pelo Dashboard, faremos nesse arquivo, especificando toda configuração via código.

Provisionamento de infraestrutura via código

Provisionamento de infraestrutura via código

A extensão do arquivo render.yaml deve ser .yaml e não .yml. Pois, mesmo ambos se referindo ao conteúdo YAML, o Render fará uma varedura no repositório procurando esse arquivo com essa extensão em específico!

Configurações Iniciais

Mesmo se já ou não tiver um projeto Django, os passos serão os mesmos, só adaptarei algumas configurações.

Caso sua aplicação ainda não esteja preparada para deploy, siga as instruções:

OBS: Se estiver utilizando outro gerenciador de dependências como PDM, troque o pip install por pdm add, por exemplo.

pip install django-cors-headers
pip install psycopg2-binary
pip install dj-database-url
pip install 'whitenoise[brotli]'
pip install python-dotenv
pip install gunicorn uvicorn
pip freeze > requirements.txt

Caso esteja utilizando pip. Se estiver usando PDM, instale o plugin autoexport para isso!

pdm plugin add pdm-autoexport
[[tool.pdm.autoexport]]
filename = "requirements.txt"
without-hashes = "true"

Adicione qualquer dependência, mesmo se já estiver, só pra gerar o requirements.txt.

Preparando o ambiente para o Render

O arquivo settings.py poderá ser configurado da seguinte forma:

import os
from pathlib import Path

from dotenv import load_dotenv

# Carrega as variáveis de ambiente do arquivo .env
load_dotenv()

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.getenv("SECRET_KEY", "django-insecure")

# "Render" é a variável de ambiente definida
# se caso estiver no ambiente do Render
DEBUG = "RENDER" not in os.environ

# Link do projeto para ser acessado externamente provido pelo Render
RENDER_EXTERNAL_HOSTNAME = os.getenv("RENDER_EXTERNAL_HOSTNAME")

ALLOWED_HOSTS = ["localhost"]

CSRF_TRUSTED_ORIGINS = [
	"http://localhost:3000",
	"http://localhost:8000",
	# Outros (vue, react etc) que irão consumir a API localmente...
]

# Caso já estiver no ambiente do Render
# redefina algumas configurações
# para se adequar a um ambiente de Produção
if RENDER_EXTERNAL_HOSTNAME:
	ALLOWED_HOSTS = [RENDER_EXTERNAL_HOSTNAME]
	CSRF_TRUSTED_ORIGINS = [
	  "https://<PROJETO-FRONTEND>.onrender.com", # Exemplo
	  # Domínios de clientes (vue, react etc) que
	  # poderão consumir a API externamente...
	]

Não esqueça de atualizar o “Middleware“ e o “Installed Apps” nessa ordem abaixo:

MIDDLEWARE = [
	"django.middleware.security.SecurityMiddleware",
	"whitenoise.middleware.WhiteNoiseMiddleware",
	...
	"corsheaders.middleware.CorsMiddleware",
	"django.middleware.common.CommonMiddleware",
	...
]

INSTALLED_APPS = [
	...
	"corsheaders",
	...
]

Configuração dos caminhos dos arquivos estáticos:

# Mostra onde serão servidos os arquivos estáticos na sua aplicação
# No caso, pode-se obter acesso em
# <SEU-DOMINIO>.onrender.com/static/... ou <OUTRO-DOMINIO>.com/static/...
STATIC_URL = "/static/"

# Diz para o Django para copiar os assets estáticos
# para um caminho chamado `staticfiles`,
# é específico para o Render
if not DEBUG:
	STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
	STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

Banco de dados, caso não definido, utilizará por padrão o SQLite:

DATABASES = {
	"default": dj_database_url.config(
		default=os.getenv("DATABASE_URL", "sqlite:///db.sqlite3"),
		conn_max_age=600,
		conn_health_checks=True,
	)
}

Vamos definir a variável DATABASE_URL agora.

Infrastructure as Code

Para provisionar o serviço e demais no Render, o render.yaml poderá ser configurado da seguinte forma:

databases:
  - name: <NOME-SERVIÇO-BD>
	plan: free
	databaseName: <NOME-BANCO-DE-DADOS>
	user: <USUÁRIO-BANCO-DE-DADOS>

services:
  - type: web
	plan: free
	name: <NOME-SERVIÇO-API>
	runtime: python
	buildCommand: "./build.sh"
	startCommand: "python -m gunicorn <NOME-PROJETO>.asgi:application -k uvicorn.workers.UvicornWorker"
	envVars:
	  - key: DATABASE_URL
		fromDatabase:
		  name: <NOME-SERVIÇO-BD>
		  property: connectionString
	  - key: SECRET_KEY
		generateValue: true
	  - key: WEB_CONCURRENCY
		value: 4

Aqui está sendo definido:

Arquivo build.sh

A fim de automatizar todos os comandos a serem dados para o build, adaptei o script base para esse mais completo. Crie-o na raiz do projeto:

#!/usr/bin/env bash
# Exit on error
set -o errexit

# Atualiza o pip
pip install --upgrade pip

# Instala todas as dependências do projeto
pip install -r requirements.txt

# Obtem os arquivos estáticos
python manage.py collectstatic --no-input

# Aplica as migrações do banco de dados
python manage.py migrate

# Compila as traduções do `django.po`
python manage.py compilemessages

# Carrega os dados de um arquivo JSON para o banco de dados
python manage.py loaddata <DATA>.json

# Verifica se existe o super usuário antes de criá-lo
if
  [ $(echo "from django.contrib.auth import get_user_model; User = get_user_model(); print(User.objects.filter(is_superuser=True).exists())" | python manage.py shell) == "False" ];
then
  # Cria seu super usuário (verifique suas variáveis de ambiente)
  python manage.py createsuperuser --no-input
fi

Edite conforme a sua necessidade. Exemplo, talvez o super usuário já esteja definido quando feito o dump dos dados. Sendo assim, não faz sentido verificar se não existe para criá-lo.

Talvez sua aplicação não esteja lidando com internacionalização, então nem adianta compilar as traduções. Portanto, fique a vontade, é só um template geral que deve ser adaptado para seu caso. Ou seja, remova os comandos que você não usará de fato.

Na parte do super usuário, os valores das variáveis de ambiente devem ser definidas no Dashboard. Por mais que seja possível defini-las no render.yaml, os valores não deveriam estar expostos publicamente em seu repositório. Caso precise criar seu super usuário, adicione esse código no seu serviço web (API) na seção envVars:

- key: DJANGO_SUPERUSER_USERNAME
  sync: false
- key: DJANGO_SUPERUSER_EMAIL
  sync: false
- key: DJANGO_SUPERUSER_PASSWORD
  sync: false

No momento da criação do serviço, o Render solicitará os valores pelo Dashboard, como na imagem abaixo.

Render Deploy Blueprint

Render Deploy Blueprint

No Ubuntu e derivados, pra não ter problema quando for digitar python (o padrão é python3), rode no terminal o comando abaixo para driblar isso:

sudo apt install python-is-python3

Antes de executar o build.sh, certifique-se que arquivo possui as permissões para execução:

chmod a+x build.sh

Antes da etapa de build, sugiro que crie um ambiente virtual para que as dependências não sejam instaladas globalmente, pode-se fazer desse modo:

python -m venv .venv

Digite esse comando para ativar o ambiente virtual:

# Linux (incluindo WSL 2) e MacOS
source .venv/bin/activate

Execute o build.sh no terminal na raiz do projeto:

./build.sh

Para ver se tudo roda como o esperado localmente, utilize o comando abaixo:

python -m gunicorn <NOME-PROJETO>.asgi:application -k uvicorn.workers.UvicornWorker

Use o comando abaixo para sair do ambiente virtual:

deactivate

Blueprints

É bem simples fazer o deploy agora. Após logar na plataforma do Render, acesse o parte de Blueprints. Crie uma nova Blueprint Instance, conecte sua conta do GitHub no Render, selecione o repositório com o projeto e conecte! Sim, só isso, depois só confirme os serviços a serem levantados.

No Dashboard, você encontrará os seus serviços recém instanciados desagrupados. O Render nos permite agrupá-los em Projetos. Na versão gratuita, você pode definir somente UM projeto. Facilita na organização, caso tenha outros serviços sendo hospedados no Render.

Subindo com front-end

Pode-se definir vários serviços no mesmo arquivo render.yaml. Separe cada serviço em seu respectivo diretório, exemplo backend e frontend.

Na mesma seção services do arquivo YAML, é possível adicionar sua interface no render.yaml:

  - type: web
	plan: free
	name: <NOME-SERVIÇO-CLIENTE>
	runtime: node
	rootDir: frontend
	buildCommand: pnpm install && pnpm run build
	startCommand: pnpm run preview --host 0.0.0.0
	envVars:
      - key: VITE_API_URL
        fromService:
          type: web
          name: <NOME-SERVIÇO-API>
          envVarKey: RENDER_EXTERNAL_URL

O exemplo é um projeto Vite que utiliza o gerenciador de pacotes PNPM. Pode usar o NPM, só substituir mesmo. Caso prefira o NPM, no startCommand, adicione -- antes do --host.

Questões frequentes

Conclusão

Contemplei nesse artigo as principais configurações para rodar um projeto Django no Render. Com essa configuração base, é possível provisionar o ambiente para hospedar uma app Django, até com front-end junto se a estratégia for desenvolver um monorepo. Qualquer dúvida, estou a disposição.



Post Anterior
Instalação e Configuração do Kind
Próximo Post
Introdução ao Clack CLI