Uma das motivações para escrever este artigo foi a dificuldade que encontrei ao buscar materiais em português para implantar um aplicativo Django utilizando Docker com certificação HTTPS em um servidor na nuvem. Implantar essa estrutura de modo “tradicional” é relativamente simples, mas realizar o mesmo processo usando Docker não é . Após pesquisar e testar várias abordagens, encontrei estes dois artigos artigo 1, artigo 2, que serviram como referência para o template que criei.
Antes de prosseguir, vou supor que o leitor já tenha familiaridade com Docker, Nginx e Certbot, pois o foco será em como combinar esses serviços, e não em explicar seu funcionamento.
A estratégia é dividir o processo em três etapas. A primeira é o desenvolvimento do aplicativo dentro de um container Docker, juntamente com um container Postgres, que foi o SGBD escolhido. A segunda etapa é instalar o certificado Let’s Encrypt nos volumes correspondentes a cada serviço, Nginx e Certbot. A terceira e última etapa é rodar o serviço completo, com certificação HTTPS, e configurar um agendador de tarefas para manter o certificado válido automaticamente. Sem mais delongas, vamos lá.
Rodar um aplicativo Django com Docker é relativamente simples, e essa primeira parte pode ser implementada de várias maneiras diferentes. Vou apresentar uma forma, mas sinta-se livre para adaptar conforme necessário.
Crie um diretório de trabalho:
mkdir work_dir
cd work_dir
Crie um ambiente virtual Python e ative-o na pasta de trabalho:
python3 -m venv venv
source venv/bin/activate
Instale o Django e as dependências do projeto:
pip install --upgrade pip
pip install django gunicorn "psycopg[binary]"
Dentro do diretório de trabalho, crie uma pasta chamada app:
mkdir app
Ainda no diretório de trabalho, inicie um aplicativo Django e direcione a instalação para dentro da pasta app:
django-admin startproject appDjango app/
Crie um arquivo requirements.txt dentro da pasta app:
pip freeze > app/requirements.txt
Neste ponto, a estrutura do projeto deve estar assim:
work_dir
├── venv/
└── app/
├── manage.py
├── requirements.txt
└── appDjango/
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
A partir de agora, implementaremos os containers do nosso app Django e Postgres, para que possamos iniciar o desenvolvimento do aplicativo. Para isso, é necessário ter o Docker e o Compose instalados em sua máquina. Há diversos tutoriais de instalação, além do próprio site do Docker. Se não tiver instalado, pare aqui, instale, e então continue com o processo.
O app.dockerfile ficará da seguinte forma:
FROM python:3.11.12-bullseye
# Evita criação de .pyc e força output direto no terminalENV PYTHONDONTWRITEBYTECODE=1ENV PYTHONUNBUFFERED=1
# Cria e define diretório de trabalhoWORKDIR /app
COPY app/requirements.txt /app/requirements.txtCOPY app /appCOPY scripts /scripts# Instala libs do sistemaRUN apt-get update && apt-get -y dist-upgrade && \ apt-get install -y libpq5 && \ apt-get install -y netcatRUN python -m venv /venv && \ /venv/bin/pip install --upgrade pip && \ /venv/bin/pip install -r /app/requirements.txtRUN mkdir -p /web/static /web/media && \ chmod -R 755 /venv /web/static /web/media && \ chmod -R +x /scripts
# Define PATH para incluir o virtualenv e os scriptsENV PATH="/app:/scripts:/venv/bin:$PATH"
# Comando de entradaCMD ["/scripts/commands.sh"]
Você deve estar se perguntando: por que atualizar e instalar pacotes em um container Python? Quando estava desenvolvendo este template, me deparei com um erro no plugin do ORM do Django, relacionado à biblioteca psycopg. A psycopg3, que é utilizada diretamente pelo ORM do Django para conectar ao Postgres, usa uma biblioteca C chamada libpq5 por baixo dos panos, que costuma estar presente nos pacotes básicos do Ubuntu e Debian, mas não vem nos containers Python, tanto no Alpine quanto no Bullseye. Como já havia enfrentado esse erro anteriormente, a solução foi instalar o pacote libpq5, o que resolveu o problema. Além disso, a instalação de netcat é necessária para rodar o utilitário nc, que verifica se o host Postgres já está respondendo a solicitações, evitando que o app Django inicie sem o banco de dados, o que causaria um crash total do aplicativo. No entanto, já vi outros desenvolvedores usarem o Postgres em uma configuração semelhante e não enfrentarem esse problema.
Criando scripts para gerenciar o início do container:
O próximo passo é criar os scripts que irão gerenciar o início do nosso container. Optei por subdividi-los em um arquivo por comando, que podem ser estendidos conforme a necessidade. Durante o desenvolvimento, usaremos vários comandos dentro dos containers, então esses scripts serão muito úteis.
Crie um diretório scripts no diretório de trabalho:
mkdir scripts
cd scripts
Dentro do diretório scripts, crie os seguintes arquivos:
waitPSQL.sh
#!/bin/sh
while ! nc -z $POSTGRES_HOST $POSTGRES_PORT; do
echo "🟡 Waiting for Postgres Database Startup ($POSTGRES_HOST $POSTGRES_PORT) ..."
sleep 2
done
echo "✅ Postgres Database Started Successfully ($POSTGRES_HOST:$POSTGRES_PORT)"
collectstatic.sh
#!/bin/sh
python manage.py collectstatic --noinput
makemigrations.sh
#!/bin/sh
python manage.py makemigrations --noinput
migrate.sh
#!/bin/sh
makemigrations.sh
python manage.py migrate --noinput
runserver.sh
#!/bin/sh
# Este comando deve ser usado no desenvolvimento
python manage.py runserver 0.0.0.0:8000
# Este comando deve ser usado em produção
# gunicorn --config gunicorn_config.py Nome_Aplicacao.wsgi:application
commands.sh
#!/bin/sh
# O shell irá encerrar a execução do script quando um comando falhar
set -e
waitPSQL.sh
collectstatic.sh
migrate.sh
runserver.sh
Preparando o arquivo .env:
Para iniciar o nosso aplicativo em desenvolvimento, precisamos passar algumas informações para que os containers iniciem nosso app corretamente. Para isso, vamos criar o arquivo .env.
No diretório de trabalho, crie uma pasta chamada env_files:
mkdir env_files
cd env_files
No diretório env_files, crie um arquivo .env e cole o código abaixo:
SECRET_KEY="Chave_Secreta_Django" # (evite o caractere $)
DEBUG="1" # 1 para desenvolvimento, 0 para produção.
CSRF_TRUSTED_ORIGINS="https://DOMAIN"
ALLOWED_HOSTS="DOMAIN, 0.0.0.0, 127.0.0.1, [::1], localhost"
DB_ENGINE="django.db.backends.postgresql" # Para banco de dados PostgreSQL
POSTGRES_DB="Nome_do_Banco"
POSTGRES_USER="Usuario"
POSTGRES_PASSWORD="Senha_Forte"
POSTGRES_HOST="psql"
POSTGRES_PORT="5432"
POSTGRES_HOST_AUTH_METHOD="trust"
Este arquivo substitui alguns parâmetros no arquivo settings.py. Ele deve ser mantido fora dos commits, pois contém informações sensíveis de segurança, portanto, adicione-o ao arquivo .gitignore. Para manter este artigo objetivo, vou abordar somente o necessário.
Nosso settings.py deve estar parecido com esse:
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Local dos arquivos estáticos -> /data/web/static
# Local dos arquivos de mídia -> /data/web/media
DATA_DIR = BASE_DIR.parent / 'web'
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY', 'OK')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(int(os.getenv('DEBUG', 0)))
ALLOWED_HOSTS = [
h.strip() for h in os.getenv('ALLOWED_HOSTS', '').split(',') if h.strip()
]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
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 = 'hello_django.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'hello_django.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': os.getenv('DB_ENGINE', 'django.db.backends.postgresql'),
'NAME': os.getenv('POSTGRES_DB', 'CHANGE'),
'USER': os.getenv('POSTGRES_USER', 'CHANGE'),
'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'CHANGE'),
'HOST': os.getenv('POSTGRES_HOST', 'psql'),
'PORT': os.getenv('POSTGRES_PORT', '5432'),
}
}
# Password validation
# https://docs.djangoproject.com/en/5.1/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/5.1/topics/i18n/
LANGUAGE_CODE = 'pt-br'
TIME_ZONE = 'America/Sao_Paulo'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATICFILES_DIRS = [
BASE_DIR / 'templates' / 'static',
]
STATIC_URL = '/static/'
STATIC_ROOT = DATA_DIR / 'static'
MEDIA_URL = '/media/'
MEDIA_ROOT = DATA_DIR / 'media'
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Criando o arquivo docker-compose.yml:
Agora vamos criar um arquivo Compose para rodar nossos containers do app Django e Postgres. A estrutura básica está assim:
No diretório de trabalho, crie um arquivo docker-compose.yml:
version: "3.9"
services:
web:
build:
context: .
dockerfile: app.dockerfile
command: commands.sh
env_file:
- env_files/.env
volumes:
- web_static:/web/static
- web_media:/web/media
depends_on:
- psql
ports:
- 8000:8000
networks:
- internal-network
psql:
image: postgres:15.2-alpine
env_file:
- env_files/.env
volumes:
- postgres_data:/var/lib/postgresql/data/
ports:
- 5432:5432
networks:
- internal-network
networks:
internal-network:
driver: bridge
volumes:
postgres_data:
web_static:
web_media:
Neste ponto, nosso diretório de trabalho deve estar assim:
work_dir
├── venv/
├── app/
│ ├── app_django/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── manage.py
│ └── requirements.txt
├── data/
│ └── postgres/
│ └── data/
├── env_files/
│ └── .env
├── scripts/
│ ├── commands.sh
│ ├── collectstatic.sh
│ ├── makemigrations.sh
│ ├── migrate.sh
│ ├── runserver.sh
│ └── waitPSQL.sh
├── web/
│ ├── media/
│ └── static/
├── app.dockerfile
└── docker-compose-dev.yml
Agora vamos construir o nosso container, levantar os serviços e acessar a aplicação no navegador.
No diretório de trabalho, execute os seguintes comandos:
docker compose build
docker compose up
Após a criação dos containers, será possível acessar o navegador utilizando:
localhost:8000
127.0.0.1:8000
Se tudo foi feito corretamente, você verá a página inicial do Django e já poderá começar a desenvolver seu aplicativo. Qualquer problema ou dúvida, sugiro revisar os passos. Se encontrar algo errado, faça uma pesquisa para verificar se houve alguma mudança no Docker, Postgres, Django ou Python, pois estes estão em constante evolução.
Na segunda parte deste artigo, abordaremos a criação do arquivo Nginx para o Compose, a criação dos volumes correspondentes, a instalação do Certbot e a finalização da configuração para permitir acesso ao aplicativo de forma segura, com certificação HTTPS.
Para acessar o repositório no GitHub é só clicar Aqui.
Até a próxima!