Despliegue en servidor Debian con Docker
Guía para desplegar Xenpia Chatbot en un servidor Debian usando Docker y Docker Compose.
Requisitos
- Servidor Debian 11 o 12 (o derivados como Ubuntu 22.04+)
- Acceso SSH con usuario con permisos
- Dominio o IP pública (recomendado: dominio con HTTPS)
1. Instalar Docker y Docker Compose en Debian
# Actualizar e instalar dependencias
apt update && apt install -y ca-certificates curl gnupg
# Añadir clave y repo oficial de Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Instalar Docker Engine y Compose plugin
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Comprobar
docker --version
docker compose version
# (Opcional) Evitar usar para docker
usermod -aG docker $USER
# Cerrar sesión y volver a entrar para que aplique
2. Clonar el proyecto y configurar variables
# En el directorio donde quieras el proyecto (ej: /opt o ~)
git clone <url-del-repositorio> xenpia-chatbot
cd xenpia-chatbot
# Crear .env desde la plantilla
cp .env.example .env
# Editar .env con tus valores (URLs públicas, Supabase, Meta, OpenAI, etc.)
nano .env
Script de despliegue (opcional)
Puedes usar el script deploy.sh para arrancar o actualizar:
./deploy.sh # Levanta; construye solo si hace falta
./deploy.sh --build # Fuerza reconstrucción de imágenes (tras cambiar .env o código)
./deploy.sh --logs # Levanta y muestra logs en tiempo real
./deploy.sh --stop # Para los contenedores
La primera vez que no exista .env, el script lo creará desde .env.example y te pedirá editarlo antes de continuar.
Importante: En .env debes definir las URLs públicas que usará el navegador:
NEXT_PUBLIC_SERVER_URL: URL base de la app (ej.https://app.tudominio.com)NEXT_PUBLIC_BACKEND_URLyNEXT_PUBLIC_ORCHESTRATOR_URL: URL del API (ej.https://api.tudominio.comohttp://IP_DEL_SERVIDOR:3000si no usas proxy)
Si accedes por IP sin dominio, puedes usar por ejemplo:
NEXT_PUBLIC_SERVER_URL=http://TU_IP:3001NEXT_PUBLIC_BACKEND_URL=http://TU_IP:3000NEXT_PUBLIC_ORCHESTRATOR_URL=http://TU_IP:3000
3. Construir y levantar los contenedores
# Construir imágenes y arrancar en segundo plano
docker compose -f docker-compose.yml up -d --build
# Ver estado
docker compose -f docker-compose.yml ps
docker compose -f docker-compose.yml logs -f
Servicios y puertos:
| Servicio | Puerto | Descripción |
|---|---|---|
| backend | 3000 | API y orquestador |
| frontend | 3001 | Aplicación Next.js |
| redis | 6379 | Solo localhost (red interna) |
4. Probar el despliegue
- Frontend:
http://TU_IP:3001ohttps://app.tudominio.com - Backend (health):
http://TU_IP:3000(o la ruta que expongas)
4.1 WhatsApp Embedded Signup (Meta)
"Conectar WhatsApp" abre la ventana de Meta con el SDK de JavaScript
(FB.login), sin redirect URI: al terminar, Meta devuelve un code a la propia página y
el backend lo canjea en menos de 30 s. Por eso ya no existe la ruta /oauth/whatsapp-embedded
ni la variable NEXT_PUBLIC_META_OAUTH_REDIRECT_URI.
En Meta for Developers → la app de Xenpia:
- Settings → Basic → App Domains: el dominio del panel (ej.
app.tudominio.com). - Facebook Login for Business → Settings: activar Login with the JavaScript SDK y
añadir el dominio HTTPS del panel en Allowed Domains for the JavaScript SDK. El SDK no
funciona por
http://(tampoco en local: verdocs/interno/empezar/entorno-local.md). - Facebook Login for Business → Configurations: una configuración de tipo
WhatsApp Embedded Signup en la versión v4 (la v2 deja de funcionar el 15-oct-2026),
con los permisos
whatsapp_business_managementywhatsapp_business_messaging. Su ID va enNEXT_PUBLIC_META_EMBEDDED_SIGNUP_CONFIG_ID. Instagram y Messenger tienen las suyas (NEXT_PUBLIC_META_INSTAGRAM_SIGNUP_CONFIG_ID,NEXT_PUBLIC_META_MESSENGER_SIGNUP_CONFIG_ID). - WhatsApp → Configuration → Webhook: URL
https://api.tudominio.com/api/webhook/whatsappconMETA_VERIFY_TOKEN, suscrita a los camposmessages,history,smb_app_state_sync,smb_message_echoesyaccount_update. Los cuatro últimos son los de la coexistencia (el cliente sigue usando la app WhatsApp Business): sin ellos la conexión funciona, pero no se importa nada ni se ve lo que contestan desde el teléfono. - En la configuración v4, dejar activada la opción de onboarding de usuarios de la app WhatsApp Business. Se comprueba abriendo el asistente: la pantalla de elegir WABA debe ofrecer "conectar tu cuenta existente de WhatsApp Business".
Las variables NEXT_PUBLIC_* se incrustan al compilar: tras cambiarlas, reconstruye el
frontend con docker compose -f docker-compose.yml up -d --build.
El backend necesita META_APP_ID y META_APP_SECRET de esta misma app: con el secreto canjea
el code y verifica la firma de cada webhook (META_WEBHOOK_SIGNATURE, ver
docs/interno/canales/whatsapp.md §7).
5. (Recomendado) Nginx como proxy inverso con HTTPS
Para servir por dominio y HTTPS con Let's Encrypt:
apt install -y nginx certbot python3-certbot-nginx
certbot --nginx -d app.tudominio.com -d api.tudominio.com
Ejemplo de configuración Nginx (/etc/nginx/sites-available/xenpia):
# Frontend
server {
listen 80;
server_name app.tudominio.com;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Backend
server {
listen 80;
server_name api.tudominio.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Habilitar y recargar:
ln -s /etc/nginx/sites-available/xenpia /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Asegúrate de que en .env las URLs usen https://app.tudominio.com y https://api.tudominio.com.
6. Comandos útiles
# Ver logs
docker compose -f docker-compose.yml logs -f backend
docker compose -f docker-compose.yml logs -f frontend
# Reiniciar un servicio
docker compose -f docker-compose.yml restart backend
# Reconstruir tras cambios
docker compose -f docker-compose.yml up -d --build
# Parar todo
docker compose -f docker-compose.yml down
# Parar y eliminar volúmenes (¡borra datos de Redis!)
docker compose -f docker-compose.yml down -v
7. Actualizar la aplicación
cd xenpia-chatbot
git pull
docker compose -f docker-compose.yml up -d --build
8. Pipeline Bitbucket (despliegue automático en cada push a dev)
El proyecto incluye bitbucket-pipelines.yml en la raíz. En cada push a la rama dev, el pipeline se conecta por SSH al servidor y ejecuta git pull + docker compose up -d --build.
8.1 Variables del repositorio en Bitbucket
En Bitbucket → tu repositorio → Repository settings → Pipelines → Repository variables, crea:
| Variable | Valor | Secured |
|---|---|---|
SSH_PRIVATE_KEY | Contenido completo de la clave privada SSH | Sí |
SERVER_HOST | IP o dominio del servidor (ej. 192.168.1.10 o deploy.tudominio.com) | No |
SERVER_USER | Usuario SSH en el servidor (ej. deploy) | No |
DEPLOY_PATH | Ruta del proyecto en el servidor (ej. /opt/xenpia-chatbot) | No |
Para SSH_PRIVATE_KEY: pega el contenido del archivo de la clave privada (incluidas las líneas -----BEGIN ... KEY----- y -----END ... KEY-----). Marca la variable como Secured para que no aparezca en los logs.
8.2 Clave SSH para que el pipeline se conecte al servidor
Genera un par de claves solo para el pipeline (en tu máquina o en el servidor):
ssh-keygen -t ed25519 -C "bitbucket-pipeline" -f bitbucket_deploy -N ""
-
Clave pública (
bitbucket_deploy.pub): añádela en el servidor Debian en~/.ssh/authorized_keysdel usuario con el que te conectas (ej.deploy):# En el servidor, como usuario SERVER_USER (ej. deploy)mkdir -p ~/.sshecho "contenido de bitbucket_deploy.pub" >> ~/.ssh/authorized_keyschmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys -
Clave privada (
bitbucket_deploy): copia todo su contenido y pégalo en la variable SecuredSSH_PRIVATE_KEYen Bitbucket.
8.3 Deploy key en el servidor para git pull (repositorio privado)
En el servidor, git pull debe poder acceder al repositorio privado de Bitbucket. Dos opciones:
Opción A – Deploy key del repositorio (recomendada)
-
En el servidor, genera otra clave SSH solo para Git (si no la tienes):
ssh-keygen -t ed25519 -C "server-deploy" -f ~/.ssh/bitbucket_deploy_key -N ""cat ~/.ssh/bitbucket_deploy_key.pub -
En Bitbucket → tu repositorio → Repository settings → Access keys → Add key: pega la clave pública y dale Read (solo lectura).
-
En el servidor, configura Git para usar esa clave con Bitbucket. Crea o edita
~/.ssh/config(como el usuario que ejecuta el pipeline, ej.deploy):Host bitbucket.orgIdentityFile ~/.ssh/bitbucket_deploy_keyIdentitiesOnly yes -
Comprueba desde el servidor:
ssh -T [email protected](debe identificar el repo sin pedir contraseña).
Opción B – App password (HTTPS)
Si clonaste el repo por HTTPS, en el servidor puedes usar un App password de Bitbucket y configurar la URL con usuario y contraseña (o un credential helper). Es más frágil que la deploy key.
8.4 Activar Pipelines
En Bitbucket → Repository settings → Pipelines → Settings: activa Enable Pipelines. Asegúrate de que el archivo bitbucket-pipelines.yml está en la raíz del repositorio.
Tras un push a dev, el pipeline se ejecutará y desplegará en el servidor.
8.5 Usar runner auto-hospedado (opcional)
Si ya tienes un runner instalado en el propio servidor (como en frontend/bitbucket-pipelines.yml), puedes hacer que el pipeline se ejecute ahí y no usar SSH:
- En la raíz del repo usa solo el
bitbucket-pipelines.ymlde la raíz (Bitbucket ignora el defrontend/). - En ese archivo, comenta el step que hace SSH y descomenta el bloque que usa
runs-on: self.hosted. - En el runner, el código se hace checkout automáticamente; solo hace falta que exista un
.enven la raíz del workspace (puedes crearlo una vez a mano o con un script previo).
Así no necesitas variables SSH_PRIVATE_KEY, SERVER_HOST, etc., ni deploy key en el servidor para Git (el checkout lo hace Bitbucket en el runner).