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.
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.
- CorsHeaders para obter acesso de outras origens:
pip install django-cors-headers
- Caso use PostgreSQL, instale um adaptador:
pip install psycopg2-binary
- Para especificar o banco de dados via URL, instale o dj-database-url:
pip install dj-database-url
- WhiteNoise para servir os arquivos estáticos do nosso serviço web:
pip install 'whitenoise[brotli]'
- Para carregar as variáveis de ambiente, instale o pacote:
pip install python-dotenv
- Instale Gunicorn e Uvicorn para rodar o projeto em um servidor web:
pip install gunicorn uvicorn
- Após toda essa parte de instalação, atualize a lista de dependências:
pip freeze > requirements.txt
Caso esteja utilizando pip. Se estiver usando PDM, instale o plugin autoexport para isso!
pdm plugin add pdm-autoexport
- PDM: Configure-o no pyproject.toml:
[[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:
-
Banco de dados (na versão gratuita, PostgreSQL somente)
-
Serviço (Django) usando Python como runtime
-
Comandos de Build e Start
-
Variáveis de ambiente, como a URL do banco de dados, já buscando a recém instanciada desse mesmo arquivo e a SECRET_KEY, com o valor que será gerado pelo Render se ainda não estiver definida
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.
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
-
Variáveis: Verifique se adaptou da maneira certa os nomes que estão da forma
<NOME-TAL>
, por exemplo. -
Banco de dados: Caso apague as migrações do projeto após migrá-las pro Postgres no Render, você irá enfrentar inconsistências nas migrações. Evite apagar as migrações. Se for realmente necessário, sugiro que apague o seu Postgres no Render e sincronize com seu repositório novamente para recriar a instância, assim recebendo as novas migrações. Para isso, acesse seu Blueprint e faça o Manual Sync. Isso considerando um projeto para estudos, claro.
-
Diretórios: Cheque os caminhos, podem mudar se estiver desenvolvendo um projeto monorepo. Nesse caso, é necessário mudar a propriedade
rootDir
no render.yaml. Adapte conforme sua situação. -
Free trial: Lembre-se, tudo que estamos utilizando pode ser usado na versão gratuita, porém o banco de dados tem seu lifetime. Seus serviços não estarão disponíveis 24/7, ou seja, a requisição pode demorar se a API já estiver um tempo não sendo utilizada.
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.