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

3. Un Docker Compose por entorno

Descripción

Al terminar esta lección vas a poder definir dev, staging y prod como tres stacks aislados con Docker Compose, entendiendo qué mecanismo exacto los mantiene separados (el nombre del proyecto de Compose), qué recursos —contenedores, volúmenes, redes, base de datos, puertos— tiene cada uno para sí solo, y qué no se comparte jamás entre entornos y por qué. Vas a conocer las convenciones de nombres y de puertos que evitan que confundas un entorno con otro, y vas a dejar armado, dentro de cumbre-automations, el esqueleto environments/{dev,staging,prod}/ que el resto del módulo va a llenar.

Esto importa porque es el corazón técnico del módulo: el aislamiento. Todo lo que prometimos en la lección 1 —un cuarto acolchonado para equivocarte, prod intocable mientras pruebas— depende de que los tres entornos estén de verdad separados, no separados "de nombre". Un aislamiento a medias es peor que ninguno, porque te da una falsa sensación de seguridad: crees que pruebas en dev y en realidad estás tocando datos de prod. Esta lección te enseña a construir el aislamiento real y a verificar que lo es.

Conexión con el módulo: la lección 2 te dio la base —un stack reproducible— y las piezas (contenedor, volumen, red, puerto). Esta toma ese único stack y lo multiplica por tres, aislados. La lección 4 le va a poner a cada entorno su .env con su clave de cifrado y sus URLs; la 5 y la 6, sus credenciales y secretos. Aquí construyes el esqueleto; las siguientes lo amueblan. Es el mismo patrón que en el Módulo 3: primero la estructura, luego el contenido. De hecho, la carpeta environments/ que armas aquí es la que ya adelantaste en la lección 5 del Módulo 3, cuando diseñaste el layout "que crece hacia los entornos".

Tres edificios con el mismo plano

Empecemos por la imagen que vamos a usar todo el módulo para pensar el aislamiento.

Imagina una constructora que levanta tres edificios de departamentos con el mismo plano arquitectónico. Los tres tienen la misma distribución: dos recámaras, un baño, la cocina en el mismo lugar. El plano es idéntico. Pero cada edificio está en una dirección distinta, tiene su propia instalación eléctrica, sus propias tuberías, su propio medidor de agua. Lo que pasa en el edificio de la calle A no afecta en nada al de la calle B: si en el A se tapa una tubería, en el B el agua corre igual. Comparten el plano; no comparten las instalaciones.

Eso es exactamente lo que vamos a construir. Los tres entornos —dev, staging, prod— nacen del mismo plano: el mismo docker-compose.yml, la misma definición de servicios. Por eso son iguales por construcción, y por eso order-triage se comporta igual en los tres. Pero cada uno tiene sus propias instalaciones: su propia base de datos, sus propios volúmenes, su propia red, su propio puerto de entrada. Un pedido de prueba que entra a dev se procesa contra la base de datos de dev, con los datos de dev, y no roza a prod ni por accidente.

La pregunta técnica es: ¿cómo logra Docker que tres stacks nacidos del mismo archivo no se pisen? La respuesta tiene un nombre, y es la idea central de la lección.

El mecanismo del aislamiento: el nombre del proyecto

Cuando levantas un stack con docker compose up, Docker Compose no crea los contenedores, volúmenes y redes con nombres sueltos. Los agrupa bajo un nombre de proyecto (project name), y les pone ese nombre como prefijo a todo lo que crea. Por defecto, el nombre del proyecto es el nombre de la carpeta donde está el docker-compose.yml.

Esto suena a un detalle administrativo, pero es el mecanismo del aislamiento. Míralo con un ejemplo. Si levantas el mismo stack bajo dos nombres de proyecto distintos —cumbre-dev y cumbre-prod—, Docker crea dos conjuntos completamente separados de recursos:

RecursoBajo el proyecto cumbre-devBajo el proyecto cumbre-prod
Volumen de la base de datoscumbre-dev_postgres_storagecumbre-prod_postgres_storage
Volumen de n8ncumbre-dev_n8n_storagecumbre-prod_n8n_storage
Contenedor de n8ncumbre-dev-n8n-1cumbre-prod-n8n-1
Red privadacumbre-dev_defaultcumbre-prod_default

Fíjate en lo que acaba de pasar: son los mismos servicios, con el mismo plano, pero cada recurso lleva el prefijo de su proyecto, así que son objetos distintos para Docker. El cumbre-dev_postgres_storage y el cumbre-prod_postgres_storage son dos cajones de disco completamente separados. Un dato que escribes en la base de datos de dev va al primero; jamás toca el segundo. El aislamiento no es una promesa ni una configuración delicada: es una consecuencia directa de que cada entorno vive bajo su propio nombre de proyecto.

Volviendo a los edificios: el nombre del proyecto es la dirección del edificio. "Calle A número 10" y "Calle B número 20" son el mismo plano en dos direcciones, y por eso sus instalaciones no se cruzan. Cambiar el nombre del proyecto es cambiar de dirección, y con la dirección cambian todas las instalaciones.

Hay dos formas de fijar el nombre del proyecto, y conviene conocer las dos:

  • Por la carpeta: si pones el docker-compose.yml de dev en una carpeta llamada dev/, Compose usa dev como nombre de proyecto por defecto. Simple, pero frágil: dos carpetas dev/ en proyectos distintos chocarían.
  • Explícito, con COMPOSE_PROJECT_NAME: pones esa variable en el .env del entorno, y Compose usa ese nombre sin importar cómo se llame la carpeta. Es la forma que vamos a usar, porque es explícita y no depende de dónde esté el archivo. COMPOSE_PROJECT_NAME=cumbre-dev en el .env de dev deja clarísimo, y por escrito, bajo qué proyecto corre ese entorno.

Qué NO se comparte jamás entre entornos

Esta es la lista que tienes que grabar, porque cada elemento que se comparte por error es una grieta en el aislamiento. Entre dev, staging y prod nunca se comparte:

La base de datos. Cada entorno tiene su propia instancia de PostgreSQL, en su propio volumen. Es lo más importante: la base de datos es donde viven los workflows, las credenciales cifradas, los datos de las ejecuciones. Si dos entornos compartieran base de datos, un cambio en uno aparecería en el otro, y el aislamiento no existiría. Bases separadas, sin excepción.

Los volúmenes. Ya lo viste: los volúmenes llevan el prefijo del proyecto, así que son distintos por entorno. El volumen n8n_storage de dev guarda la clave de cifrado de dev; el de prod, la de prod. Compartir un volumen sería compartir esos secretos.

La clave de cifrado (N8N_ENCRYPTION_KEY). Cada entorno tiene la suya, distinta a propósito (lección 4). Una credencial cifrada en dev no se puede descifrar en prod porque las llaves son distintas. Esa imposibilidad es una pared de seguridad, no un estorbo.

Las credenciales. Las llaves reales del CRM de Cumbre solo viven en prod. dev y staging usan llaves sandbox. Compartir credenciales entre entornos significaría que una prueba en dev podría tocar el CRM real. Lección 5.

Los puertos. Dos contenedores en la misma máquina no pueden escuchar en la misma ventanilla. Cada entorno tiene su puerto: 5678 para dev, 5679 para staging, 5680 para prod. Lo descubriste tú solo en el ejercicio 2 de la lección anterior.

¿Y qué viaja de un entorno a otro? Una sola cosa, y de forma controlada: la lógica del workflow. order-triage se construye y prueba en dev, y cuando está listo se promueve a staging y luego a prod. Pero eso no es "compartir en vivo": es un traspaso deliberado, versionado, un paso a la vez. Es el tema del Módulo 6. En este módulo, los entornos están aislados; la promoción controlada viene después. Por ahora, la regla es simple: entre entornos no fluye nada solo. Lo único que se mueve, se mueve a mano y a propósito.

Las convenciones de nombres y puertos

Con tres entornos parecidos corriendo a la vez, el mayor riesgo cotidiano no es técnico: es humano. Es confundir un entorno con otro y correr en prod algo que creías que estabas corriendo en dev. Las convenciones existen para que esa confusión sea difícil.

Nombres de proyecto con prefijo claro. Todos empiezan con cumbre- y siguen con el entorno: cumbre-dev, cumbre-staging, cumbre-prod. El prefijo agrupa (sabes que son de Cumbre) y el sufijo distingue (sabes cuál es cuál). Nunca uses nombres genéricos como dev a secas: el día que tengas dos proyectos, dev de uno choca con dev del otro.

Puertos ordenados y memorizables. Una ventanilla por entorno, en orden:

EntornoNombre de proyectoPuerto en tu máquinaURL del editor
devcumbre-dev5678http://localhost:5678
stagingcumbre-staging5679http://localhost:5679
prodcumbre-prod5680http://localhost:5680

El puerto interno del contenedor sigue siendo 5678 en los tres —n8n siempre escucha ahí adentro—; lo que cambia es la ventanilla de tu máquina que apunta a cada uno. 5678, 5679, 5680: consecutivos, fáciles de recordar, imposibles de confundir una vez que los internalizas.

Una nota de disciplina que vale oro: cuando trabajes con prod, que tu propia cabeza tenga una señal de alerta. Muchos equipos ponen incluso el nombre del entorno visible en la interfaz de n8n (con un banner o un color distinto) justo para que nadie olvide dónde está parado. En Community puedes lograr algo parecido con el nombre de la instancia. El principio: haz que sea difícil confundir prod con lo demás, porque el error de "creía que estaba en dev" es el más caro y el más humano.

Ejemplo trabajado: montar los tres esqueletos

Vamos a armar la estructura de los tres entornos dentro de cumbre-automations. Recuerda: tú corres estos comandos; la guía no los ejecuta. El objetivo de este ejemplo es dejar el esqueleto listo; los valores concretos del .env (clave de cifrado, contraseñas) son de la lección 4, así que aquí solo dejamos los archivos de ejemplo.

Paso 1 — Crea la carpeta environments/ con un subdirectorio por entorno.

mkdir -p environments/dev environments/staging environments/prod

mkdir -p crea todas las carpetas de la ruta que falten. Qué esperar: dentro de cumbre-automations, una carpeta environments/ con tres subcarpetas: dev, staging, prod. Compruébalo con ls environments/.

Paso 2 — Escribe el docker-compose.yml del entorno. Este es el "plano" común. Va parametrizado con variables ${...} que cada entorno rellenará con su propio .env. Para el módulo usamos una versión ligera del stack —solo n8n y su base de datos—, que es lo esencial para el aislamiento. Guárdalo como environments/dev/docker-compose.yml:

# environments/dev/docker-compose.yml
# Plano de un entorno de Cumbre. Idéntico entre entornos:
# lo que cambia vive en el .env de cada uno.

volumes:
  n8n_storage:          # La carpeta ~/.n8n de ESTE entorno (clave de cifrado, config)
  postgres_storage:     # La base de datos de ESTE entorno

services:

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    volumes:
      - postgres_storage:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
      interval: 5s
      retries: 10

  n8n:
    image: n8nio/n8n:latest
    ports:
      - ${N8N_PORT}:5678          # La ventanilla de ESTE entorno (5678/5679/5680)
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}   # La clave de ESTE entorno
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=5678
      - WEBHOOK_URL=${WEBHOOK_URL}                 # La URL de webhooks de ESTE entorno
    volumes:
      - n8n_storage:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

Fíjate en el detalle que hace todo el trabajo: el archivo no tiene ni un solo valor concreto. Todo lo que distingue a un entorno de otro —el puerto, la clave de cifrado, la contraseña de la base, la URL— entra por una variable ${...}. El plano es el mismo; las diferencias las inyecta el .env. Por eso este mismo archivo sirve, tal cual, para los tres entornos.

Paso 3 — Copia el mismo plano a los otros dos entornos.

cp environments/dev/docker-compose.yml environments/staging/docker-compose.yml
cp environments/dev/docker-compose.yml environments/prod/docker-compose.yml

Qué esperar: cada carpeta de entorno tiene ahora una copia idéntica del docker-compose.yml. Y esto es correcto y deseado: el plano es el mismo. (Si te incomoda tener tres copias iguales, tienes razón en que hay técnicas para no duplicar —un solo archivo compartido con --env-file—; para aprender el patrón, tres copias explícitas se leen mejor y se rompen menos. Puedes consolidar más adelante.)

Paso 4 — Crea el .env.example de cada entorno, sin valores reales. Este archivo es el contrato de configuración: dice qué variables hace falta llenar, sin decir con qué. Guárdalo como environments/dev/.env.example:

# environments/dev/.env.example
# Copia este archivo a .env en la MISMA carpeta y rellena los valores de ESTE entorno.
# El .env con valores reales NO se sube al repo (ver .gitignore).

COMPOSE_PROJECT_NAME=cumbre-dev       # La "dirección" del entorno; distinta por entorno

N8N_PORT=5678                         # 5678 dev, 5679 staging, 5680 prod
N8N_HOST=localhost
WEBHOOK_URL=http://localhost:5678/

N8N_ENCRYPTION_KEY=                    # Genérala tú (lección 4); DISTINTA por entorno
POSTGRES_USER=cumbre
POSTGRES_PASSWORD=                     # Genérala tú; distinta por entorno
POSTGRES_DB=n8n

Qué esperar: un .env.example por entorno, con los nombres de las variables y comentarios, pero con los secretos (N8N_ENCRYPTION_KEY, POSTGRES_PASSWORD) vacíos. Este archivo se versiona; el .env real que crearás a partir de él, no. Repite el paso para staging y prod, ajustando COMPOSE_PROJECT_NAME, N8N_PORT, WEBHOOK_URL a los valores de cada uno.

Paso 5 — Asegúrate de que el .gitignore cubre todos los .env. Un .env puede aparecer en cualquiera de las tres carpetas de entorno, así que el patrón tiene que alcanzarlas a todas. En el .gitignore de la raíz de cumbre-automations:

# Cualquier .env en cualquier carpeta, incluidos los de environments/*/
**/.env
**/.env.*
!**/.env.example

El ** significa "en cualquier subcarpeta, a cualquier profundidad". Así, environments/prod/.env —el que tiene la clave real de producción— queda ignorado igual que un .env en la raíz. La excepción !**/.env.example rescata las plantillas, que sí van al repo. Qué esperar: al correr git status, ves los docker-compose.yml y los .env.example, pero ningún .env. Si ves un .env, detente: el patrón no está bien y estás a un commit de subir una clave.

Con esto, el esqueleto está montado: tres carpetas, cada una con su plano y su contrato, y el .gitignore blindando los secretos. Lo que falta —los valores reales de cada .env y levantar los tres a la vez— es de la lección 4 y del proyecto final.

Las redes también están separadas (y por qué importa)

Vale la pena detenerse un momento en un recurso que Compose crea sin que lo pidas y que refuerza el aislamiento: la red.

Cuando levantas un stack, Compose crea una red privada para él —recuerda de la lección 2 que dentro de esa red los servicios se encuentran por su nombre—. Y como todo, esa red lleva el prefijo del proyecto: cumbre-dev_default, cumbre-prod_default. Son redes distintas. La consecuencia es importante: el contenedor de n8n de dev y el de prod no están en la misma red, así que ni siquiera podrían hablarse aunque quisieran. El postgres de dev solo es alcanzable desde dentro de la red de dev; el n8n de prod no tiene forma de verlo.

Esto cierra una posible grieta. En la lección 2 viste que n8n encuentra su base de datos escribiendo simplemente postgres, sin IP. Podrías preguntarte: si los dos entornos tienen un servicio llamado postgres, ¿no se confundirían? No, porque cada postgres vive en la red de su propio proyecto, y el nombre postgres solo se resuelve dentro de esa red. El n8n de dev que busca postgres encuentra el de dev; el de prod, el de prod. Mismo nombre, redes distintas, cero confusión. El aislamiento de red es lo que hace que el mismo plano —con los mismos nombres de servicio— funcione en tres entornos sin que se crucen.

Ejemplo trabajado: verificar el aislamiento con comandos

Una cosa es confiar en que los entornos están aislados y otra es verlo. Docker te da comandos para inspeccionar los recursos y confirmar la separación con tus ojos. Recuerda: tú corres estos comandos; la guía no los ejecuta. Asume que ya levantaste cumbre-dev y cumbre-prod.

Ver los proyectos que corren:

docker compose ls

Qué esperar: una lista con los proyectos activos. Si cumbre-dev y cumbre-prod aparecen como dos entradas separadas, tienes dos entornos de verdad. Si esperabas dos y ves uno, los nombres de proyecto colisionaron (el error de más abajo).

Ver los volúmenes, con sus prefijos:

docker volume ls

Qué esperar: volúmenes con los dos prefijos —cumbre-dev_n8n_storage, cumbre-dev_postgres_storage, cumbre-prod_n8n_storage, cumbre-prod_postgres_storage—. Ver los cuatro, con prefijos distintos, es la prueba visual de que cada entorno tiene sus propios cajones de datos. No hay un volumen compartido entre los dos.

Ver las redes:

docker network ls

Qué esperar: entre las redes, cumbre-dev_default y cumbre-prod_default, separadas. Cada entorno, su propia red.

Estos tres comandos son tu "radiografía" del aislamiento. Cuando en el proyecto final tengas que demostrar que los entornos están separados, esta es la evidencia: tres proyectos, volúmenes con tres prefijos, tres redes. El aislamiento no es un acto de fe; se ve en la salida de estos comandos.

Por qué el mismo plano, y no un plano por entorno

Vale la pena detenerse en una decisión de diseño que a mucha gente le cuesta, porque va contra la intuición: el docker-compose.yml es el mismo en los tres entornos. La tentación es hacer un compose "de dev" más ligero, uno "de prod" más robusto, cada uno distinto. Resistir esa tentación es lo correcto, y aquí está el porqué.

Si dev y prod corren planos distintos, dejan de ser comparables. El sentido de tener staging es que sea lo más parecido posible a prod, para que si algo funciona en staging, funcione en prod. Si sus planos difieren, esa garantía se cae: un cambio podría funcionar en un plano y romperse en el otro, y descubrirías el problema justo donde no querías, en prod. La regla de oro de los entornos es paridad: que se parezcan tanto como se pueda, y que las únicas diferencias sean las que declaraste a propósito.

Hay un caso legítimo en el que un entorno difiere del plano común, y conviene nombrarlo para que la regla no suene absoluta: en dev a veces quieres servicios extra que en prod no van —por ejemplo, el contenedor de Ollama para probar IA local, o una herramienta de depuración—. Eso está bien, siempre que la diferencia sea aditiva y consciente: dev tiene lo mismo que prod más algo para desarrollar, no una versión distinta de lo mismo. La forma limpia de manejarlo, cuando llegue, es con perfiles de Compose (como el profiles: ["cpu"] que viste en el kit) o con un archivo de sobreescritura, de modo que el plano base siga siendo común y lo extra viva aparte. Lo que rompe la paridad no es agregarle a dev una herramienta de desarrollo; es que dev y prod corran el mismo servicio configurado distinto. Lo primero es sano; lo segundo es la trampa.

Por eso el plano es común y las diferencias viven, todas y solo, en el .env. Cuando alguien pregunta "¿en qué se diferencia dev de prod?", la respuesta no es "hay que comparar dos archivos de compose distintos"; es "abre los dos .env y compáralos". Todas las diferencias están en un solo lugar, explícitas, en una lista corta de variables. Eso es lo que hace que el sistema sea entendible y auditable. Un plano, muchos entornos; las diferencias, siempre en la configuración.

Errores comunes

Compartir un volumen o una base de datos entre entornos (conceptual, y rompe todo). Qué pasa: alguien, por ahorrar recursos, hace que dev y staging usen el mismo volumen de base de datos, o el mismo servicio de PostgreSQL. Al instante, un workflow que prueba en dev modifica datos que staging también ve: el aislamiento desapareció, aunque los contenedores de n8n sean distintos. Por qué pasa: correr tres bases de datos parece un desperdicio, y compartir "solo la base" suena inofensivo. Cómo detectarlo: si en dos docker-compose.yml de entornos distintos el volumen tiene el mismo nombre y corren bajo el mismo proyecto, o si apuntan al mismo servicio de base externo, están compartiendo. Cómo corregirlo: cada entorno, su propio proyecto (su propio COMPOSE_PROJECT_NAME), y por lo tanto sus propios volúmenes y su propia base. El aislamiento de datos no es negociable; es la razón de ser de los entornos.

Olvidar cambiar el puerto y chocar (práctico). Qué pasa: alguien copia el .env de dev a staging y se le olvida cambiar N8N_PORT. Al levantar staging, falla con "port is already allocated" porque dev ya tiene el 5678. Por qué pasa: el .env se copia entero y es fácil que un valor se quede sin ajustar. Cómo detectarlo: el error de puerto ocupado al levantar el segundo entorno es la señal inequívoca. Cómo corregirlo: asigna los puertos por convención (5678/5679/5680) y revisa el .env de cada entorno antes de levantarlo. Es de los errores más fáciles de arreglar: cambias un número y listo.

Usar el mismo COMPOSE_PROJECT_NAME para dos entornos (conceptual). Qué pasa: alguien deja COMPOSE_PROJECT_NAME=cumbre en los tres .env. Como el nombre de proyecto es lo que aísla, ahora los tres entornos comparten prefijo y Docker los trata como el mismo proyecto: el segundo up no crea un entorno nuevo, sino que reemplaza al primero. Por qué pasa: el nombre parece cosmético, así que se copia sin cambiarlo. Cómo detectarlo: corre docker compose ls; si esperabas tres proyectos y ves uno, los nombres colisionaron. Cómo corregirlo: un COMPOSE_PROJECT_NAME único por entorno —cumbre-dev, cumbre-staging, cumbre-prod—. El nombre del proyecto no es cosmético: es la dirección del edificio, y dos edificios no pueden tener la misma.

Hacer un docker-compose.yml distinto por entorno "para optimizar" (conceptual). Qué pasa: alguien arma un compose minimalista para dev y uno robusto para prod, con servicios o configuraciones distintas. Ahora staging deja de predecir a prod, porque no corren el mismo plano. Por qué pasa: la idea de "optimizar cada entorno" suena responsable. Cómo detectarlo: si comparar dev con prod requiere leer dos archivos de compose distintos en vez de dos .env, rompiste la paridad. Cómo corregirlo: un solo plano para los tres; las diferencias, todas en el .env. La paridad entre entornos vale más que la micro-optimización de cada uno.

Ejercicios

Ejercicio 1 — Predice los nombres de los recursos. Levantas el mismo docker-compose.yml bajo dos entornos con COMPOSE_PROJECT_NAME=cumbre-dev y COMPOSE_PROJECT_NAME=cumbre-prod. Para el volumen declarado como postgres_storage, escribe el nombre real que Docker le va a dar en cada entorno, y explica en una frase por qué eso garantiza que las dos bases de datos no se mezclan.

Ver solución

En dev: cumbre-dev_postgres_storage. En prod: cumbre-prod_postgres_storage.

Docker le pone al volumen el prefijo del nombre del proyecto, así que aunque el volumen se declare con el mismo nombre (postgres_storage) en el mismo plano, los dos entornos producen dos volúmenes distintos con nombres distintos. Un dato escrito en la base de dev va físicamente a cumbre-dev_postgres_storage, un cajón de disco separado del de prod. No se mezclan porque, para Docker, son objetos diferentes.

Por qué funciona: si predijiste bien los nombres, entendiste el mecanismo del aislamiento —el prefijo del proyecto—, que es la idea que sostiene toda la lección. El aislamiento no es una configuración frágil; es una consecuencia automática de correr cada entorno bajo su propio nombre de proyecto.

Ejercicio 2 — Clasifica qué se comparte y qué no. Para cada elemento, di si debe ser distinto por entorno o si puede ser el mismo en los tres, y por qué: (a) el docker-compose.yml; (b) la N8N_ENCRYPTION_KEY; (c) el volumen de la base de datos; (d) el puerto de tu máquina; (e) el JSON de order-triage; (f) COMPOSE_PROJECT_NAME.

Ver solución

(a) El mismo en los tres: es el plano común, y su igualdad es lo que garantiza la paridad entre entornos.

(b) Distinta por entorno: cada entorno cifra sus credenciales con su propia llave, para que un secreto de dev no se descifre en prod. Es una pared de seguridad.

(c) Distinto por entorno (por el prefijo del proyecto): cada entorno tiene su propia base de datos aislada. Compartirlo rompería el aislamiento de datos.

(d) Distinto por entorno: dos contenedores no pueden escuchar en la misma ventanilla de tu máquina; 5678/5679/5680.

(e) El mismo (la lógica): order-triage se versiona una vez y corre en los tres. Lo que cambia es su configuración, no su lógica.

(f) Distinto por entorno: es la "dirección" que aísla cada stack; dos entornos con el mismo nombre de proyecto se pisarían.

Por qué funciona: la clave es ver el patrón —lo que es lógica común (a, e) es igual; lo que es instancia física o secreto (b, c, d, f) es distinto—. Si acertaste los seis, ya tienes el mapa de qué comparten y qué no los entornos, que es lo que evita las grietas en el aislamiento.

Ejercicio 3 — Diseña los tres .env.example. Escribe las tres primeras líneas —COMPOSE_PROJECT_NAME, N8N_PORT y WEBHOOK_URL— tal como quedarían en el .env.example de cada uno de los tres entornos. Son nueve líneas en total (tres por entorno). Después explica por qué WEBHOOK_URL tiene que cambiar junto con el puerto.

Ver solución

Para dev:

COMPOSE_PROJECT_NAME=cumbre-dev
N8N_PORT=5678
WEBHOOK_URL=http://localhost:5678/

Para staging:

COMPOSE_PROJECT_NAME=cumbre-staging
N8N_PORT=5679
WEBHOOK_URL=http://localhost:5679/

Para prod:

COMPOSE_PROJECT_NAME=cumbre-prod
N8N_PORT=5680
WEBHOOK_URL=http://localhost:5680/

WEBHOOK_URL tiene que cambiar con el puerto porque es la dirección base que n8n usa para construir las URLs de los webhooks de sus workflows. Si el entorno se alcanza en el puerto 5679 pero WEBHOOK_URL sigue diciendo 5678, n8n le mostraría a los servicios externos una URL que apunta al entorno equivocado, y las llamadas entrantes caerían donde no deben o fallarían. La URL de webhooks y el puerto tienen que contar la misma historia.

Por qué funciona: escribir los tres a la vez te obliga a ver que las diferencias entre entornos son pocas, ordenadas y viven todas en el .env. Y la pregunta sobre WEBHOOK_URL te adelanta un tema de la lección 4 —las URLs propias de cada entorno—, que es donde más gente se equivoca al separar entornos.

Resumen y siguiente paso

En esta lección construiste el corazón del módulo: el aislamiento. Usaste la imagen de los tres edificios con el mismo plano —misma distribución, distintas instalaciones y direcciones— para entender que los tres entornos nacen del mismo docker-compose.yml pero no comparten nada de sus instalaciones. Descubriste el mecanismo exacto del aislamiento: el nombre del proyecto de Compose (COMPOSE_PROJECT_NAME), que Docker usa como prefijo de todos los recursos, de modo que cumbre-dev y cumbre-prod producen volúmenes, contenedores y redes distintos por construcción. Grabaste la lista de lo que nunca se comparte —base de datos, volúmenes, clave de cifrado, credenciales, puertos— y lo único que se mueve entre entornos, a mano y a propósito: la lógica del workflow. Adoptaste las convenciones de nombres (cumbre-dev/staging/prod) y puertos (5678/5679/5680), y montaste el esqueleto environments/{dev,staging,prod}/ con su plano común, su .env.example de contrato y el .gitignore que blinda todos los .env.

Antes de avanzar deberías poder: explicar cómo el nombre del proyecto de Compose produce el aislamiento; nombrar cinco cosas que nunca se comparten entre entornos; y decir por qué el docker-compose.yml es el mismo en los tres pero el .env es distinto.

La lección 4 amuebla ese esqueleto con lo que de verdad distingue a cada entorno: su .env. Vas a entender a fondo qué es un archivo .env, vas a generar una N8N_ENCRYPTION_KEY distinta por entorno —y a ver, con detalle, qué se rompe si la compartes o la cambias—, vas a configurar el WEBHOOK_URL y el host propios de cada uno, y vas a cerrar el reflejo de seguridad del módulo: el .env fuera del repositorio, el .env.example dentro como contrato. Es la lección donde las claves se vuelven reales.

Recursos