Django y Tailwind CSS: Estrategia de construcción multietapa para imágenes Docker ligeras
Para los desarrolladores backend, el CSS suele ser una tarea interminable. Cuando descubrimos Bootstrap, celebramos su facilidad, pero a medida que el mantenimiento crecía, la personalización fuera de su marco se volvía cada vez más dolorosa.
La tendencia actual es, sin duda, Tailwind CSS. Su enfoque utility‑first, que permite terminar el estilo solo con combinaciones de clases, se acerca a la salvación para los ingenieros backend. He migrado proyectos de Bootstrap a Tailwind y, como resultado, esta herramienta se ha convertido en un elemento imprescindible en el desarrollo web moderno, especialmente en proyectos Django.
En este artículo comparto la estrategia de optimización de imágenes Docker para producción cuando se usa Tailwind CSS en un proyecto Django, con énfasis en mantener la imagen ligera mediante construcción multietapa.
Problema: Imágenes Docker que crecen de tamaño
Para integrar Tailwind en un entorno Django, la opción más popular es el paquete django‑tailwind. Su instalación y documentación son sólidas, por lo que en desarrollo local no suele haber problemas.
El problema surge en la fase de despliegue, especialmente cuando se trabaja con contenedores Docker.
En un entorno local o en un servidor bare‑metal, se puede compilar el CSS de forma sencilla:
python manage.py tailwind build
Sin embargo, este proceso requiere internamente Node.js y npm. Como resultado, el contenedor de producción de Django termina con Node.js instalado, lo cual es ineficiente en varios aspectos.
- Punto 1: Para una tarea única de compilación de CSS, se incluye un runtime de Node.js en la imagen.
- Punto 2: El tamaño de la imagen aumenta innecesariamente.
- Punto 3: Desde la perspectiva de la seguridad, incluir binarios que no se usan en tiempo de ejecución no es ideal.
Nuestro objetivo es claro:
"Incluir solo los archivos CSS compilados en una imagen de runtime puro de Python, sin Node.js"
Para lograrlo, utilizaremos la construcción multietapa de Docker.
Solución: Construcción multietapa en 3 etapas
La construcción multietapa de Docker permite separar el proceso de construcción en etapas distintas. Aquí separamos la imagen en tres fases:
- Python Builder: Construir paquetes Python en formato Wheel.
- Node Builder: Compilar Tailwind CSS con npm.
- Final Runtime: Copiar únicamente los resultados de las dos etapas anteriores y crear la imagen final.
Con este enfoque, la imagen final contiene solo los archivos necesarios para la ejecución, sin Node.js ni herramientas de construcción.
Ejemplo de Dockerfile
A continuación se muestra un Dockerfile basado en un proyecto Django + Tailwind. Ajusta las rutas según la estructura de tu proyecto (por ejemplo, el nombre de la app theme, la ubicación de archivos estáticos, etc.).
# -------------------------------------------------------------------
# Stage 1: Python Builder
# Instala dependencias de Linux y construye paquetes Python en Wheel.
# -------------------------------------------------------------------
FROM python:3.11-slim as python-builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt
# -------------------------------------------------------------------
# Stage 2: Node Builder
# Usa una imagen de Node para compilar Tailwind CSS.
# -------------------------------------------------------------------
FROM node:20-alpine as node-builder
WORKDIR /app
# Copia todo el proyecto para que Tailwind JIT pueda escanear plantillas y código Python.
COPY . .
WORKDIR /app/theme/static_src
# Instala dependencias y ejecuta la compilación.
RUN npm ci
RUN npm run build
# Se asume que el resultado se genera en /app/theme/static/css/dist.css.
# -------------------------------------------------------------------
# Stage 3: Final Runtime
# Imagen final sin Node.js, solo Python y archivos estáticos.
# -------------------------------------------------------------------
FROM python:3.11-slim
WORKDIR /app
# 1. Instala paquetes Python construidos en la etapa 1.
COPY --from=python-builder /app/wheels /wheels
COPY --from=python-builder /app/requirements.txt .
RUN pip install --no-cache-dir /wheels/*
# 2. Copia el código fuente de la aplicación.
COPY . .
# 3. Copia solo el CSS compilado de la etapa 2.
COPY --from=node-builder /app/theme/static/css/dist.css \
/app/theme/static/css/dist.css
# 4. Ejecuta collectstatic.
RUN python manage.py collectstatic --noinput
# Comando de inicio
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "config.wsgi:application"]
Optimizaciones adicionales que puedes considerar:
- Utiliza
.dockerignorepara excluir archivos innecesarios (por ejemplo,.git, archivos multimedia locales). - Añade variables de entorno como
PYTHONDONTWRITEBYTECODE=1yPYTHONUNBUFFERED=1. - Si usas una imagen basada en Alpine, instala solo las bibliotecas del sistema que realmente necesitas.
Puntos clave resumidos
1. Separación de responsabilidades en la etapa Node
En la etapa 2 nos enfocamos únicamente en la compilación de CSS. Es importante que Tailwind pueda escanear todas las plantillas y archivos Python, por lo que en el ejemplo copiamos todo el proyecto. Si tu proyecto es muy grande y content de Tailwind está bien delimitado, puedes copiar solo los directorios esenciales (static_src, plantillas, etc.).
2. Uso del script npm run build
El package.json generado por django‑tailwind suele incluir un script como:
{
"scripts": {
"build": "tailwindcss -c tailwind.config.js -o ../static/css/dist.css --minify"
}
}
En la construcción multietapa simplemente ejecutas npm run build o el script que hayas definido. Solo necesitas conocer la ruta del resultado para copiarlo en la etapa final.
3. Ligereza y seguridad de la imagen final
En la etapa 3 no aparecen Node, npm ni Tailwind CLI. Solo quedan los paquetes Python instalados vía Wheel y los archivos estáticos compilados. Esto reduce el tamaño de la imagen, disminuye la superficie de ataque y garantiza que el contenedor contenga únicamente lo que realmente necesita para ejecutarse.

Conclusión
Al construir imágenes Docker, es tentador incluir todo lo necesario para que la aplicación funcione. Sin embargo, en producción, el tamaño de la imagen, el tiempo de construcción y la seguridad son factores críticos.
Aplica la estrategia de construcción multietapa en 3 etapas cuando combines Django y Tailwind:
- Limita las dependencias de Node.js solo a la fase de compilación.
- Mantén la imagen final como un entorno puro de Python con los archivos estáticos ya compilados.
- Obtén una imagen más ligera, más rápida de desplegar y más segura.
Si ya tienes un proyecto en producción, prueba esta configuración en tu próximo pipeline de despliegue. Una vez que la estructura esté establecida, podrás reutilizarla prácticamente sin cambios en nuevos proyectos.
No hay comentarios.