Cualquiera que haya trabajado con Django sabe que el framework es una maravilla de la productividad. Sin embargo, el verdadero reto empieza cuando pasamos del entorno local a un servidor de producción real. Configurar bases de datos, aislar las dependencias, gestionar las variables de entorno y escribir scripts repetitivos para levantar contenedores puede convertirse rápidamente en un verdadero dolor de cabeza.
Para solucionar esto de raíz, diseñé django-quickstart, un boilerplate/template estructurado para automatizar, aislar y estandarizar el despliegue de Django utilizando contenedores Docker tanto en desarrollo como en producción.
En este artículo desglosamos los fundamentos, objetivos y conceptos detrás de este enfoque de arquitectura orientada a contenedores — y, al cierre, hago una revisión técnica honesta de algunos puntos del template que conviene ajustar antes de llevarlo a un entorno productivo real.
Objetivos de este template
Cuando decidí armar esta estructura, mi meta principal no era solo “hacer andar Docker”. Quería resolver problemas reales de arquitectura y flujos de trabajo en equipos de desarrollo:
- Paridad de entornos (desarrollo == producción). Eliminar por completo el clásico *”pero en mi máquina funcionaba”*. Si corre en tu Docker local, corre en el servidor.
- Abstracción de la infraestructura. El programador no tiene que lidiar con la instalación local de PostgreSQL o Adminer; todo se orquesta automáticamente con comandos limpios.
- Automatización de tareas repetitivas. Scripts (
.sh) listos para usar que agilicen el ciclo de construcción, inicialización y apagado de los servicios. - Seguridad nativa para producción. Separar estrictamente las configuraciones de desarrollo y producción en imágenes optimizadas de múltiples etapas (multi-stage builds) y archivos Docker Compose dedicados.
Aclaración conceptual — “paridad de entornos” Esta idea no es original de Docker: es el punto X (Dev/prod parity) de la metodología The Twelve-Factor App, publicada originalmente por ingenieros de Heroku. Propone minimizar la brecha entre desarrollo y producción en tres dimensiones: tiempo (deploys frecuentes), personal (quien programa participa del deploy) y herramientas (usar el mismo motor de base de datos, cache, etc. en ambos entornos). Contenerizar ambos entornos con la misma imagen base es, en esencia, resolver la tercera dimensión.
Conceptos y arquitectura clave
El repositorio está estructurado bajo tres pilares fundamentales:
1. Desacoplamiento de entornos (dev vs prod)
Un error común es usar el mismo archivo de configuración o el mismo Dockerfile para todo. En django-quickstart, los entornos están estrictamente divididos mediante archivos gemelos especializados:
- Desarrollo:
Dockerfile,docker-compose.dev.yml, y scripts tiporun-devel.sh. Aquí se montan volúmenes en vivo para que los cambios en el código Python se reflejen al instante (hot reload). - Producción:
Dockerfile.prod,docker-compose.prod.yml, y scripts tiporun-prod.sh. Esta versión compila el código estático, remueve herramientas de depuración y optimiza el peso de la imagen para despliegues rápidos y seguros.
Aclaración conceptual — multi-stage builds Lo que en producción permite “remover herramientas de depuración” y compilar el estático sin arrastrar el peso del compilador es la técnica de build de múltiples etapas: el
Dockerfile.produsa varias instruccionesFROM, y solo la última pasa a formar parte de la imagen final. Según la documentación oficial de Docker, separar el entorno de build del entorno de runtime “reduce significativamente el tamaño de la imagen y aumenta la seguridad de las imágenes finales”, ya que las herramientas de compilación (compiladores, cabeceras de desarrollo, etc.) nunca llegan a la imagen que corre en producción. Vale aclarar: elDockerfilede desarrollo mostrado en este artículo no es multi-stage (no tendría sentido, porque en dev se necesita el toolchain completo para reconstruir dependencias); el multi-stage aplica específicamente alDockerfile.prod.
2. Abstracción por scripts (la interfaz CLI del desarrollador)
En lugar de forzar al equipo a recordar comandos masivos de Docker en la terminal, el proyecto introduce scripts de automatización limpios en la raíz:
init-devel.sh: descarga las imágenes, construye el entorno y corre las migraciones iniciales por primera vez.run-devel.shystop-devel.sh: levantan o apagan el entorno de desarrollo en un segundo.build-prod.sh: compila las imágenes optimizadas listas para subir a tu registro de contenedores (Docker Hub, AWS ECR, etc.).
El corazón del template: el código que une todo
Para entender la lógica detrás de la orquestación, veamos cómo interactúan el archivo de definición del contenedor y el orquestador de desarrollo.
El Dockerfile (entorno de desarrollo)
Este archivo define el plano de la aplicación. Partimos de una imagen oficial y liviana de Python, configuramos las variables de entorno para evitar que Python escriba archivos innecesarios de caché (.pyc), e instalamos las dependencias:
# Dockerfile (Desarrollo)
FROM python:3.11-slim
# Evita que Python escriba archivos .pyc en el disco
ENV PYTHONDONTWRITEBYTECODE=1
# Evita que Python bufferée la salida estándar (logs en tiempo real)
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Instalar dependencias del sistema si fueran necesarias
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Instalar requerimientos de Python
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
# Copiar el código fuente
COPY . /app/Code language: PHP (php)Aclaración conceptual — por qué
COPY requirements.txtva antes queCOPY . /app/No es casualidad ni prolijidad: Docker cachea cada instrucción (layer) por separado, y solo invalida una capa —y todas las siguientes— si su contenido cambió. Copiar primerorequirements.txte instalar dependencias, y recién después copiar el resto del código, hace que la instalación de paquetes (la parte lenta) se reutilice desde caché mientras solo cambie el código fuente y no las dependencias. Si se invirtiera el orden, cualquier cambio de una línea en el proyecto forzaría a reinstalar todos los paquetes de Python en cada build.
El docker-compose.dev.yml (orquestación local)
Aquí es donde ocurre la magia del ecosistema. Un solo comando levanta la aplicación Django, una base de datos PostgreSQL y un cliente gráfico como Adminer para auditar los datos sin instalar nada en el sistema operativo anfitrión:
services:
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=django_db
- POSTGRES_USER=django_user
- POSTGRES_PASSWORD=django_password
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- DEBUG=True
- DATABASE_URL=postgres://django_user:django_password@db:5432/django_db
depends_on:
- db
adminer:
image: adminer
ports:
- "8080:8080"
depends_on:
- db
volumes:
postgres_data:Code language: PHP (php)Nota técnica: quité la línea
version: '3.8'del original. Desde Docker Compose v2 (2022) esa clave de nivel superior está obsoleta: Compose ya usa siempre el schema más reciente disponible sin importar lo que declares ahí, y solo genera una advertencia. Es un detalle menor, pero vale la pena desterrarlo de cualquier plantilla nueva — la documentación oficial de Docker lo confirma explícitamente.
Conclusión: el verdadero valor del boilerplate
Utilizar un enfoque basado en plantillas como django-quickstart no se trata simplemente de conveniencia técnica. Se trata de establecer estándares de ingeniería. Al empaquetar la base de datos, el administrador, las herramientas de automatización y el código en compartimentos estancos, garantizás que la aplicación crezca de forma saludable.
Ya sea que decidas aplicar Clean Architecture, modularizar tus aplicaciones (separando usuarios, roles y permisos en módulos independientes) o exponer APIs complejas, esta base asegura que la infraestructura jamás sea un obstáculo.
Te invito a clonar el repositorio, probar los scripts de inicialización y llevar tu flujo de desarrollo con Django al siguiente nivel.
Código fuente completo: dagorret/django-quickstart
Revisión técnica: lo que ajustaría antes de ir a producción
Esta sección no estaba en el original — la agrego porque el artículo promete “seguridad nativa para producción” y hay puntos del docker-compose.dev.yml mostrado que, si se replican tal cual en el docker-compose.prod.yml, contradicen esa promesa. Ninguno invalida la arquitectura general, que es sólida; son ajustes puntuales.
DATABASE_URLno lo interpreta Django por sí solo. Django no tiene, de fábrica, ningún mecanismo para parsear una variableDATABASE_URLy volcarla en el diccionarioDATABASESdesettings.py. Hace falta una librería intermedia — típicamentedj-database-urlodjango-environ— que traduzca ese string al formato que Django espera. Vale la pena aclararlo en elREADMEdel template, porque es el primer punto donde alguien nuevo se va a trabar.- Credenciales en texto plano dentro del
docker-compose.yml.POSTGRES_PASSWORD=django_passwordhardcodeado es aceptable para desarrollo local descartable, pero si el mismo patrón se repite endocker-compose.prod.yml, cualquiera con acceso de lectura al repositorio tiene la contraseña de la base productiva. La alternativa estándar es usar un archivo.env(excluido de git vía.gitignore) referenciado conenv_file:, o directamente Docker secrets si se orquesta con Swarm/Compose en modo secretos. - Adminer no debería exponerse en producción. Es una herramienta de administración de base de datos sin capas de autenticación propias más allá del login a la DB — dejarla accesible en el puerto
8080de un servidor público es superficie de ataque innecesaria. Tiene sentido endocker-compose.dev.yml; en el archivo de producción debería eliminarse o, como mínimo, quedar detrás de un túnel/VPN. - Falta mencionar
.dockerignore. Sin él,COPY . /app/copia también.git/, entornos virtuales locales, archivos.envy demás basura de desarrollo dentro de la imagen — infla el build y puede filtrar secretos si alguien hacedocker historyo inspecciona las capas. Vale la pena que el template incluya uno por defecto. - Sin
healthchecknirestartpolicy. Ninguno de los tres servicios definehealthcheck:nirestart:. Para desarrollo no importa, pero endocker-compose.prod.ymles la diferencia entre que Docker reinicie automáticamente un contenedor caído y que el sitio quede abajo hasta que alguien lo note manualmente.
Ninguno de estos puntos es difícil de resolver — son, en todo caso, la lista de tareas naturales para pasar el template de “development-ready” a “production-hardened” en sentido estricto, más allá del multi-stage build que ya está bien encaminado.