Módulo 4: Entornos dev, staging y prod en self-hosted

6. Secretos externos y variables

Descripción

Al terminar esta lección vas a poder sacar los valores de configuración —y en particular los secretos— fuera del workflow, hacia el entorno donde corre, usando la expresión $env para que un nodo lea una variable del .env de su entorno. Vas a conocer las Variables de n8n ($vars) y por qué son una feature de pago, vas a entender la diferencia entre una variable de configuración y un secreto, y vas a saber, con honestidad de planes, qué son los secretos externos (Vault, AWS Secrets Manager, GCP, Azure) como feature Enterprise y cuál es la alternativa equivalente en Community usando variables de entorno.

Esto importa porque cierra el círculo del aislamiento. En la lección 5 sacaste el valor de la credencial de la vista, pero seguía viviendo dentro de la instancia de n8n. Hay un paso más limpio: que el workflow ni siquiera contenga valores que cambian por entorno —una URL base, un identificador de cuenta, un secreto—, sino que los lea del entorno en tiempo de ejecución. Así, el mismo order-triage.json corre en dev apuntando al CRM sandbox y en prod apuntando al CRM real, sin una sola diferencia en su JSON, porque la diferencia la provee el entorno. Es el grado máximo de "un workflow, muchos entornos".

Conexión con el módulo: la lección 4 te dio el .env por entorno; la 5, las credenciales por entorno. Esta las une: te enseña a que el workflow consuma ese .env con $env, cerrando el patrón. También es la lección de honestidad de planes del módulo en materia de secretos: distingue lo que se hace gratis en Community ($env + .env) de lo que solo trae Enterprise (Variables y secretos externos), y te da el workaround Community para lo segundo. La lección 7 sube un nivel más en esa honestidad, comparando el patrón entero de esta guía con la feature nativa de entornos de pago.

Un secreto no se escribe en el guion: se pasa en un sobre

Empecemos por el principio que ordena la lección. Imagina una obra de teatro donde un personaje tiene que decir un número de cuenta bancaria real. Hay dos formas de manejarlo.

La forma mala: escribir el número real en el guion, en todas las copias impresas. Ahora el número está en cada libreto, en manos de cada actor, técnico y asistente; cualquiera que tenga una copia del guion tiene el número. Y si mañana el número cambia, hay que reimprimir todos los guiones.

La forma buena: en el guion se escribe solo un marcador —"[NÚMERO DE CUENTA]"— y el número real se le pasa al actor en un sobre cerrado, aparte, justo antes de la función. El guion se puede fotocopiar y repartir sin riesgo, porque no contiene el secreto. El secreto viaja por un canal distinto, controlado, y se puede cambiar sin tocar el guion.

Esa es exactamente la diferencia entre escribir un valor dentro del workflow y leerlo del entorno. El workflow es el guion: se versiona, se comparte, se copia entre entornos. Si le escribes dentro la URL del CRM real o un secreto, ese valor viaja en cada copia del guion —al repositorio, a cada entorno—, y cambiarlo obliga a editar el workflow. Si en cambio el workflow dice solo $env.CRM_BASE_URL —el marcador—, el valor real se lo pasa el entorno en un sobre —su .env—, distinto en cada entorno, cambiable sin tocar el workflow.

La regla que sale de aquí, y que gobierna la lección:

Lo que cambia por entorno o es secreto no se escribe dentro del workflow: se lee del entorno donde corre. El workflow lleva el marcador; el entorno provee el valor.

Qué es $env, con manzanas

La herramienta que hace esto en Community se llama $env, y es una expresión. Antes de nada, dos definiciones.

Una variable de entorno es un valor con nombre que existe alrededor de un programa, provisto por el sistema que lo arranca —en nuestro caso, por el .env que Docker Compose le inyecta al contenedor de n8n—. No vive dentro del workflow; vive en el entorno que rodea a n8n. CRM_BASE_URL, POSTGRES_PASSWORD y la propia N8N_ENCRYPTION_KEY son variables de entorno.

Una expresión en n8n es un pedacito de código que escribes dentro de un campo de un nodo, entre llaves dobles, para calcular un valor en vez de escribirlo fijo. En lugar de teclear una URL a mano en el campo "URL" de un nodo HTTP Request, escribes una expresión que la produce. Se ven así: {{ ... }}.

$env es la expresión que, dentro de un nodo, lee una variable de entorno por su nombre. Si en el .env del entorno hay una línea CRM_BASE_URL=https://sandbox.crm.example, entonces dentro de un nodo puedes escribir:

{{ $env.CRM_BASE_URL }}

y n8n, al ejecutar, reemplaza esa expresión por el valor que la variable tenga en ese entorno. En dev da la URL sandbox; en prod, donde el .env tiene CRM_BASE_URL=https://crm.cumbre.com, la misma expresión da la URL real. El workflow no cambió; el valor lo puso el entorno.

Un par de precisiones importantes, para no enseñarte algo que luego te confunda:

  • $env se usa en los campos de un nodo normal, escrito como expresión —por ejemplo, en el campo "URL" o en un header de un nodo HTTP Request—. Es donde tiene sentido: parametrizar la configuración de un nodo según el entorno.
  • $env no está disponible dentro del nodo Code en n8n 2.0. El nodo Code está restringido y no accede a las variables de entorno por esa vía. Así que cuando quieras que un valor del entorno entre a tu workflow, ponlo en el campo de un nodo como expresión, no lo busques desde código dentro de un nodo Code.
  • En el self-hosted, el acceso de las expresiones a $env está permitido por defecto. Lo controla la variable N8N_BLOCK_ENV_ACCESS_IN_NODE, cuyo valor por defecto es false (es decir, el acceso está permitido). Si en tu instancia $env devuelve vacío, es probable que esa variable esté puesta en true; conviene verificarlo en la doc de tu versión, porque este comportamiento puede cambiar y en n8n Cloud suele estar más restringido.

Ejemplo trabajado: la URL del CRM que cambia sola por entorno

Veamos el patrón completo con order-triage. El nodo HTTP Request de order-triage consulta el CRM. En vez de escribir la URL del CRM fija en el nodo, vamos a leerla del entorno. Recuerda: tú haces esto en tu instancia; la guía no ejecuta nada.

Paso 1 — Declara la variable en cada .env. Agrega una línea al .env de cada entorno con la URL base del CRM de ese entorno. En environments/dev/.env:

CRM_BASE_URL=https://sandbox.crm.example

En environments/prod/.env:

CRM_BASE_URL=https://crm.cumbre.com

Misma variable, valor distinto por entorno. (Si esta URL no fuera secreta —una URL base a veces no lo es—, igual gana viviendo en el .env: es configuración que cambia por entorno, y ese es exactamente el material de $env. Un secreto de verdad, como una llave, se maneja mejor como credencial de n8n, que además la cifra; $env brilla para la configuración no secreta o de bajo riesgo que varía por entorno.)

Paso 2 — Declárala también en el .env.example. Para que el contrato quede completo, agrega la variable al .env.example de cada entorno, con el nombre y sin el valor real (o con un valor de ejemplo evidente):

CRM_BASE_URL=

Paso 3 — Usa $env en el nodo. En el nodo HTTP Request de order-triage, en el campo "URL", en vez de una URL fija, escribe una expresión que combine la base del entorno con la ruta del endpoint:

{{ $env.CRM_BASE_URL }}/customers/{{ $json.customerId }}

Aquí {{ $env.CRM_BASE_URL }} trae la base según el entorno, y {{ $json.customerId }} trae el id del cliente del pedido actual (eso ya lo conoces de construir workflows). Qué esperar: al ejecutar en dev, la URL resultante es https://sandbox.crm.example/customers/123; el mismo nodo en prod produce https://crm.cumbre.com/customers/123. Un solo workflow, dos destinos, cero cambios en el JSON.

Paso 4 — Reinicia el entorno tras cambiar el .env. Un detalle que atrapa a mucha gente: las variables de entorno se leen cuando el contenedor arranca. Si agregas CRM_BASE_URL al .env con n8n ya corriendo, n8n todavía no la ve. Tienes que reiniciar el stack para que tome el .env nuevo:

docker compose down
docker compose up -d

Qué esperar: tras el reinicio, {{ $env.CRM_BASE_URL }} ya devuelve el valor. Si sigue vacío, o el .env no se guardó, o N8N_BLOCK_ENV_ACCESS_IN_NODE está en true. (La -d de up -d significa detached: levanta el stack en segundo plano y te devuelve la terminal, en vez de quedarse mostrando los logs.)

No todo lo que sale del workflow es igual de sensible

Antes de seguir, conviene afinar una idea que evita confusiones: sacar un valor del workflow no es una sola cosa. Hay un espectro de sensibilidad, y dónde debe vivir cada valor depende de dónde caiga en ese espectro. Pensarlo como "secreto sí / secreto no" es demasiado grueso; hay grados.

Recorramos el espectro de menos a más sensible, con un ejemplo de cada uno en order-triage y dónde vive mejor:

1. Configuración pública que cambia por entorno. La URL base del CRM (https://sandbox.crm.example vs. https://crm.cumbre.com) no es realmente un secreto —cualquiera que use el sistema la ve—, pero cambia por entorno. Vive bien en el .env y se lee con $env. Ponerla ahí no es por seguridad; es por portabilidad entre entornos.

2. Identificadores que cambian por entorno. El id de una cuenta de Cumbre en un servicio externo, o un número de proyecto: no son secretos de alto valor, pero varían por entorno y no queremos escribirlos dentro del workflow. También $env + .env.

3. Secretos de bajo valor. Una llave de API sandbox con gasto limitado, o un token de una cuenta de prueba: si se filtrara, el daño es acotado. Se puede manejar como credencial de n8n (que la cifra) o, en un apuro, por .env con $env. Preferir la credencial cifrada.

4. Secretos de alto valor. La llave real del CRM de producción, la llave del proveedor de IA con gasto real: si se filtran, hay daño serio. Estos viven como credenciales de n8n —que las cifra con la clave del entorno— y, en una empresa con la feature Enterprise, en un gestor de secretos externo. Nunca en texto plano dentro del workflow, y con el máximo cuidado incluso en el .env.

La regla de decisión que sale del espectro:

$env + .env es para configuración que cambia por entorno y para secretos de bajo valor. Los secretos de alto valor viven como credenciales cifradas de n8n (y, a escala, en un gestor externo). Nada sensible se escribe fijo en el workflow.

¿Por qué prefiere un secreto de alto valor ser una credencial de n8n antes que una variable $env? Por dos razones. Primera, la credencial se guarda cifrada con la N8N_ENCRYPTION_KEY del entorno; una variable de $env vive en texto plano en el .env y en el entorno del contenedor. Segunda, n8n trata a las credenciales con cuidado especial: no las muestra en los logs de ejecución, las oculta en la interfaz. Una variable de $env que uses en un campo podría aparecer en un log de depuración. Así que la jerarquía es clara: para lo de alto valor, credencial cifrada; para la configuración por entorno, $env. No son rivales, son herramientas para grados distintos de sensibilidad.

Las Variables de n8n ($vars): útiles, pero de pago

n8n tiene otra forma de guardar valores reutilizables, distinta de $env: las Variables. Son pares nombre-valor que defines en la interfaz de n8n, y que los workflows leen con la expresión $vars:

{{ $vars.crmBaseUrl }}

Suenan parecidas a $env, y para el propósito de "un valor reutilizable que no quiero repetir en cada nodo" cumplen una función similar. La diferencia práctica es de plan: según la documentación oficial, las Variables ($vars) son una feature de los planes de pago —Enterprise en self-hosted, y Pro/Enterprise en Cloud—. No están disponibles en la edición Community ni en la Community registrada (la gratuita que desbloquea algunos extras registrando tu correo). Como los planes cambian, conviene confirmarlo en la doc y la página de precios de tu versión.

Entonces, ¿qué usa esta guía? $env, porque es lo que funciona gratis en Community. La equivalencia mental es directa:

$vars (Variables de n8n)$env (variables de entorno)
Dónde se definenEn la interfaz de n8nEn el .env del entorno
Cómo se leen{{ $vars.nombre }}{{ $env.NOMBRE }}
PlanDe pago (Enterprise / Pro)Community (gratis)
Distintas por entornoSí, por instanciaSí, por .env de cada entorno

Para lo que hacemos —un valor por entorno, gratis— $env con el .env es la vía. Si algún día tu equipo paga por un plan que trae Variables, migrar es sencillo: cambias $env.CRM_BASE_URL por $vars.crmBaseUrl y defines la variable en la interfaz de cada instancia. Mientras tanto, $env te da lo mismo a costo cero.

¿Cuándo tiene sentido $vars sobre $env, más allá de que sea de pago? Cuando quien administra los valores es alguien que vive en la interfaz de n8n y no toca los archivos del servidor: definir una variable en una pantalla es más cómodo para esa persona que editar un .env y reiniciar el contenedor. $vars también evita el reinicio —cambias la variable en la interfaz y toma efecto sin apagar nada—, que es una ventaja real en producción. Pero para un equipo técnico que ya gestiona el .env de cada entorno, $env cubre la necesidad sin esa comodidad extra. Como con casi todo en este módulo: la feature de pago compra ergonomía, no una capacidad que de otro modo no tendrías. Empieza con $env; si algún día la comodidad de $vars justifica el plan, la migración es un cambio de prefijo.

$env no es solo para secretos: también para el comportamiento por entorno

Vale la pena mostrar que $env sirve para más que URLs y llaves. A veces quieres que un workflow se comporte distinto según el entorno, sin cambiar su lógica. $env es la vía limpia para eso también.

Un ejemplo concreto en order-triage. Supongamos que en dev quieres que el workflow, además de clasificar, deje un rastro extra en los logs para depurar —imprima el pedido completo antes de procesarlo—, pero en prod no, para no ensuciar los logs de producción con datos de clientes. Ese "¿imprimo el detalle o no?" es un comportamiento que cambia por entorno. En vez de tener dos workflows, pones una variable en el .env:

# environments/dev/.env
DEBUG_MODE=true

# environments/prod/.env
DEBUG_MODE=false

Y en un nodo IF de order-triage, la condición lee la variable:

{{ $env.DEBUG_MODE === "true" }}

Qué esperar: en dev, la condición es verdadera y el workflow toma la rama que imprime el detalle; en prod, es falsa y salta esa rama. El mismo order-triage.json, con un comportamiento distinto por entorno, gobernado por una variable del .env, sin duplicar el workflow.

Dos precauciones. Primera, un detalle de tipos: las variables de entorno siempre son texto, así que DEBUG_MODE no es el booleano true, es la cadena "true". Por eso la comparación es === "true" (contra la cadena), no === true. Es un tropiezo clásico: comparar contra el booleano y que nunca se cumpla. Segunda, no abuses de esto: los comportamientos que cambian por entorno deben ser pocos y de bajo riesgo (un log extra, un límite distinto). La lógica de negocio —el umbral de 5000, la decisión de clasificar— es igual en los tres entornos y va dentro del workflow, no en una bifurcación por $env. $env gobierna la configuración del comportamiento, no reescribe la lógica.

Los secretos externos: qué son y por qué son Enterprise

Llegamos a la parte de honestidad más importante de la lección. Hay una categoría de herramienta que n8n integra y que conviene conocer aunque no la puedas usar gratis: los secretos externos (external secrets).

Un gestor de secretos externo es un servicio dedicado, fuera de n8n, cuyo único trabajo es guardar secretos de forma segura y entregárselos a las aplicaciones que los necesitan, con control de acceso, auditoría y rotación. Los más conocidos son HashiCorp Vault, AWS Secrets Manager, Azure Key Vault y GCP Secret Manager (y n8n también integra 1Password e Infisical). La idea: en vez de que cada aplicación guarde sus propios secretos, todos viven en un solo lugar central, endurecido, y cada aplicación los pide cuando los necesita.

La feature de n8n que se conecta a esos gestores —para que un workflow use un secreto que vive en Vault sin que el secreto toque nunca la base de datos de n8n— es, según la documentación oficial, una feature Enterprise (self-hosted Enterprise y Enterprise Cloud). No está en Community. Como siempre, los planes cambian; confírmalo en la doc de tu versión.

¿Por qué existe esta feature y qué gana quien la paga? Tres cosas que en equipos grandes valen mucho:

  • Un solo lugar para los secretos de todos los entornos. En vez de un .env por entorno regado por varias máquinas, los secretos viven centralizados, y n8n los pide. Cambiar un secreto se hace en un lado.
  • Auditoría. El gestor registra quién accedió a qué secreto y cuándo. En una empresa con requisitos de cumplimiento, ese registro es obligatorio.
  • Rotación gestionada. El gestor puede rotar secretos automáticamente, sin intervención manual.

El workaround Community: el .env fuera del repo es tu "gestor de secretos"

Aquí está la buena noticia, que es el patrón que esta guía usa: para el objetivo de que los secretos vivan fuera del workflow y cambien por entorno, no necesitas la feature Enterprise. Tu .env por entorno —fuera del repositorio, en cada máquina— cumple la función esencial de un gestor de secretos a la escala de esta guía:

  • Los secretos viven fuera del workflow (en el .env, no en el JSON). ✅
  • Son distintos por entorno (un .env por entorno). ✅
  • Nunca van al repositorio (.gitignore). ✅
  • El workflow los consume en tiempo de ejecución con $env. ✅

Lo que el .env no te da, y la feature Enterprise sí, es la centralización, la auditoría automática y la rotación gestionada. Para un equipo pequeño con tres entornos locales, esa diferencia rara vez justifica el costo: un .env bien guardado por entorno es suficiente y correcto. Para una empresa con muchos entornos, muchas personas y requisitos de cumplimiento, la centralización empieza a pagar su precio. La lección 7 te da la matriz completa para decidir; por ahora, la conclusión honesta:

El patrón "secreto en el .env del entorno, fuera del repo, leído con $env" te da a costo cero lo esencial de un gestor de secretos. Vault, AWS y compañía (vía la feature Enterprise) agregan centralización, auditoría y rotación —valioso a escala, innecesario para empezar.

Y si algún día quieres algo intermedio sin pagar la feature de n8n, existe la vía de un gestor externo que inyecte los secretos como variables de entorno al arrancar el contenedor: el gestor entrega los valores, Docker los pone en el entorno, y n8n los lee con $env igual que hoy. Es más trabajo de montaje, pero mantiene el costo en cero y te acerca a la centralización. No lo necesitas para este módulo; tenlo en el radar para cuando crezcas.

La forma de ese patrón, en grandes trazos y sin entrar en detalle porque es material avanzado, sería: en vez de escribir los secretos a mano en el .env, un script de arranque le pide los valores al gestor externo —por ejemplo, con la CLI de Vault— y los exporta como variables de entorno justo antes de levantar el stack. n8n nunca ve el gestor; solo ve variables de entorno, que consume con $env exactamente como en este módulo. Lo que cambió no es cómo n8n lee el secreto, sino de dónde salió el valor: en vez de un .env estático, de un gestor central que puede rotarlo y auditarlo. Es el mismo $env de siempre, alimentado por una fuente más robusta. Esa continuidad —que el workflow no cambia aunque cambie de dónde vienen los secretos— es justo lo que hace que empezar con $env + .env sea una base sólida y no un callejón: creces cambiando la fuente, no el workflow.

Errores comunes

Escribir el secreto o la URL real dentro del workflow (conceptual, y el que anula todo). Qué pasa: alguien pone la URL del CRM real, o peor un token, directamente en el campo de un nodo, fijo. Ese valor ahora viaja en el JSON: al repositorio, a los tres entornos, a cada copia. En dev se termina apuntando al CRM real, y el secreto queda escrito en un archivo versionado. Por qué pasa: escribir el valor fijo es lo más rápido y "funciona" al probar. Cómo detectarlo: busca en el JSON de tus workflows URLs de producción o cadenas que parezcan llaves; si están ahí, son un problema. Cómo corregirlo: lo que cambia por entorno o es secreto se lee del entorno con $env (o se maneja como credencial). El workflow lleva el marcador, no el valor.

Cambiar el .env y esperar que n8n lo vea sin reiniciar (práctico). Qué pasa: alguien agrega CRM_BASE_URL al .env con n8n corriendo, prueba {{ $env.CRM_BASE_URL }}, y le devuelve vacío. Concluye que $env "no sirve". Por qué pasa: las variables de entorno se leen al arrancar el contenedor; un cambio en el .env no llega a un proceso ya corriendo. Cómo detectarlo: $env.NUEVA_VARIABLE vacío justo después de agregarla al .env sin reiniciar. Cómo corregirlo: reinicia el stack (docker compose down && docker compose up -d) para que n8n tome el .env actualizado. Regla: tocaste el .env, reinicia el entorno.

Creer que $vars funciona en Community (conceptual). Qué pasa: alguien lee un tutorial que usa {{ $vars.algo }}, lo copia en su instancia Community, y no funciona. Pierde tiempo pensando que lo escribió mal. Por qué pasa: $vars y $env se parecen, y muchos tutoriales asumen un plan de pago sin decirlo. Cómo detectarlo: si usas $vars en Community y la variable no resuelve, es la feature, no tu sintaxis. Cómo corregirlo: en Community usa $env con el .env; reserva $vars para cuando tengas un plan que lo incluya. Y como buen hábito, cuando un tutorial use una feature, verifica en qué plan está antes de asumir que la tienes.

Intentar $env dentro de un nodo Code y frustrarse (práctico). Qué pasa: alguien quiere un valor del entorno dentro de un nodo Code en n8n 2.0 y no lo consigue, porque el nodo Code está restringido y no accede a $env por esa vía. Por qué pasa: es natural suponer que si $env existe, funciona en cualquier lado. Cómo detectarlo: $env que no resuelve específicamente dentro de un nodo Code, aunque funcione en otros nodos. Cómo corregirlo: lee el valor del entorno en el campo de un nodo normal (una expresión en un HTTP Request, por ejemplo) y pásalo al Code por sus datos de entrada si de verdad lo necesitas ahí. La configuración del entorno entra por los campos de los nodos, no por dentro del Code.

Ejercicios

Ejercicio 1 — ¿Dentro o fuera del workflow? Para cada valor, di si debería escribirse dentro del workflow (fijo en el nodo) o vivir fuera, en el entorno (leído con $env o como credencial), y por qué: (a) la URL base del CRM, distinta en dev y prod; (b) el umbral de 5000 pesos para mandar un pedido a revisión; (c) el token de autenticación del CRM; (d) el nombre del campo customerId que trae el pedido; (e) el identificador de la cuenta de Cumbre en un servicio externo, distinto por entorno.

Ver solución

(a) Fuera, con $env: cambia por entorno. {{ $env.CRM_BASE_URL }}.

(b) Dentro: el umbral de 5000 es lógica de negocio, igual en los tres entornos. Es parte de lo que el workflow decide, no configuración que varíe por entorno. (Si Cumbre quisiera un umbral distinto por entorno para probar, ahí sí saldría al .env; pero por defecto es lógica y va dentro.)

(c) Fuera, y además como credencial de n8n (que la cifra), no como $env en texto: es un secreto de alto valor. $env sirve para configuración; para una llave, la credencial cifrada es mejor.

(d) Dentro: el nombre del campo customerId es parte de la estructura de los datos que el workflow procesa; no cambia por entorno.

(e) Fuera, con $env: es configuración que cambia por entorno (la cuenta de prueba en dev, la real en prod).

Por qué funciona: el criterio que separa "dentro" de "fuera" es "¿cambia por entorno o es secreto?". Si es lógica igual en todos lados (b, d), va dentro; si varía por entorno o es secreto (a, c, e), va fuera. Y entre "fuera", los secretos de alto valor prefieren ser credenciales cifradas antes que $env en claro.

Ejercicio 2 — Escribe la expresión. El .env de cada entorno tiene una variable CRM_BASE_URL. Quieres que el nodo HTTP Request de order-triage consulte el endpoint /customers/{id} del CRM del entorno, donde {id} viene del campo customerId del pedido. Escribe la expresión completa del campo "URL", y luego di qué URL produce en dev (con CRM_BASE_URL=https://sandbox.crm.example) para un pedido cuyo customerId es A-42.

Ver solución

La expresión del campo "URL":

{{ $env.CRM_BASE_URL }}/customers/{{ $json.customerId }}

En dev, con CRM_BASE_URL=https://sandbox.crm.example y un pedido cuyo customerId es A-42, produce:

https://sandbox.crm.example/customers/A-42

El mismo nodo, sin cambiar nada, en prod (con CRM_BASE_URL=https://crm.cumbre.com) produciría https://crm.cumbre.com/customers/A-42.

Por qué funciona: la expresión combina dos fuentes —el entorno ($env.CRM_BASE_URL, distinto por entorno) y los datos del pedido ($json.customerId, distinto por ejecución)— en una sola URL. Ver que el mismo nodo produce URLs distintas en dev y prod sin tocarse es el "un workflow, muchos entornos" hecho concreto.

Ejercicio 3 — Community o Enterprise. Clasifica cada capacidad como "gratis en Community" o "solo en un plan de pago", y para las de pago, di cuál es el equivalente Community: (a) leer un valor del .env con $env; (b) definir Variables en la interfaz y leerlas con $vars; (c) que un workflow use un secreto guardado en HashiCorp Vault vía la integración de secretos externos; (d) tener secretos distintos por entorno, fuera del repositorio.

Ver solución

(a) Gratis en Community. $env + .env es el patrón base de esta guía.

(b) De pago (Enterprise self-hosted / Pro-Enterprise Cloud). Equivalente Community: $env con el .env, que cumple la misma función de "un valor reutilizable por entorno".

(c) De pago (Enterprise). Equivalente Community: el .env fuera del repo como "gestor de secretos" a pequeña escala, o —con más montaje— un gestor externo que inyecte los secretos como variables de entorno al arrancar el contenedor, que n8n lee con $env.

(d) Gratis en Community. Es justo lo que montaste en las lecciones 4 y 5: un .env por entorno, ignorado por Git. No requiere ninguna feature de pago.

Por qué funciona: si clasificaste bien las cuatro, tienes clara la frontera de planes en materia de secretos —lo esencial (a, d) es gratis; la comodidad y la escala (b, c) se pagan—, y sabes el workaround Community de cada feature de pago. Esa honestidad de planes es lo que te deja tomar decisiones informadas en vez de creer que necesitas pagar para tener entornos seguros.

Resumen y siguiente paso

En esta lección sacaste los valores que cambian por entorno —y los secretos— fuera del workflow, hacia el entorno donde corre. Con la imagen del secreto que no se escribe en el guion sino que se pasa en un sobre grabaste la regla: el workflow lleva el marcador, el entorno provee el valor. Conociste $env, la expresión que lee una variable del .env del entorno dentro del campo de un nodo (no dentro del nodo Code, y permitida por defecto en self-hosted según N8N_BLOCK_ENV_ACCESS_IN_NODE), y la usaste para que la URL del CRM de order-triage cambie sola entre dev y prod sin tocar el JSON. Distinguiste $env de las Variables de n8n ($vars), que son de pago, y viste la equivalencia para migrar si algún día pagas. Y enfrentaste con honestidad los secretos externos (Vault, AWS, Azure, GCP): una feature Enterprise que agrega centralización, auditoría y rotación, cuyo equivalente esencial en Community es tu .env por entorno fuera del repo —gratis y suficiente para empezar—.

Antes de avanzar deberías poder: explicar por qué un valor que cambia por entorno no se escribe dentro del workflow; escribir una expresión con $env; decir en qué plan están $vars y los secretos externos, y cuál es el equivalente Community; y recordar que hay que reiniciar el entorno tras cambiar el .env.

La lección 7 sube al último nivel de honestidad de planes del módulo. Ya viste, feature por feature, qué es gratis y qué se paga. Ahora vas a mirar de frente la feature nativa de entornos de n8n —los entornos y el control de versiones integrados en la interfaz, que son de pago— y vas a recibir una matriz de decisión honesta: cuándo el patrón self-hosted a costo cero de esta guía es más que suficiente, y cuándo el tamaño del equipo, el presupuesto o el cumplimiento hacen que valga la pena pagar. Y verás cómo sería la migración de self-hosted a Cloud si tu equipo crece.

Recursos