Objetivos de aprendizaje
Después de leer este artículo podrás:
- Distinguir variables de build y de runtime en Next.js, Vite y Docker
- Decidir qué valores pueden ser públicos y cuáles deben quedarse en el servidor
- Mantener los secretos fuera de los bundles y de las capas de las imágenes Docker
En esta página
- ¿Qué diferencia hay entre variables de build y de runtime?
- ¿Qué variables son de build en Next.js?
- ¿Qué variables son de build en Vite y otros bundlers?
- ¿Cómo funcionan las variables de build y de runtime en Docker?
- ¿Cómo decidir dónde va cada valor?
- ¿Por qué una variable cambiada no surte efecto?
- Cómo hacerlo en Square Cloud
- Preguntas frecuentes
Defines API_URL en el panel, reinicias la app y el sitio sigue llamando a la dirección vieja. O un secreto que creías privado aparece en la pestaña de red del navegador. Los dos problemas tienen la misma causa: la variable se leyó en un momento distinto del que suponías.
Una variable se puede consumir en dos momentos. En tiempo de build, una herramienta como Next.js, Vite o Docker la lee mientras genera los archivos finales. En tiempo de ejecución (runtime), el proceso en marcha la lee de su entorno. El momento de la lectura decide si un cambio pide un reinicio o un nuevo build, y si el valor puede filtrarse.
¿Qué diferencia hay entre variables de build y de runtime?#
Una variable de build se lee una sola vez, durante el build, y por lo general se escribe en los archivos generados como texto plano. A partir de ahí, manda el archivo, no el entorno.
Una variable de runtime se consulta cada vez que el proceso inicia, con process.env en Node.js u os.environ en Python. Cambias el valor, reinicias y la app ve el nuevo. Es el modelo de la regla de configuración de twelve-factor: la configuración vive en variables de entorno, porque cambia entre deploys y el código no.
| Tiempo de build | Runtime | |
|---|---|---|
| Quién la lee | Bundler, compilador, build de Docker | Tu proceso en ejecución |
| Un cambio exige | Nuevo build | Reinicio |
| Dónde termina el valor | Dentro de los archivos generados | Solo en la memoria del proceso |
| Segura para secretos | Solo si nunca llega a los archivos generados | Sí, si nunca la registras en logs ni la expones |
¿Qué variables son de build en Next.js?#
En Next.js, todo lo que empieza con NEXT_PUBLIC_ es de build. La documentación dice que Next.js incrusta el valor en el bundle de JavaScript durante next build, reemplazando cada referencia process.env.NEXT_PUBLIC_* por un string fijo. Después del build, la app ya no responde a cambios en esas variables, y una misma imagen Docker promovida a varios entornos conserva el valor del build.
Las variables sin prefijo se quedan en el servidor. Si las lees durante el renderizado dinámico, se evalúan en runtime:
// app/page.tsx
import { connection } from "next/server";
export default async function Page() {
await connection(); // activa el renderizado dinámico: se evalúa en cada petición
const apiUrl = process.env.API_URL; // runtime, solo en el servidor
return <p>Using {apiUrl}</p>;
}Las lecturas dinámicas como process.env[name] no se incrustan, así que nunca llegan al navegador. Si el navegador necesita un valor de runtime, exponlo desde una ruta de API propia en lugar de una variable NEXT_PUBLIC_.
¿Qué variables son de build en Vite y otros bundlers?#
Vite reemplaza los valores de import.meta.env de forma estática en el build. Solo las variables que empiezan con VITE_ llegan al código del cliente, y la documentación advierte que no deben contener información sensible, como claves de API, porque quedan dentro del código empaquetado.
// src/main.js
const api = import.meta.env.VITE_API_URL; // congelada en el bundle durante el build
const dbPassword = import.meta.env.DB_PASSWORD; // undefined en el código del clienteVite también da prioridad a las variables que ya existen en el entorno sobre los archivos .env, así que VITE_API_URL=https://api.example.com npm run build le gana a un archivo .env. Otros bundlers siguen la misma idea con su propio prefijo: el prefijo significa "copia esto en el bundle del navegador durante el build".
Un servidor Node.js o Python simple no tiene paso de build, así que todo lo que lee es runtime.
¿Cómo funcionan las variables de build y de runtime en Docker?#
Docker suma una tercera pregunta: ¿el valor termina en una capa de la imagen? ARG existe durante el build y ENV persiste en la imagen, pero la documentación de Docker dice que los argumentos de build y las variables de entorno no deben llevar secretos, porque quedan en la imagen final.
Para un secreto que el build de verdad necesita (un token de un registro privado de paquetes, por ejemplo), usa un secret mount. Se monta como archivo en /run/secrets/<id> (o se expone como variable de entorno con env=) solo para esa instrucción y no se guarda en ninguna capa:
#syntax=docker/dockerfile:1
FROM node:lts-slim AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN npm ci
COPY . .
ARG VITE_API_URL
RUN npm run build
FROM node:lts-slim
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]Aquí npm_token existe solo durante ese RUN (tu .npmrc debe leer ${NPM_TOKEN}), VITE_API_URL es un valor público incrustado a propósito y DATABASE_URL queda fuera de la imagen a propósito: se pasa cuando el contenedor inicia.
docker build --secret id=npm_token,env=NPM_TOKEN \
--build-arg VITE_API_URL=https://api.example.com -t my-app .
docker run -e DATABASE_URL="$DATABASE_URL" my-appEl build en varias etapas también ayuda: la imagen final copia solo los archivos que eliges de la etapa de build, no sus capas ni sus argumentos. Más sobre esto en cómo dockerizar una aplicación.
¿Cómo decidir dónde va cada valor?#
Hazte tres preguntas, en este orden:
- ¿Haría daño que cualquier visitante lo viera? Si la respuesta es sí (URL de la base de datos, secreto de API, token de bot), debe ser una variable de runtime, solo en el servidor. Nunca le pongas prefijo.
- ¿El navegador la necesita? Entonces es pública por definición. Un prefijo de build sirve para valores como un ID de analítica o la dirección de una API pública.
- ¿Necesitas cambiarla sin un nuevo build? Entonces no la incrustes. Léela en el servidor al iniciar, o sírvela desde un pequeño endpoint de configuración.
Una trampa frecuente: un secreto funciona en tu máquina porque tu .env lo tiene, y luego se filtra porque alguien lo renombró a VITE_SECRET para que desapareciera un error de undefined. Si el framework dice que una variable es undefined en el navegador, es la protección haciendo su trabajo.
¿Por qué una variable cambiada no surte efecto?#
Revisa esta lista:
- El proceso inició antes del cambio. Las variables de runtime se leen al iniciar; reinicia la app.
- El valor se incrustó en el build. Búscalo en los archivos generados con
grep -r "old-value" .next dist build. Si aparece, haz un nuevo build. - El build corrió sin la variable. Comprueba que exista donde corre el build, que puede ser un lugar distinto de donde corre la app. Mira cómo leer errores de build y deploy con GitHub Actions, donde las variables de build viven en la configuración del CI.
- Todo es un string.
"false"es verdadero en una condición; conviértelo en el código.
Cómo hacerlo en Square Cloud#
En Square Cloud, defines las variables de entorno por aplicación en el panel, la API, la CLI o la extensión de VS Code. El panel también importa un archivo .env y exporta en formato KEY=VALUE. El cambio se aplica en el próximo reinicio, sin nuevo build, lo que coincide con el modelo de runtime de arriba. Tu app puede leer SQUARECLOUD_APP_ID en runtime para conocer el ID de su propia aplicación.
Por eso, los secretos como URLs de bases de datos y tokens van en estas variables de runtime, no en tu repositorio ni en tu bundle. La plataforma no tiene paso de build: al iniciar solo instala las dependencias (npm install cuando no existe node_modules), y tus variables ya existen en ese momento. Los valores que tu bundler incrusta (NEXT_PUBLIC_, VITE_) tienen que estar bien en el job de CI donde corres el build, como GitHub Actions, o puedes poner el build en el comando de inicio, donde las variables ya están definidas.
Los detalles están en la guía de variables de entorno.
Deploy
Sube desde GitHub, la CLI o un ZIP y publica en segundos
Preguntas frecuentes
¿Por qué mi variable muestra el valor viejo después de cambiarla en el panel?
Si el framework la incrustó en el build (NEXT_PUBLIC_ en Next.js, VITE_ en Vite), el valor viejo ya está escrito en los archivos JavaScript. Cambiar el entorno no sirve hasta que hagas un nuevo build.
¿Puedo poner un secreto de API en una variable NEXT_PUBLIC_ o VITE_?
No. Esas variables se copian en el JavaScript que se envía a cada visitante, y cualquiera puede leerlas en el navegador. Guarda los secretos en variables sin prefijo, leídas solo en el servidor.
¿ARG de Docker es seguro para contraseñas?
No. La documentación de Docker advierte que los argumentos de build y las variables de entorno quedan en la imagen final. Usa un secret mount (RUN --mount=type=secret) para los secretos del build y pasa los secretos de runtime al iniciar el contenedor.
¿Las apps Node.js y Python sin bundler tienen variables de build?
Solo si agregas un paso de build. Una app interpretada sin bundler lee process.env u os.environ al iniciar, así que toda variable es de runtime.
Más en Deploy
Ver los 33 artículos- Qué son las variables de entornoLas variables de entorno son pares clave-valor que el sistema operativo entrega a un proceso al iniciar. Aprende a leerlas en Node.js y Python sin exponer tokens.
- Qué es un deploy o despliegueUn deploy o despliegue lleva una versión de tu código a un servidor, la prepara y la pone en marcha. Conoce las etapas, los métodos y cómo hacer rollback.
- ¿Cuánta RAM y CPU necesita mi aplicación?Dimensiona RAM y CPU midiendo el pico real de la aplicación y sumando un margen del 30%. Mira cómo medir en Node.js, Python y Java y qué hacer cuando falte.
- Deploy automático con GitHub ActionsEl deploy automático con GitHub Actions envía tu código al servidor en cada push. Mira el workflow paso a paso, los secrets, las pruebas y los errores comunes.