Funciona en local pero no después del deploy: causas comunes

Tu servidor es una máquina distinta de tu computadora, y la aplicación depende de algo que solo existe en la computadora. Los culpables habituales son las variables de entorno, el archivo .env, las versiones del runtime, las mayúsculas en nombres de archivo, el puerto y dependencias que faltan.

Actualizado el 8 min de lectura

Objetivos de aprendizaje

Después de leer este artículo podrás:

  • Ordenar las causas más probables de un fallo que solo aparece al desplegar
  • Comparar tu entorno local y el de producción punto por punto
  • Hacer que la aplicación falle pronto y con un mensaje claro, en lugar de en silencio
En esta página

Ejecutaste la aplicación cien veces en tu máquina. La desplegaste y ahora se cae al arrancar, devuelve errores o arranca y no hace nada. El código no cambió entre esos dos momentos, así que rara vez el problema es el código.

Lo que cambió es todo lo que lo rodea: el sistema operativo, la versión del runtime, los archivos presentes, las variables de entorno, la red y la memoria disponible. Tu computadora acumula años de estado que ya ni notas. Un servidor empieza vacío y solo tiene lo que le diste.

log del despliegue: lee el primer errorTypeError: Cannot read properties of undefined1Variables de entornodefínelasen el servidor2Versionesfija las versionesy usa el lockfile3Archivos y mayúsculasnombre exacto yruta relativa4Puerto y hostPORT del entorno,host 0.0.0.05Recursosmás memoria o menospico al arrancar
Lee el primer error del log y revisa el entorno en este orden, de la causa más común a la menos común.

¿Por dónde empezar?#

Lee el log desde el inicio de la ejecución, no desde el final. El primer error es la causa, y los siguientes suelen ser consecuencia. Si necesitas ayuda para leerlo, mira cómo leer los logs de la aplicación.

Después recorre las comprobaciones de abajo, en este orden. Van de la causa más común a la menos común, y cada una toma unos minutos.

¿Faltan variables de entorno?#

Es la causa número uno. Una aplicación al estilo twelve-factor guarda la configuración, como credenciales y URLs, en variables de entorno, separada del código (The Twelve-Factor App). En local probablemente las sacas de un archivo .env. Ese archivo está, y debe estar, en el .gitignore, así que un deploy desde Git nunca lo lleva.

Síntomas típicos:

text
TypeError: Cannot read properties of undefined (reading 'split')
DiscordjsError [TokenInvalid]: An invalid token was provided.
connect ECONNREFUSED 127.0.0.1:5432

El último es un clásico: falta la URL de la base de datos, la biblioteca recurre a localhost y en el servidor no hay nada escuchando ahí.

Revisa tres cosas:

  1. ¿Producción tiene todas las variables? Lista los nombres que lee tu código y compáralos con los definidos en el servidor. Los nombres son exactos y distinguen mayúsculas de minúsculas.
  2. ¿Tu código carga el archivo? process.env solo ve aquello con lo que se inició el proceso. En las versiones recientes de Node.js, puedes arrancar con node --env-file=.env index.js (documentación de Node.js). Un simple node index.js no lee ningún archivo, y Python necesita un cargador como python-dotenv.
  3. ¿Los valores son strings? Las variables de entorno siempre son strings. DEBUG=false es el string "false", que es truthy en JavaScript. Node.js también convierte a string todo lo que asignas a process.env, así que asignar undefined guarda el texto "undefined" (documentación de Node.js).

Falla pronto para que la próxima ausencia sea evidente:

js
const required = ["DISCORD_TOKEN", "DATABASE_URL"];
const missing = required.filter((name) => !process.env[name]);
if (missing.length > 0) {
  throw new Error(`Missing environment variables: ${missing.join(", ")}`);
}

Para la diferencia entre los valores que se leen al compilar y en tiempo de ejecución, mira variables de build y de runtime. Para lo básico, mira qué son las variables de entorno.

¿Coinciden las versiones del runtime y de los paquetes?#

Tu computadora puede usar una versión nueva de Node.js mientras el servidor usa por defecto una más antigua, o al revés. Una sintaxis o API que existe en una versión lanza SyntaxError o TypeError en otra. Lo mismo pasa con Python: una sentencia match o una función más nueva de la biblioteca estándar falla en un intérprete antiguo.

  • Imprime la versión en los dos sitios: node --version, python --version.
  • Declara la versión que necesita tu aplicación. En package.json, el campo engines indica con qué versiones de Node.js funciona el paquete, pero por defecto es solo orientativo (documentación de npm), así que no frena por sí solo una versión equivocada. Revisa también la configuración de runtime de tu hosting.
  • Haz commit del lockfile (package-lock.json, poetry.lock o un requirements.txt fijado con pip freeze, mira la documentación de pip). Sin él, el servidor puede resolver versiones de dependencias más nuevas que las que probaste.

En el servidor, instala desde el lockfile en lugar de confiar en una carpeta copiada:

bash
npm ci

npm ci exige un lockfile, borra cualquier node_modules existente y falla si package.json y el lockfile no coinciden (documentación de npm). Ese fallo es útil, porque saca a la luz la diferencia al instalar y no en tiempo de ejecución.

¿Falta una dependencia o un archivo?#

Errores como Cannot find module 'express' o No module named 'discord' significan que el paquete nunca se instaló donde se ejecuta la aplicación. Motivos comunes: está en devDependencies pero se usa en tiempo de ejecución, lo instalaste de forma global en tu computadora o envías un node_modules armado en otro sistema operativo. La guía completa está en errores de Cannot find module.

Los archivos se comportan igual. Un archivo puede faltar porque:

  • Está en el .gitignore o quedó fuera del ZIP que subiste (un config.json, una carpeta de datos, una fuente).
  • Lo genera un paso de build que ejecutaste en local pero nunca en el servidor. Las carpetas dist/ de TypeScript son el caso habitual.
  • Lo lees con una ruta absoluta como C:\Users\yo\proyecto\data.json. Usa rutas relativas al archivo (path.join(__dirname, "data.json")) para que la aplicación no dependa de dónde está.

¿Una diferencia de mayúsculas o de ruta la rompe?#

Los sistemas de archivos de Windows y macOS normalmente no distinguen mayúsculas, así que require("./Utils") encuentra utils.js. Linux los trata como dos nombres distintos y lanza MODULE_NOT_FOUND. Los servidores son casi todos Linux, lo que convierte esto en el clásico fallo de "en mi Windows funciona".

text
Error: Cannot find module './Utils'

Corrígelo haciendo que el import coincida exactamente con el nombre del archivo. Si Git ya registró el archivo con las mayúsculas equivocadas, renómbralo con git mv, porque un cambio de nombre normal puede pasar inadvertido para Git en un disco que no distingue mayúsculas. Algunas trampas relacionadas:

  • Separadores de ruta: arma las rutas con path.join, no concatenando "\\".
  • Los scripts de shell guardados con saltos de línea de Windows (CRLF) pueden fallar en Linux con errores de bad interpreter.
  • Los módulos nativos (sharp, bcrypt, better-sqlite3) incluyen binarios compilados. Un binario armado en tu sistema no funciona en otro. Instala en el servidor para que se descargue la versión correcta.

¿El puerto y el host están mal?#

La aplicación arranca, el log parece sano, pero la URL da timeout o devuelve un error de gateway. Dos causas:

  • Puerto fijo en el código. Muchos hostings le dicen a tu aplicación qué puerto usar. Léelo del entorno y deja un valor por defecto para trabajar en local: const port = process.env.PORT ?? 3000;. Revisa en la documentación de tu hosting cuál es la variable exacta.
  • Escuchar en la dirección equivocada. Escuchar solo en localhost o 127.0.0.1 significa que solo los procesos dentro de la misma máquina o contenedor pueden conectarse. En Node.js, server.listen(port) sin host acepta conexiones en todas las interfaces (:: o 0.0.0.0) (documentación de Node.js); si pasas "localhost" de forma explícita, cámbialo por "0.0.0.0". Servidores de Python como el servidor de desarrollo de Flask (documentación de Flask) y uvicorn (documentación de uvicorn) escuchan en 127.0.0.1 por defecto, así que pasa --host 0.0.0.0.

Revisa también las URLs dentro de tu propio código. Un front-end que llama a http://localhost:4000/api funciona para ti y para nadie más. Convierte la URL base en una variable.

¿Se están agotando los recursos?#

Tu computadora puede tener 16 GB de RAM y una CPU rápida. Un plan pequeño de servidor tiene un techo de memoria estricto. Si el proceso muere sin stack trace, o el log termina con Killed o JavaScript heap out of memory, probablemente superaste el límite. Los picos de memoria al arrancar (cargar un dataset grande, construir cachés) son un motivo común. Mira errores de falta de memoria para confirmarlo y corregirlo.

El acceso a la red también cambia. Una base de datos que solo acepta conexiones desde tu IP, una clave de API restringida a localhost o una lista de permitidos que no incluye la dirección del servidor funcionan en casa y son rechazadas desde el servidor.

¿Cómo evitas que vuelva a pasar?#

Reduce la distancia entre los dos entornos:

  1. Mantén un .env.example con todos los nombres de variables y ningún secreto, y valida al arrancar como se mostró arriba.
  2. Fija las versiones del runtime y de las dependencias, e instala desde el lockfile.
  3. Prueba un clon limpio. Clona el repositorio en una carpeta nueva, instala desde cero, copia solo las variables que tiene producción y ejecútalo. Si se rompe, encontraste un archivo o una variable que faltaba en dos minutos.
  4. Usa un contenedor en local si la aplicación es compleja, para ejecutar el mismo sistema operativo y la misma imagen base (qué es Docker).
  5. Registra la configuración efectiva al arrancar (nombres y versiones, nunca valores secretos), para que las primeras líneas de cada log de deploy digan qué vio la aplicación.

Cómo hacerlo en Square Cloud#

Las comprobaciones de arriba valen en cualquier hosting. En Square Cloud, cada una tiene un lugar concreto donde revisarla.

Define las variables por aplicación en el panel, la API, la CLI o VS Code. El panel importa un archivo .env y exporta las variables como KEY=VALUE, y un cambio se aplica en el siguiente reinicio, sin rebuild. Así, la pregunta "¿está definida mi variable?" se responde en una página de ajustes, sin suposiciones.

Sigue el arranque en tiempo real desde el panel, la CLI o la extensión de VS Code, y lee el primer error. Cada aplicación se ejecuta en un contenedor aislado y solo ve lo que desplegaste y configuraste, como en la prueba del clon limpio.

Algunas reglas de la plataforma coinciden con las causas de este artículo. Al arrancar, ejecuta npm install cuando no existe node_modules, con NODE_ENV=production, así que las devDependencies se omiten. Una aplicación web debe escuchar en 0.0.0.0 en el puerto de la variable PORT. El archivo squarecloud.app es opcional: el panel lo genera al desplegar con el runtime, la memoria y el archivo principal, o puedes escribirlo tú en la raíz del proyecto. Como la memoria forma parte de esa configuración, un desbordamiento de memoria se corrige subiendo ese valor, dentro de la RAM de tu plan, y volviendo a iniciar la aplicación.

Mira cómo funcionan las variables en la página de variables de entorno.

Deploy

Sube desde GitHub, la CLI o un ZIP y publica en segundos

Más información

Preguntas frecuentes

¿Por qué mi aplicación funciona en localhost pero se cae después de desplegarla?

Al servidor le falta algo que tu computadora sí tiene: un archivo .env, un paquete instalado de forma global, la misma versión del runtime o un archivo cuyo nombre difiere en mayúsculas y minúsculas. El log de arranque casi siempre lo nombra, así que lee el primer error, no el último.

¿Por qué mi archivo .env no funciona en producción?

El archivo .env suele estar en el .gitignore, así que nunca llega al servidor. Producción necesita las mismas variables definidas en su propia configuración, o un cargador explícito que lea el archivo que envías.

¿Por qué funciona en Windows pero falla en Linux?

Los sistemas de archivos de Linux distinguen mayúsculas de minúsculas, así que ./Utils.js y ./utils.js son archivos distintos. Los separadores de ruta, los saltos de línea en scripts de shell y los binarios nativos compilados para otro sistema operativo causan el mismo tipo de fallo.

¿Cómo reproduzco en local un bug de producción?

Ejecuta la aplicación desde un clon limpio, con instalación nueva, la versión del runtime de producción y solo las variables que tiene producción. Ejecutarla en un contenedor con la misma imagen base elimina casi todas las diferencias restantes.

¿Es un bug del código o un problema del entorno?

Si el mismo commit funciona en una máquina y falla en otra, la diferencia es el entorno: versiones, variables, archivos, red o recursos. Compara los entornos antes de tocar el código.

Siguiente artículoEntornos de desarrollo, staging y producciónDesarrollo, staging y producción son tres copias de tu app con config y datos separados. Aprende a montarlas, promover código y evitar que se mezclen.

Más en Deploy

Ver los 33 artículos