Runbook · P0 — OpenMetadata en una VM con Docker Compose
Fase P0 de
semantic-context-graph.md,
bajo el norteagentic-data-cloud.md.
Qué desbloquea: que OpenMetadata sea la fuente de verdad de la metadata del
ecosistema (decisión N2), construida desde la ingesta.Decisión de despliegue (tomada): una VM propia con
docker compose, no cuatro
servicios en Railway. Railway sigue alojando los servicios de Carbon (ml-runner,
karma-server, duck-server, Lakekeeper). Motivos en §0.⚠️ Convención de este runbook: lo marcado
[confirmar]son detalles que cambian
entre versiones de OM y se verifican contra el fichero descargado o la doc oficial en el
momento de ejecutar. Lo demás es alcance y decisión, que es lo que no cambia. Mismo
criterio quelakekeeper-standup.md.
0 · Por qué una VM y no Railway
Tres razones concretas, no de gusto:
- El parámetro del kernel. El buscador (OpenSearch/Elasticsearch) necesita
vm.max_map_count=262144— cuántas regiones de memoria puede mapear un proceso; Lucene mapea muchísimos ficheros. Es un ajuste del kernel del host, y en un PaaS normalmente no se puede tocar. En una VM es una línea. (Elvm.ahí significa «virtual memory», nada que ver con máquinas virtuales.) - La forma de la memoria. Son cuatro servicios con estado y suelos de memoria propios. En Railway pagas cuatro huellas reservadas; en una VM comparten la RAM de la máquina — el buscador aprovecha la caché que la BD no usa en ese instante. Para una carga a ráfagas como la ingesta, compartir es mucho más eficiente.
- El compose ya existe. OM publica el suyo. En una VM se usa casi tal cual; en Railway habría que despiezarlo en cuatro servicios y recablearlo a mano.
0.1 · Airflow entra en el stack, y su UI no se expone
Se evaluó ejecutar los conectores en los workers de Carbon (el paquete
openmetadata-ingestion sobre ml-runner) para ahorrarse el orquestador. Decisión: no.
El flujo se monta sobre OpenMetadata — conectores a 130+ servicios, modelo de entidades
dirigido por esquema, linaje y gestión de ingestas ya resueltos. Reescribir eso no es
producto.
Lo que esa decisión no cambia: el usuario nunca ve Airflow. Las ingestas se crean desde el asistente de nueva fuente que Carbon ya tiene (Data Gateway), que llama a la API de OM. Airflow es el ejecutor, no una superficie: su UI se queda dentro de la red de Docker y no se publica.
Salida de emergencia: si el buscador da guerra operativa, la ruta siguiente es alquilarlo gestionado (Bonsai / Elastic Cloud / AWS OpenSearch) y dejar en la VM sólo servidor + BD + ingesta. No hay que rehacer nada: son variables de entorno.
1 · La máquina
| Recomendado | Por qué | |
|---|---|---|
| Ejemplo | Hetzner CCX23 · GCP e2-standard-4 | en Hetzner US la línea dedicada sale más barata que la compartida para la misma RAM — comprobado en consola |
| vCPU / RAM | 4 dedicados / 16 GiB | los cuatro servicios en marcha; ver §2 |
| Disco | 160 GB SSD | índice + BD + imágenes Docker + logs |
| SO | Ubuntu 24.04 LTS | los repos de Docker y la doc de OM están probados contra ella |
Los cuatro servicios, incluido el orquestador. Airflow entra en el stack por decisión de producto (§0.1): el flujo se monta sobre OpenMetadata. Eso fija el tamaño — sin orquestador cabría en 8 GB, con él no.
vCPU dedicado, no compartido. Sin steal time, las pausas del recolector de basura de las dos JVM (buscador y servidor) son predecibles. En vCPU compartido, un vecino ruidoso se traduce en timeouts de ingesta que parecen bugs de OM y no lo son.
Redimensionar no es una puerta de un solo sentido: Hetzner Rescale (coger la opción de CPU y RAM sin tocar el disco — ampliar disco no se deshace) o, en GCP, parar → cambiar tipo → arrancar.
Cualquier proveedor con VM sirve (Hetzner, DigitalOcean, EC2, Compute Engine). No un plan «compartido» o de tipo contenedor: hace falta el kernel.
Por qué 16 GiB y no los ~40 de los mínimos publicados
Los números oficiales son de producción, para el patrimonio de metadata de una empresa entera y con el buscador en clúster replicado (1 master + 2 workers). Ninguna de las dos cosas aplica el día uno. Lo que recortamos, y lo que cuesta:
| Recorte | Ahorro | Lo que se paga |
|---|---|---|
| Buscador de un solo nodo | ~2/3 de su memoria | sin réplica: si se corrompe el índice, se reindexa desde la BD — se pierde el descubrimiento mientras dura, no la metadata |
| Perfilado apagado en los conectores | mucho de la ingesta | sin estadísticas de columna (nulos, distintos, mín/máx). No afecta a esquema ni linaje |
| Concurrencia de ingesta 1–2 | memoria a ráfagas | los conectores van en fila, tardan más |
Estos ~10–12 GiB son mi estimación, no un dato publicado. Se mide en §7, y si se queda corta se sube la VM — que es precisamente lo fácil en esta ruta.
2 · Preparar el sistema
# 1. Docker Engine + plugin compose (script oficial)
curl -fsSL https://get.docker.com | sh
# 2. Usuario no-root operando docker (cerrar sesión y volver a entrar después)
sudo usermod -aG docker "$USER"
# 3. El parámetro del kernel que necesita el buscador — PERSISTENTE
echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-opensearch.conf
sudo sysctl --system
# 4. Verificar que quedó puesto
sysctl vm.max_map_count # debe decir 262144
El paso 3 en /etc/sysctl.d/ y no sysctl -w a secas: un -w se pierde al reiniciar y el
buscador no arrancaría después del primer reboot, con un error que no apunta a la causa.
Cortafuegos
Ningún puerto de OM abierto al mundo. Sólo 22 (SSH), 80 y 443 (el proxy inverso de §4). Los 8585/8586 del servidor, la BD y el buscador quedan dentro de la red de Docker.
3 · El compose
Los ficheros ya están escritos en
infra/openmetadata/:
docker-compose.override.yml,env.exampleyCaddyfile, con su README de uso. Se
copian a la VM tal cual. Lo que sigue explica por qué son así — que es lo que hay que
entender antes de tocarlos.
mkdir -p ~/openmetadata && cd ~/openmetadata
# Fijar la versión. 1.13.3 es la estable al escribir esto — [confirmar] la vigente en
# https://github.com/open-metadata/OpenMetadata/releases antes de bajar nada.
export OM_VERSION=1.13.3
# El compose oficial. [confirmar] el nombre exacto del fichero en la release: hay variante
# MySQL y variante PostgreSQL, y el nombre ha cambiado entre versiones.
wget "https://github.com/open-metadata/OpenMetadata/releases/download/${OM_VERSION}-release/docker-compose-postgres.yml"
Elegimos la variante PostgreSQL (OM soporta PG ≥ 15 y MySQL ≥ 8.0.42). No por rendimiento —dan igual a esta escala— sino porque el equipo ya opera Postgres en todas partes: cuando algo falle a las tres de la mañana, la diferencia la marca saber leer el log.
⚠️ Esta BD es exclusiva de OpenMetadata. No es el Index, ni el PG de Lakekeeper. Son
tres metastores distintos y mezclarlos es cómo se pierde la capacidad de restaurar uno
sin arrastrar a los otros.
Los cuatro cambios del arranque reducido
Sobre el compose descargado, cuatro ediciones. Los nombres exactos de servicio y de algunas
variables [confirmar] contra el fichero:
a) Buscador de un nodo. En el servicio del buscador: discovery.type=single-node, y
bajar réplicas a 0. Sin esto intentará formar clúster, no encontrará pares y se quedará en
amarillo/rojo esperando quórum.
b) Heaps acotados. Una JVM crece hasta donde le dejes; si no las acotas, buscador y servidor se pelean por la RAM de la máquina y el que pierde muere por OOM.
# buscador
OPENSEARCH_JAVA_OPTS: "-Xms2g -Xmx2g" # [confirmar] la var de la imagen que use el compose
# servidor OM
OPENMETADATA_HEAP_OPTS: "-Xms2g -Xmx2g" # [confirmar]
Regla del buscador: heap = la mitad de lo que le asignes; la otra mitad debe quedar libre para la page cache del sistema, que es donde Lucene lee los segmentos.
c) Techos de memoria por servicio, para que un servicio desbocado no se lleve la máquina:
deploy:
resources:
limits:
memory: 4g # buscador 4g · servidor 4g · BD 2g · ingesta 3g
d) Nada de puertos publicados al host salvo el del servidor, y ése sólo en localhost — el proxy de §4 es quien lo expone:
ports: !override
- "127.0.0.1:8585:8585"
⚠️ El !override no es decorativo. Compose apenda las listas de ports al
fusionar ficheros en vez de sustituirlas: sin el tag te quedas con el mapeo público del
fichero base además del tuyo, y OM queda escuchando en 0.0.0.0 sin que nada avise.
Comprobación obligatoria tras el primer up:
sudo ss -tlnp | grep -E '8585|9200|5432' # sólo debe salir 127.0.0.1:8585
Si tu versión de Compose no soporta !override, edita los ports en el fichero base y
deja constancia de esa edición — es la única que sobrevive a un cambio de versión sin
avisar.
Secretos
Cambiar todas las contraseñas por defecto (BD, buscador, Airflow) y la
fernet/clave de cifrado que OM usa para guardar credenciales de conectores. Van en un
.env junto al compose, con chmod 600, fuera de git. Esas credenciales dan acceso a
todas las fuentes del ecosistema: es el fichero más sensible de la máquina.
4 · Exponer a Carbon (proxy inverso + TLS)
Carbon corre en Vercel: son funciones sin IP de salida fija, así que no se puede cerrar por lista de IPs. La consecuencia es directa: OM necesita un endpoint HTTPS público, y el control de acceso real es su autenticación, no la red.
Caddy delante, que resuelve TLS solo:
om.nodeworkspace.com {
reverse_proxy 127.0.0.1:8585
}
Requisitos: un subdominio apuntando a la IP de la VM, y 80/443 abiertos (80 lo necesita Let's Encrypt para emitir).
No saltarse esto. OM sobre HTTP plano expone en claro el token de bot y las
credenciales de cada conector que se configure desde la UI.
3.1 · Lo que el compose de fábrica trae mal (medido, 1.13.3)
Está pensado para un quickstart en localhost. Colgado de un dominio público, cinco
defectos. Todos corregidos en el override — se listan para que nadie los reintroduzca al
subir de versión:
| # | Defecto de fábrica | Corrección |
|---|---|---|
| 1 | POSTGRES_PASSWORD: password literal (no variable) y 5432 en 0.0.0.0 | contraseña generada en la VM + puerto cerrado |
| 2 | RSA_PRIVATE_KEY_FILE_PATH: ./conf/private_key.der — la clave de firma de JWT viene dentro de la imagen pública: con ella cualquiera forja un token de admin | par RSA propio en ./certs, montado :ro, + JWT_KEY_ID nuevo |
| 3 | AUTHENTICATION_ENABLE_SELF_SIGNUP: true con registro abierto a todos los dominios | false + dominio acotado |
| 4 | Elasticsearch con xpack.security.enabled=false y 9200/9300 publicados | puertos cerrados; sólo red interna de Docker |
| 5 | Airflow admin/admin con 8080 publicado | credenciales generadas + puerto cerrado |
Se deja a propósito sin tocar: AUTHORIZER_ENFORCE_PRINCIPAL_DOMAIN=false. Ponerlo a
true exigiría que todo usuario tuviera correo del dominio — incluido el admin inicial,
que es admin@open-metadata.org. Te dejaría fuera en el primer login. Es endurecimiento
posterior a crear tus propios usuarios.
No hay FERNET_KEY en 1.13.x. El cifrado de credenciales de conectores va por
SECRET_MANAGER: db ⇒ la propia base de datos es el secreto. El pg_dump del §8 deja
de ser higiene y pasa a ser lo único que hay.
3.2 · Tres trampas que cuestan una tarde
Encontradas ejecutando esto de verdad. Las tres fallan sin decir por qué:
- La contraseña del usuario de la BD no se puede cambiar sólo por env. La imagen
openmetadata/postgresqlcreaopenmetadata_usercon una contraseña horneada en su script de inicialización. CambiarDB_USER_PASSWORDen el.envcambia lo que el servidor usa para conectar, no lo que la base tiene →password authentication failed. Hay que alinear la base después del primer arranque:docker exec -e PGPASSWORD="$PGPW" openmetadata_postgresql \ psql -U postgres -d openmetadata_db -c "ALTER USER openmetadata_user WITH PASSWORD '$DBPW';" - Los certs los tiene que poder leer el UID del contenedor, y el DIRECTORIO también.
El servidor corre como UID 1000; en GCP el usuario de la VM es 1001. Y no basta
con
chownde los ficheros: sicerts/quedó en700(unumask 077al crearlo), el contenedor no puede entrar aunque los ficheros ya sean suyos. Síntoma:AccessDeniedExceptiony el servidor en bucle de reinicio.sudo chown 1000:1000 certs certs/*.der && sudo chmod 750 certs # y verifícalo DE VERDAD, con el UID real: docker run --rm -v ~/openmetadata/certs:/etc/openmetadata/certs:ro \ --entrypoint sh <imagen-server> -c 'head -c8 /etc/openmetadata/certs/private_key.der >/dev/null && echo OK' execute-migrate-alles un quinto servicio que no aparece en la documentación de arranque. Es un job de un solo uso que corre las migraciones y siembra los bots con sus tokens ⇒ necesita las mismas claves RSA que el servidor. Si no se las montas, los bots quedan firmados con las claves de fábrica y el endurecimiento nº2 es papel mojado.
5 · Arranque y gates
docker compose --env-file ./.env -f docker-compose-postgres.yml up --detach
docker compose -f docker-compose-postgres.yml ps
docker compose -f docker-compose-postgres.yml logs -f openmetadata-server # [confirmar] nombre
El primer arranque tarda: el servidor corre sus migraciones de esquema antes de escuchar.
Gates de §5:
docker ps— los cuatro contenedores arriba y estables pasados 5 minutos (no basta con que arranquen: el buscador puede morir por OOM justo después).https://om.nodeworkspace.comcarga la UI.- Login con el admin inicial y cambio inmediato de su contraseña.
[confirmar]las credenciales por defecto en la doc de la versión.
6 · Token de lectura para Carbon
Por la decisión N2-bis, Carbon escribe en OM configuración (servicios, pipelines de ingesta) pero nunca metadata (glosario, descripciones, clasificaciones). Esa frontera tiene que sostenerse por permisos, no por buena voluntad del código:
Procedimiento verificado (1.13.3). Cuatro llamadas, en este orden — cada una depende de la anterior:
A=http://127.0.0.1:8585/api/v1
H=(-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json")
# 1) Política — la frontera N2-bis expresada en reglas. En OM el DENY gana al ALLOW.
# resources válidos: consúltalos en GET /policies/resources
curl -X POST "${H[@]}" "$A/policies" -d '{
"name":"CarbonBotPolicy",
"description":"Lee toda la metadata; escribe solo configuracion.",
"rules":[
{"name":"carbon-read-all","resources":["all"],"operations":["ViewAll"],"effect":"allow"},
{"name":"carbon-write-config",
"resources":["databaseService","dashboardService","messagingService","pipelineService",
"mlmodelService","storageService","searchService","apiService",
"metadataService","driveService","ingestionPipeline"],
"operations":["Create","EditAll","Delete","Trigger"],"effect":"allow"},
{"name":"carbon-deny-metadata-authoring",
"resources":["glossary","glossaryTerm","tag","classification"],
"operations":["Create","EditAll","Delete"],"effect":"deny"}]}'
# 2) Rol
curl -X POST "${H[@]}" "$A/roles" \
-d '{"name":"CarbonBotRole","description":"...","policies":["CarbonBotPolicy"]}'
# 3) USUARIO bot — ⚠️ PRIMERO el usuario. `POST /bots` con un botUser inexistente
# devuelve 404 "user instance for X not found". `roles` va por UUID, no por nombre.
curl -X PUT "${H[@]}" "$A/users" -d '{
"name":"carbon","email":"carbon@nodeworkspace.com","isBot":true,
"authenticationMechanism":{"authType":"JWT","config":{"JWTTokenExpiry":"Unlimited"}},
"roles":["<UUID del rol>"]}'
# 4) Entidad bot
curl -X POST "${H[@]}" "$A/bots" -d '{"name":"carbon","botUser":"carbon","description":"..."}'
El JWT del bot se recupera de GET /users/token/{userId} — no de GET /bots/name/{name},
que devuelve 200 pero sin el token. Es el gotcha que cuesta el rato.
⚠️
Triggeres una operación aparte, y no la cubreEditAll. Se descubrió el
2026-08-02 con la primera ingesta real:POST …/ingestionPipelines→ 201,
POST …/deploy/{id}→ 200,POST …/trigger/{id}→ 403
Principal: CatalogPrincipal{name='carbon'} operations [Trigger] not allowed.
Crear el pipeline y desplegarlo sin poder ejecutarlo deja el producto a medias: Carbon
monta la ingesta desde su asistente y luego no puede lanzarla.Las operaciones válidas de un recurso se consultan —no se adivinan— en
GET /policies/resources. ParaingestionPipelineson:Create,Delete,ViewAll,
ViewBasic,EditAll,EditDescription,EditDisplayName,EditOwners,
EditGlossaryTerms,EditTier,EditIngestionPipelineStatus,Trigger,
CreateIngestionPipelineAutomator.El bot no puede arreglarse solo: sobre
policysólo tieneViewAll, así que esto se
corrige con elADMIN_TOKEN(o desde la UI). En una instancia ya montada:curl -X PATCH "$A/policies/name/CarbonBotPolicy" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H 'Content-Type: application/json-patch+json' \ -d '[{"op":"add","path":"/rules/2/operations/-","value":"Trigger"}]'⚠️ El índice de
/rules/Nno es el del POST original: OM reordena las reglas. Míralas
antes conGET /policies/name/CarbonBotPolicy?fields=rulesy usa el índice de
carbon-write-config. Añádelo también a la verificación: un cuartocurlque dispare
una ingesta y espere200, junto a los tres de 200/403/201.
El
ADMIN_TOKENse genera en la UI (avatar → Profile → Personal Access Token) y se
revoca al terminar: dura horas y tiene permisos totales. El token del bot, en cambio,
es permanente y acotado — ése es el que va a Carbon.
Verificación — las tres llamadas, y las tres importan:
OM=https://om.nodeworkspace.com
# 1) Lee metadata → 200
curl -s -o /dev/null -w 'lectura %{http_code}\n' \
-H "Authorization: Bearer $OM_TOKEN" "$OM/api/v1/glossaries"
# 2) Escribe metadata → 403. Si devuelve 201, N2-bis es papel mojado.
curl -s -o /dev/null -w 'glosario %{http_code}\n' -X POST \
-H "Authorization: Bearer $OM_TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"canary","description":"debe fallar"}' "$OM/api/v1/glossaries"
# 3) Escribe configuración → 201. Si devuelve 403, el asistente de nueva
# fuente de Carbon no podrá registrar nada y P1 se queda sin producto.
# [confirmar] el cuerpo exacto que pide `services/databaseServices`.
curl -s -o /dev/null -w 'servicio %{http_code}\n' -X POST \
-H "Authorization: Bearer $OM_TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"canary-svc","serviceType":"CustomDatabase","connection":{"config":{"type":"CustomDatabase","sourcePythonClass":"x"}}}' \
"$OM/api/v1/services/databaseServices"
Las dos últimas son las que definen la frontera. Un token que puede escribir glosario convierte la separación entre las dos fuentes de verdad en una convención — y las convenciones se rompen solas. Uno que no puede crear servicios rompe el producto.
Limpia el canario (DELETE …/services/databaseServices/name/canary-svc?hardDelete=true&recursive=true).
✅ Verificado en om-carbon-01 el 2026-08-02: 200 / 403 / 201.
Wiring en Carbon
OPENMETADATA_URL = https://om.nodeworkspace.com
OPENMETADATA_TOKEN = <JWT del bot de lectura>
OPENMETADATA_ENABLED = 0 # a 1 cuando P1 esté verde
El gate a 0 es deliberado: mientras esté apagado, la pantalla del Semantic Context Graph
debe degradar con un mensaje honesto («OpenMetadata no configurado»), no romper. Mismo
patrón de seam fail-safe que KARMA_SQL_URL.
7 · El primer conector: el Warehouse
Uno solo para empezar, y que sea el que sostiene la hidratación de P3: el Warehouse (Iceberg vía Lakekeeper).
- Configurar el conector con perfilado APAGADO (§1). Esquema y linaje sí; estadísticas de columna no, todavía.
- Concurrencia de ingesta 1–2.
- Ejecutarlo y ver aparecer las tablas del Warehouse en OM con su esquema.
Gate de §7 — las dos mitades son obligatorias:
- una tabla real del Warehouse es visible en OM con su esquema;
- su FQN es idéntico entre dos ejecuciones seguidas de la ingesta.
La segunda no es opcional. Todo el anclaje de Pods (N4) es por FQN: si una reingesta lo mueve, cada atributo de cada Pod queda apuntando a la nada. Mejor descubrirlo aquí, con una tabla, que en P3 con un catálogo entero.
Medida real — 2026-08-02, VM om-carbon-01 (GCP e2-standard-4, us-west1-b)
En reposo, los cuatro servicios arriba y sanos, sin ningún conector aún:
| Servicio | Uso | Techo | % |
|---|---|---|---|
elasticsearch | 2,64 GiB | 4 GiB | 66% |
ingestion (Airflow) | 2,07 GiB | 3 GiB | 69% ← el más apretado |
openmetadata-server | 1,12 GiB | 4 GiB | 28% |
postgresql | 0,13 GiB | 2 GiB | 6% |
| Máquina | 6,7 GiB usados | 15 GiB | 8,9 GiB disponibles |
Conclusión: la estimación de ~10–12 GiB de §1 era alta; el consumo real en reposo es ~6 GiB. Pero 8 GB seguiría siendo mala idea: dejaría ~1 GiB de holgura para el sistema y para los picos de ingesta, y el servicio más cercano a su techo es justamente Airflow, que es el que crece al ingerir. Los 16 GiB están bien elegidos.
Volver a medir con conectores corriendo — este número es el suelo, no el techo.
8 · Copias de seguridad
Aquí no hay red de seguridad de nadie. En Railway los backups del plugin de BD venían dados; en una VM, si no lo montas tú, no existe.
- Lo crítico es la BD de OM. Ahí vive la metadata y, cifradas, las credenciales de cada
conector.
pg_dumpdiario a un bucket (R2 sirve), con retención. - El buscador NO se respalda: se reindexa desde la BD. Es exactamente el trade-off que aceptamos en §1 al dejarlo en un nodo.
- Respaldar también el
.envy el compose editado, en un gestor de secretos — no en el mismo sitio que el dump. Restaurar la BD sin la clave de cifrado deja las credenciales de los conectores ilegibles.
Probar la restauración una vez. Un backup no verificado es una suposición.
9 · Qué vigilar
| Síntoma | Causa habitual | Qué hacer |
|---|---|---|
| El buscador reinicia en bucle | vm.max_map_count sin poner, o heap > memoria del contenedor | §2 paso 3; bajar -Xmx |
| La ingesta se queda colgada sin error | memoria; el worker murió por OOM | bajar concurrencia, confirmar perfilado off |
| La UI va lenta y las búsquedas no encuentran | índice degradado | reindexar desde la BD |
| Un conector falla de golpe tras semanas | credencial caducada en la fuente | rotarla en OM |
| Disco lleno | logs de Docker sin rotar | max-size/max-file en el daemon |
10 · Done-criteria de P0
- VM con Docker,
vm.max_map_countpersistente, cortafuegos cerrado salvo 22/80/443. - Compose de la versión pinneada arriba: servidor + PG + buscador (un nodo) + ingesta, estables pasados 5 minutos, con heaps y techos de memoria acotados.
- HTTPS por Caddy en un subdominio propio; nada de OM publicado en claro.
-
ss -tlnpconfirma que sólo127.0.0.1:8585escucha (el!overridese aplicó). - Contraseñas por defecto cambiadas;
.enven 600 y fuera de git. - Bot de Carbon con las tres comprobaciones verdes: 200 leyendo, 403 creando glosario, 201 creando servicio. Canario borrado.
- Conector del Warehouse ingiriendo, con FQN estable entre dos ejecuciones.
- Consumo real medido y anotado en §7.
-
pg_dumpprogramado y una restauración probada. -
OPENMETADATA_URL/_TOKENen Carbon conOPENMETADATA_ENABLED=0.
Cumplido esto, P1 (el read-through) es trabajo de código y ya no depende del operador.
11 · Rollback
Nada de P0 toca Carbon en producción: el gate OPENMETADATA_ENABLED=0 mantiene el
código inerte, y Carbon nunca escribe en OM, así que no hay estado externo que compensar.
Deshacerlo es docker compose down -v y destruir la VM. El -v borra los volúmenes —
metadata y credenciales de conectores incluidas. Ejecutarlo sólo con el dump de §8 en la
mano.