Proceso de despliegue de EventoL en producción

Punto de partida

El proyecto desplegado es EventoL, una aplicación de gestión de eventos de código abierto. El repositorio oficial es https://github.com/EventoL/eventoL y la documentación oficial del proyecto está en https://eventol.github.io/eventoL/

El despliegue se realizó sobre una copia del repositorio clonada en el servidor, en /opt/eventol/eventoL, usando Docker Compose con el archivo deploy/docker/docker-compose.prod.yml y el comando make deploy definido en el Makefile del proyecto.

1. Corrección del Dockerfile (compilación multi-etapa)

El Dockerfile original era de una sola etapa, lo que impedía compilar correctamente el frontend y generaba imágenes innecesariamente grandes. Se reemplazó por una estructura de tres etapas:

Etapa

Base

Función

frontend

node:16-alpine3.13

Compila el frontend con Yarn y genera webpack-stats-prod.json y estáticos.

builder

python:3.9-slim

Instala dependencias de compilación y compila las dependencias Python (incluido psycopg2 desde código fuente).

development

python:3.9-slim

Imagen final de ejecución, sin herramientas de compilación. Copia los paquetes ya compilados desde builder y los estáticos desde frontend.

Por qué se cambió de Alpine a Debian slim: Alpine usa musl libc con un stack predeterminado de 128 KiB, insuficiente para Python, lo que provocaba un Segmentation Fault (exit code 139) durante collectstatic. Debian usa glibc con 8 MiB de stack y elimina las incompatibilidades binarias de las extensiones C.

2. Resolución de incompatibilidades de setuptools

Durante la instalación de dependencias aparecieron dos errores sucesivos:

Error

Causa

Solución

ImportError: cannot import name 'convert_path'

django-allauth==0.34.0 usa convert_path, eliminada de setuptools >= 61.

Fijar setuptools==60.10.0 inicialmente.

error in pypng setup command: use_2to3 is invalid

pypng==0.0.18 usa use_2to3, eliminado de setuptools >= 58.

Fijar setuptools==57.5.0, que soporta ambos casos.

En la etapa builder se añadió, antes del pip install de los requerimientos:

RUN pip3 install --no-cache-dir "setuptools==57.5.0" wheel

3. Corrección de permisos

3.1 Archivo de log

El error PermissionError: [Errno 13] Permission denied: '/var/log/eventol/eventol.log' se debía a que el archivo era propiedad de root y el proceso corre como app. Se corrigió con:

RUN adduser --disabled-password --home ${APP_ROOT} --shell /bin/bash app \
    && touch /var/log/eventol/eventol.log \
    && chown -R app:app /var/log/eventol

3.2 Estáticos (static/bundles)

El error PermissionError: [Errno 13] Permission denied: '/usr/src/app/eventol/static/bundles' se resolvió en dos frentes:

  • En el Dockerfile: el chown se aplica después de collectstatic, para que los archivos recién creados queden propiedad de app.

  • En el host: como el servicio worker monta /srv/deploys/eventoldata/static mediante bind mount, se ajustaron los permisos del directorio del host al UID de app (1000):

sudo chown -R 1000:1000 /srv/deploys/eventoldata/static
sudo chown -R 1000:1000 /srv/deploys/eventoldata/media

4. Problemas de infraestructura del host

El error open /proc/stat: transport endpoint is not connected: se debía a un fallo de lxcfs en el host. Se solucionó cambiando el contenedor LXC a modo privilegiado.

5. Configuración de PostgreSQL

5.1 Contraseña y usuario

El error FATAL: password authentication failed for user "USUARIOBD" se debía a que la imagen de PostgreSQL solo lee POSTGRES_USER, POSTGRES_PASSWORD y POSTGRES_DB en la primera inicialización (directorio de datos vacío). Como el bind mount /srv/deploys/eventoldata/postgres conservaba datos antiguos, se omitía la inicialización.

5.2 Conflicto entre Makefile y .env

Se detectó que el Makefile definía:

.EXPORT_ALL_VARIABLES:
PSQL_DBNAME = eventol
PSQL_PASSWORD = secret
PSQL_USER = eventol

Estas variables sobrescribían las del .env al ejecutar make deploy, forzando el usuario eventol en lugar de USUARIOBD. Se eliminaron del Makefile (o se dejó de exportarlas) para que Compose resolviera los valores desde deploy/docker/.env.

5.3 Diferencia entre compose de dev y prod

  • docker-compose.yml (dev): valores hardcodeados (secret, eventol, eventol).

  • docker-compose.prod.yml (prod): interpolación ${PSQL_PASSWORD}, ${PSQL_USER}, ${PSQL_DBNAME}, que requiere que el .env esté en deploy/docker/ y que las variables no sean sobrescritas por el Makefile.

Solución aplicada:

docker compose -f deploy/docker/docker-compose.prod.yml down
sudo rm -rf /srv/deploys/eventoldata/postgres/*
make deploy

6. Configuración de Django en producción

6.1 ALLOWED_HOSTS

El error Invalid HTTP_HOST header: 'eventol.dominio.edu.ar' se debía a que en la configuración Prod (línea 307 de settings.py) se define:

ALLOWED_HOSTS = [os.getenv('APP_DNS'), socket.gethostname()]

Sobrescribiendo el ALLOWED_HOSTS = ['*'] de la línea 33. Como APP_DNS no estaba definida, el valor era None.

Solución: añadir al .env:

APP_DNS=eventol.dominio.edu.ar

6.2 Zona horaria

Como Django no admite desfases numéricos, se debe utilizar un nombre de zona horaria de la base de datos IANA.

TIME_ZONE = 'America/Argentina/Buenos_Aires'

o, si se prefiere UTC-3 fijo:

TIME_ZONE = 'Etc/GMT+3'

6.3 Sentry / Raven

El error InvalidDsn: Unsupported Sentry DSN scheme: (NOT_CONFIGURED) se resolvió dejando RAVEN_CONFIG = {} (o eliminando la clave) cuando no se usa Sentry, en lugar de definir un DSN vacío.

7. Configuración de SMTP (relay de Gmail por IP)

La configuración SMTP se lee de variables de entorno en settings.py (líneas 285–293).

En deploy/docker/.env:

EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.dominio.edu.ar
EMAIL_PORT=587
EMAIL_HOST_USER=usuario@dominio.edu.ar
EMAIL_HOST_PASSWORD=ALGUNACLAVESEGURA
EMAIL_USE_TLS=True
EMAIL_FROM=eventol@dominio.edu.ar
DEFAULT_FROM_EMAIL=eventol@dominio.edu.ar

Clave: sin EMAIL_BACKEND definido, Django usa el backend de consola de manera predeterminada y los correos solo se imprimen en los logs. Al definirlo como smtp.EmailBackend, se envían realmente.

8. Corrección del nombre de dominio en los correos

Los correos de confirmación salían con example.com porque django-allauth utiliza el registro Sites de Django, que viene configurado inicialmente con ese dominio y nombre.

Solución: editar el Site con ID 1 en el admin (/admin/) o desde la shell:

docker compose -f deploy/docker/docker-compose.prod.yml exec worker bash -c "cd eventol && python manage.py shell -c \"from django.contrib.sites.models import Site; site = Site.objects.get(pk=1); site.domain = 'eventol.unau.edu.ar'; site.name = 'EventoL UNAU'; site.save()\""

Después, reiniciar daphne y worker.

9. Creación del superusuario

El target make docker-backend-createsuperuser fallaba porque la imagen Debian slim no incluye make. El comando directo también fallaba porque el WORKDIR es /usr/src/app/ y manage.py está en /usr/src/app/eventol/.

Comando correcto:

docker compose -f deploy/docker/docker-compose.prod.yml exec worker bash -c "cd eventol && python manage.py createsuperuser"

Mejora permanente: añadir make a la lista de paquetes de la etapa development en el Dockerfile.

10. Resumen del flujo de despliegue final

# 1. Construir la imagen (tres etapas)
make build

# 2. Desplegar
make deploy

# 3. Verificar estado
docker compose -f deploy/docker/docker-compose.prod.yml ps

# 4. Recrear contenedores tras cambios en .env
docker compose -f deploy/docker/docker-compose.prod.yml up -d --force-recreate daphne worker

# 5. Ver logs
docker compose -f deploy/docker/docker-compose.prod.yml logs -f worker daphne


## 11. Verificaciones finales

- **Base de datos:** `docker exec -it eventol-postgres psql -U anfitrion -d eventol -c "\du"` muestra el usuario creado.
- **Variables de entorno:** `docker exec -it eventol-worker env | grep -E "EMAIL|APP_DNS|PSQL"` confirma que llegan al contenedor.
- **Django efectivo:** `python manage.py shell -c "from django.conf import settings; print(settings.ALLOWED_HOSTS, settings.EMAIL_BACKEND)"` muestra los valores reales en ejecución.
- **Acceso web:** `curl -I http://eventol.unau.edu.ar` devuelve 200 (o redirección a login), sin errores 400/500.
- **Correo:** el registro de un usuario envía un correo con el dominio y nombre correctos de `eventol.unau.edu.ar`.

Notas finales

  • El servicio nginx y nginx-proxy del compose pueden eliminarse si ya hay un nginx en el host. En ese caso, daphne debe publicar el puerto en loopback (127.0.0.1:8000:8000) y el nginx del host debe configurar location /static/ y /media/ apuntando a /srv/deploys/eventoldata/ y proxy_pass a daphne con cabeceras de websocket.

  • postgres:9.6 está fuera de soporte (EOL desde 2021). Se recomienda planificar la migración a una versión soportada.

  • Los errores No fixture named 'admin' / 'sites' / 'social' en el arranque son inocuos: el script start_eventol.sh intenta cargar fixtures que no existen en el proyecto, pero el proceso continúa.