Royal Clean CRM · Documentación
Despliegue

Desplegar en Dokploy

De una VPS vacía al sistema funcionando en producción, paso a paso y sin saltarse nada.

Esta guía lleva de una VPS con Dokploy recién instalado a Royal Clean CRM sirviendo en su dominio, con HTTPS, respaldos y mantenimiento automático.

Se puede seguir de principio a fin sin conocer el proyecto. Cada paso dice qué hacer, qué esperar y cómo comprobar que salió bien antes de pasar al siguiente.

Lo que vas a montar

Cuatro piezas, y sólo una de ellas mira a Internet directamente:

PiezaQué esExpuesta
AplicaciónEl CRM. Imagen Docker construida del repositorioSí, por su dominio
DocumentaciónEste sitio. Segundo servicio, independienteSí, por su dominio
PostgreSQL 17La base de datosNo. Sólo red interna
Cloudflare R2Las fotografíasNo. Bucket privado

PostgreSQL no se publica nunca

Si Dokploy te ofrece exponer el puerto de la base, déjalo desactivado. La aplicación la alcanza por la red interna de Docker; nadie más tiene por qué poder hacerlo.

Antes de empezar

Ten esto a mano. Si falta algo, consíguelo primero: a mitad de la guía es peor.

  • Una VPS con Dokploy instalado y accesible.
  • Un dominio apuntando a la IP de la VPS. Por ejemplo crm.royalcleancr.com, y opcionalmente docs.royalcleancr.com.
  • Acceso al repositorio privado en GitHub.
  • Una cuenta de Cloudflare para el bucket de R2.
  • Un gestor de contraseñas donde guardar los secretos que vas a generar.

Comprueba el DNS antes de nada

dig +short crm.royalcleancr.com

Debe devolver la IP de tu VPS. Si todavía no propagó, el certificado HTTPS fallará más adelante y tendrás que rehacer ese paso.


1 · Crear el proyecto

Entra a Dokploy.

Projects → Create Project.

Nombre: royal-clean.

Todo lo demás vivirá dentro de este proyecto, que es lo que hace que los servicios se vean entre sí por su nombre.


2 · Crear la base de datos

Crea el servicio

Create Service → Database → PostgreSQL.

CampoValor
Nameroyalclean-db
Version17
Databaseroyalclean
Userroyalclean
PasswordGenera una larga y guárdala

Genera la contraseña

openssl rand -base64 32

Guárdala en tu gestor de contraseñas antes de continuar. La vas a necesitar en el paso 5 y no querrás regenerarla entonces.

Despliega y anota el host interno

Pulsa Deploy. Cuando termine, anota el host interno que muestra Dokploy —normalmente el propio nombre del servicio, royalclean-db—. Es lo que irá en DATABASE_URL.


3 · Crear el bucket de R2

En el panel de Cloudflare:

El bucket

R2 → Create bucket

CampoValor
Nameroyal-clean-evidence
LocationAutomatic

Deja el acceso público desactivado. El bucket tiene que ser privado: las fotografías se sirven a través de la aplicación, que comprueba permisos en cada petición.

El token de API

Manage R2 API Tokens → Create API Token

CampoValor
PermissionsObject Read & Write
BucketSólo royal-clean-evidence

Acotarlo a un solo bucket importa: si ese token se filtra, no da acceso al resto de tu cuenta de Cloudflare.

Guarda las tres cosas

  • Access Key ID
  • Secret Access Key — no se vuelve a mostrar
  • Account ID — aparece en la barra lateral de R2

4 · Crear la aplicación

Create Service → Application.

CampoValor
ProviderGitHub (autoriza Dokploy la primera vez)
RepositoryEl repositorio privado de Royal Clean
Branchmain
Build TypeDockerfile
Dockerfile PathDockerfile

No pulses Deploy todavía: sin variables de entorno, el contenedor arrancará y se detendrá.


5 · Variables de entorno

En Environment, pega esto y sustituye los valores en mayúsculas:

NODE_ENV=production
APP_URL=https://crm.royalcleancr.com

DATABASE_URL=postgresql://royalclean:LA_CONTRASENA@royalclean-db:5432/royalclean?schema=public

AUTH_SECRET=PEGA_AQUI_48_BYTES_ALEATORIOS

R2_ACCOUNT_ID=TU_ACCOUNT_ID
R2_ACCESS_KEY_ID=TU_ACCESS_KEY
R2_SECRET_ACCESS_KEY=TU_SECRET_KEY
R2_BUCKET_NAME=royal-clean-evidence

JOBS_SECRET=PEGA_AQUI_32_BYTES_ALEATORIOS

BUSINESS_TIMEZONE=America/Costa_Rica

Genera los dos secretos:

openssl rand -base64 48
openssl rand -hex 32

`AUTH_SECRET` tiene un mínimo

Al menos 32 caracteres. La aplicación se niega a arrancar si es más corto, y lo dice con claridad en los registros en lugar de arrancar con una seguridad menor de la esperada.

El detalle de cada variable, qué pasa si falta y cuáles son opcionales está en Variables de entorno.


6 · Dominio y HTTPS

Domains → Add Domain

CampoValor
Hostcrm.royalcleancr.com
Port3000
HTTPSActivado
CertificateLet's Encrypt

Dokploy emite y renueva el certificado a través de Traefik.

Si el DNS todavía no apunta a la VPS, la validación falla y Let's Encrypt limita los reintentos. Comprueba el dig de más arriba antes de pulsar.


7 · Healthcheck

Advanced → Health Check

CampoValor
Path/api/health
Interval30 s
Timeout5 s
Retries3
Start period40 s

El margen de arranque da tiempo a que se apliquen las migraciones antes de la primera comprobación. Sin él, el orquestador daría por muerto un contenedor que sólo estaba migrando.

La imagen ya trae su propio HEALTHCHECK; configurarlo también aquí es lo que evita que Traefik enrute tráfico a un contenedor que aún no está listo.


8 · Primer despliegue

Pulsa Deploy y sigue los registros. Deberías ver, en este orden:

→ Aplicando migraciones pendientes…
All migrations have been successfully applied.
→ Iniciando Royal Clean CRM…
▲ Next.js 16.3.3
✓ Ready

Comprueba desde tu equipo:

curl https://crm.royalcleancr.com/api/health
{"status":"ok","database":"ok","latencyMs":12}

Si responde `degraded`

La aplicación arrancó pero no alcanza la base. Casi siempre es DATABASE_URL: revisa que el host sea el nombre interno del servicio —no localhost— y que el puerto sea 5432, que es el interno del contenedor, no el 5433 que se usa en desarrollo.


9 · Crear el primer administrador

La semilla de desarrollo se niega a ejecutarse en producción, así que el primer administrador se crea a mano, una sola vez.

Abre una terminal en el contenedor de la aplicación desde Dokploy (Application → Terminal) y ejecuta:

node scripts/create-admin.mjs --username admin.principal --email admin@tu-dominio.com

Te pedirá la contraseña por entrada estándar, sin mostrarla, y la repetirá para confirmar. No se acepta por argumento a propósito: un argumento queda en el historial del intérprete de comandos y en la lista de procesos de la máquina.

La cuenta se crea obligada a cambiar la contraseña en el primer acceso, así que la que tecleas aquí no sobrevive al primer día.

La cuenta que administra la aplicación

Añade --super para dar además superadministración: gestionar las cuentas de todos —crearlas, restablecer contraseñas, desactivar accesos y cerrar sesiones—. Es una atribución para quien administra el sistema, no para quien administra el negocio.

node scripts/create-admin.mjs --username admin.principal --email admin@tu-dominio.com --super

A partir de aquí, desde la aplicación

El resto de las cuentas se crean desde el panel de administración. La cuenta con superadministración las gestiona todas: crearlas, restablecer contraseñas, desactivarlas y cerrar sesiones.


10 · Programar el mantenimiento

El trabajo de mantenimiento aplica la retención de las fotografías, limpia subidas abandonadas y marca los servicios que llevan demasiado tiempo abiertos.

Es idempotente: ejecutarlo dos veces seguidas no hace daño.

Es la opción recomendada: queda dentro del proyecto y se ve en el panel.

Create Service → Cron Job (o Schedules, según la versión).

Schedule: 0 3 * * * — todos los días a las 3:00.

Command:

curl -fsS -X POST -H "Authorization: Bearer EL_JOBS_SECRET" \
  https://crm.royalcleancr.com/api/jobs/run

Ejecútalo una vez a mano para comprobarlo. Debe devolver un JSON con los recuentos y "errors": [].

Sin el token responde 401, y sin JOBS_SECRET configurado responde 503 en lugar de quedar abierto.


11 · Respaldos

Una copia en la misma VPS no es un respaldo

Si se pierde el servidor, se pierde con él. El respaldo tiene que salir de ahí.

Configura una copia diaria de PostgreSQL hacia un destino externo —R2 sirve— y verifica una restauración cada cierto tiempo. Un respaldo que nunca se ha restaurado es una suposición, no un respaldo.

El procedimiento completo está en Operación diaria.


12 · La documentación

Este sitio se despliega como un segundo servicio del mismo proyecto, con su propio Dockerfile y su propio dominio. No comparte base de datos ni secretos con la aplicación.

Los pasos están en Desplegar la documentación.


Verificación final

No des el despliegue por terminado hasta que las diez líneas estén marcadas.

  • https://crm.royalcleancr.com/api/health responde "status":"ok"
  • El certificado HTTPS es válido y no da aviso en el navegador
  • Se puede iniciar sesión con el administrador inicial
  • Se fuerza el cambio de contraseña en ese primer ingreso
  • PostgreSQL no responde desde fuera de la VPS
  • Se puede crear un cliente, una propiedad y un servicio
  • Una fotografía sube y se ve después (confirma que R2 está bien)
  • POST /api/jobs/run sin token responde 401
  • El mantenimiento está programado y se ejecutó una vez a mano
  • El respaldo diario está configurado y se probó una restauración

Para comprobar que la base no está expuesta, desde tu equipo:

nc -vz LA_IP_DE_LA_VPS 5432

Debe fallar. Si conecta, la base está publicada y hay que corregirlo antes de seguir.

On this page