La Raya · backend (5): ponerlo en producción duele — Docker, Dokploy, Celery y el día que nada arrancaba
Publicado el 17/06/2026
Hay una mentira que nos contamos: "el código ya está, solo falta desplegarlo". Este post es el antídoto. Llevar La Raya a producción (Docker + Dokploy sobre un VPS, con Postgres y Redis) fue una sucesión de pequeños incendios, y casi ninguno tenía que ver con la lógica de la app.
El contenedor que nadie encontraba
Primer acto: el sitio no respondía. Causas encadenadas, cada una con su commit:
- gunicorn escuchaba en
127.0.0.1en vez de0.0.0.0, así que dentro del contenedor funcionaba y desde fuera no llegaba nadie. - Traefik (el proxy de Dokploy) no enrutaba al backend porque no estaba en la red correcta. Añadí el servicio a
dokploy-network. - Intenté poner labels de Traefik a mano… y resultó que chocaban con la config que genera Dokploy. Los acabé quitando. Lección: cuando la plataforma genera la configuración del proxy, no te pelees con ella metiendo labels manuales.
- El healthcheck fallaba, así que añadí un endpoint
/health/minúsculo que devuelve{"status":"ok"}. Ese endpoint de juguete acabó siendo clave (la app lo usa para mostrar el estado del servicio, pero esa es otra historia). - Y los estáticos no se servían en producción: WhiteNoise al rescate.
El bug de Celery que me costó tres commits
Mi favorito por lo absurdo. Los workers de Celery petaban al arrancar por un bug conocido de Celery 5.3.1 con worker_state_db. Fui probando, commit a commit:
CELERY_WORKER_STATE_DB… no.- parchear
worker_state_dbdirectamente sobreapp.conf… tampoco del todo. CELERY_STATEDBpara esquivarlo… por fin.
Tres intentos públicos en el historial. No lo escondo: así se ve realmente depurar un bug de una dependencia que no controlas. La moraleja no es "qué torpe", es fija las versiones y desconfía de los .1 de cualquier librería.
Las variables, la red y los permisos
Más fuego de fontanería, todo real:
- Había que pasar explícitamente todas las variables de entorno a cada contenedor (web, worker, beat). Lo que el
webtenía, elworkerno, y fallaba en silencio. celery-workerycelery-beattambién necesitaban estar endokploy-network.- LibreTranslate (el traductor autoalojado del capítulo anterior) no arrancaba por permisos de volumen → tuvo que correr como root; además, no debía bloquear el arranque del backend si tardaba, así que dejé de esperar a su healthcheck y su URL pasó a ser inyectable por entorno.
- Playwright (para los scrapers HTML) no traía los navegadores en la imagen Docker y necesitaba flags específicos para correr en contenedor.
Los ficheros que desaparecían
Las imágenes scrapeadas se subían… y se esfumaban al reiniciar. Dos cosas: en Django 6 static() es un no-op con DEBUG=False, así que registré el serve de media directamente; y, sobre todo, persistí los media en un volumen Docker y lo monté también en el worker (que es quien descarga las imágenes). Obvio una vez lo ves; invisible hasta que un usuario te dice "las fotos no salen".
Y de paso, rendimiento
Ya puestos en producción llegaron las optimizaciones que solo duelen con datos reales: cacheops (caché de querysets en Redis), índices de BD para los filtros y ordenaciones habituales, y un fix para colisiones de slug entre workers concurrentes (dos scrapers creando la misma página a la vez). También configuré el email por SMTP (DonDominio) vía variables de entorno, base de las alertas y, mucho después, del formulario de contacto.
Lo que me llevo (y repito como un mantra)
- "Funciona en local" no significa nada. Producción es otra red, otros permisos, otro
DEBUG, otros volúmenes. Despliega pronto y a menudo para que los incendios sean pequeños. - No pelees con tu plataforma. Si Dokploy/Traefik generan la config, deja que la generen.
- Fija versiones. Un bug de un
5.3.1te come una tarde; poder reproducir es lo que te salva. - Healthchecks y persistencia no son opcionales. Un
/health/tonto y un volumen bien montado evitan media app caída y fotos fantasma.
Con el backend por fin en pie y estable, pude volver a lo bonito: afinar la guía. Búsqueda, ordenar por cercanía de verdad… y un susto con Google que cuento en la próxima entrada.