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 |
|---|---|---|
|
|
Compila el frontend con Yarn y genera |
|
|
Instala dependencias de compilación y compila las dependencias Python (incluido |
|
|
Imagen final de ejecución, sin herramientas de compilación. Copia los paquetes ya compilados desde |
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 |
|---|---|---|
|
|
Fijar |
|
|
Fijar |
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: elchownse aplica después decollectstatic, para que los archivos recién creados queden propiedad deapp.En el host: como el servicio
workermonta/srv/deploys/eventoldata/staticmediante bind mount, se ajustaron los permisos del directorio del host al UID deapp(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.envesté endeploy/docker/y que las variables no sean sobrescritas por elMakefile.
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
nginxynginx-proxydel compose pueden eliminarse si ya hay un nginx en el host. En ese caso,daphnedebe publicar el puerto en loopback (127.0.0.1:8000:8000) y el nginx del host debe configurarlocation /static/y/media/apuntando a/srv/deploys/eventoldata/yproxy_passadaphnecon cabeceras de websocket.postgres:9.6está 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 scriptstart_eventol.shintenta cargar fixtures que no existen en el proyecto, pero el proceso continúa.