Cómo migrar de Promtail a Grafana Alloy con Docker sin perder logs
Índice de contenidos
- Puntos clave
- Por qué tienes que migrar de Promtail ahora
- La configuración de Promtail que migré
- Qué genera alloy convert a partir de tu configuración
- Qué no convierte bien alloy convert
- El cambio silencioso de multiline
- Cómo comparar Promtail y Alloy en paralelo sin mezclar datos
- Cómo conservar las posiciones para no reenviar logs
- Cómo hacer el corte y demostrar que no se pierde ni se duplica ninguna línea
- Qué alternativas tienes si no quieres pasar a Alloy
- Preguntas frecuentes
- ¿Puedo seguir usando Promtail después del 2 de marzo de 2026?
- ¿Puedo arrancar Alloy con mi promtail.yaml sin convertirlo?
- ¿Alloy usa el mismo fichero de posiciones que Promtail?
- Conclusión
- Fuentes
Promtail dejó de tener soporte el 2 de marzo de 2026 y su sustituto es Grafana Alloy. El comando alloy convert traduce la configuración y hereda el fichero de posiciones, pero cambia multiline sin avisar y relee el historial de los contenedores Docker. Aquí lo migro y compruebo con LogQL que no se pierde ni se duplica ninguna línea.
Promtail dejó de tener soporte el 2 de marzo de 2026, y si aún envía tus logs a Loki, cada semana que pasa lo hace sin parches. Grafana Alloy es su sustituto y trae alloy convert, que traduce la configuración de Promtail en un paso. El 28 de septiembre de 2026 migré con él un Promtail 3.6.11 con ficheros, contenedores Docker y pipelines de verdad a Alloy 1.20.0, sobre Loki 3.7.8. Aquí tienes lo que convierte bien, lo que cambia sin avisar y cómo hacer el corte sin perder ni duplicar una sola línea. También está la versión en inglés de esta guía.
Puntos clave
- Grafana declara Promtail en fin de vida desde el 2 de marzo de 2026, y la última imagen publicada en Docker Hub es la 3.6.11, del 13 de mayo de 2026.
alloy convert --source-format=promtailtradujo mi configuración sin errores y añadió por su cuentalegacy_positions_file, así que Alloy continúa los ficheros donde los dejó Promtail.- La conversión cambió
multilineen silencio: Promtail corta los bloques a 128 líneas y elmax_lines = 0que escribe el conversor no los corta nunca (7 entradas frente a 4 en mi prueba). loki.source.dockerno hereda posiciones: al arrancar, Alloy volvió a leer el historial entero de cada contenedor que Promtail ya había enviado.- Con una etapa temporal
stage.dropdeolder_than = "1m"y la comprobación por número de secuencia, el corte terminó con 1295 y 1298 líneas en Loki, 0 duplicadas y 0 perdidas.
Por qué tienes que migrar de Promtail ahora
La fecha ya pasó. La página de Promtail en la documentación de Loki[1] abre con este aviso: "Promtail is end of life (EOL) as of March 2, 2026. Commercial support has ended. No future support or updates will be provided." La misma página añade que, si usas Promtail, tienes que migrar a Alloy o a otro cliente compatible. La excepción es lambda-promtail, el cliente para AWS Lambda, que queda fuera del fin de vida.
En Docker Hub, el repositorio grafana/promtail[2] tiene como última etiqueta la 3.6.11, publicada el 13 de mayo de 2026 para amd64, arm64 y arm. Loki, en cambio, sigue sacando versiones: la 3.7.8 salió el 17 de septiembre. Cada parche nuevo de Loki amplía la distancia con un agente que ya no se prueba contra él.
El sustituto es Grafana Alloy[3], un recolector que además de logs recoge métricas, trazas y perfiles. Su configuración no es YAML sino un lenguaje propio de componentes conectados entre sí. La guía oficial de migración desde Promtail[4] propone dos caminos: convertir la configuración con alloy convert o arrancar Alloy leyendo el YAML de Promtail con --config.format=promtail como paso transitorio.
La configuración de Promtail que migré
Monté el laboratorio con Docker 29.5.2 en una máquina aarch64: Loki 3.7.8, Grafana 13.2.2, Promtail 3.6.11 y Alloy v1.20.0, todo desde Docker Hub. Dos generadores escriben una línea con un número de secuencia (seq) cada 0,2 s. Uno escribe en un fichero /logs/app.log y el otro, un contenedor llamado b6p7-web, escribe JSON por su salida estándar.
Una de cada diez líneas tiene level=debug y el pipeline la descarta a propósito. Si partes de cero, la guía para instalar Loki con Docker monta la base.
La primera mitad de promtail.yaml fija el fichero de posiciones, el destino y un trabajo que lee ficheros con cuatro etapas:
positions:
filename: /positions/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
external_labels:
host: lab01
scrape_configs:
- job_name: app-files
static_configs:
- targets: [localhost]
labels:
job: app-files
__path__: /logs/*.log
pipeline_stages:
- regex:
expression: '^(?P<ts>\S+) level=(?P<level>\w+) seq=(?P<seq>\d+)'
- timestamp:
source: ts
format: RFC3339
- labels:
level:
- drop:
source: level
value: debug
La etapa regex extrae la marca de tiempo y el nivel, y timestamp usa la hora de la línea en vez de la de lectura. Después, labels convierte el nivel en etiqueta de Loki y drop tira las líneas de depuración.
La segunda mitad descubre contenedores por la API de Docker, filtrados por una etiqueta para no recoger los de otros proyectos, y aplica una etapa json:
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
filters:
- name: label
values: ["logging=b6p7-lab"]
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: container
- source_labels: ['__meta_docker_container_log_stream']
target_label: stream
- target_label: job
replacement: docker
pipeline_stages:
- json:
expressions:
level: level
- labels:
level:
- drop:
source: level
value: debug
Los relabel_configs quitan la barra inicial del nombre del contenedor, guardan si la línea vino de stdout o stderr y fijan job="docker". Es la forma típica de un Promtail que recoge los logs de un servidor Docker.
Qué genera alloy convert a partir de tu configuración
alloy convert tradujo esta configuración sin un solo error. Lo ejecuté con la misma imagen de Alloy, montando el directorio de trabajo:
docker run --rm -u $(id -u) -v $PWD:/w grafana/alloy:v1.20.0 \
convert --source-format=promtail \
--report=/w/alloy/report.txt \
--output=/w/alloy/config.alloy /w/promtail/promtail.yaml
El comando terminó con código 0 y sin escribir nada en stderr. El informe de --report solo trajo dos avisos genéricos, que salen con cualquier configuración:
(Warning) If you have a tracing set up for Promtail, it cannot be
migrated to Alloy automatically. Refer to the documentation on how
to configure tracing in Alloy.
(Warning) Alloy's metrics are different from the metrics emitted by
Promtail. If you rely on Promtail's metrics, you must change your
configuration, for example, your alerts and dashboards.
El segundo aviso importa si tienes alertas sobre métricas como promtail_sent_entries_total. En Alloy, las equivalentes se llaman loki_write_sent_entries_total, loki_source_file_read_lines_total y similares.
El trabajo de ficheros se convirtió en dos componentes, loki.process con las cuatro etapas y loki.source.file. Este es el segundo:
loki.source.file "app_files" {
targets = [{
__address__ = "localhost",
__path__ = "/logs/*.log",
job = "app-files",
}]
forward_to = [loki.process.app_files.receiver]
file_match {
enabled = true
}
legacy_positions_file = "/positions/positions.yaml"
}
La línea clave es la última. El conversor apuntó legacy_positions_file al fichero de posiciones de Promtail sin que se lo pidiera. Según la referencia de loki.source.file[5], Alloy convierte ese fichero a su formato solo si todavía no tiene uno propio.
El trabajo de Docker se tradujo en discovery.docker, discovery.relabel, loki.process y loki.source.docker, con las mismas reglas. La imagen de portada es la pestaña Graph de la interfaz de Alloy con ese pipeline cargado.
Qué no convierte bien alloy convert
Para ver los límites, pasé por el conversor un segundo YAML con opciones menos habituales: limits_config, tracing, server.log_level, etiquetas con variables de entorno y una etapa multiline. Salió con código 0 y cuatro avisos. Estos dos son nuevos, copiados tal cual:
(Warning) limits_config rate limiting settings (readline_rate,
readline_burst, readline_rate_drop) have been converted to stage.limit
stages in each loki.process component. Unlike Promtail's global rate
limiter, these limits apply independently per pipeline rather than
across all pipelines combined.
(Warning) The converter does not support converting the provided
server.log_level config: The equivalent feature in Alloy is to use the
logging config block to set the level argument.
El primero cambia el comportamiento: un límite de 500 líneas por segundo en Promtail pasa a ser 500 por cada pipeline, así que con tres trabajos el techo real se triplica. Lo peligroso, en cualquier caso, fue lo que cambió sin ningún aviso:
| Opción de Promtail | Qué escribió alloy convert | Qué hacer |
|---|---|---|
multiline sin max_lines |
max_lines = 0 |
Escribe max_lines = 128 o borra la línea |
host: ${HOSTNAME} |
La cadena literal "${HOSTNAME}" |
Usa sys.env("HOSTNAME") |
positions.sync_period |
Nada | Alloy no tiene ese ajuste en loki.source.file |
positions.ignore_invalid_yaml |
Nada | Revisa on_positions_file_error |
max_line_size_truncate |
stage.truncate con 256KiB |
Nada, la conversión es correcta |
Lo de las variables tiene una razón: Promtail solo las expande con -config.expand-env=true, que es una opción de línea de comandos. Intenté pasársela al conversor con --extra-args y respondió "Error: (Critical) extra arguments are not supported for the promtail converter: [-config.expand-env true]".
El cambio silencioso de multiline
En Promtail, un bloque multilínea se corta a las 128 líneas si no dices otra cosa. El conversor escribe max_lines = 0, y en el código de la etapa multiline de Alloy 1.20[6] el bloque se cierra cuando el contador de líneas iguala max_lines. Como el contador empieza en 1, con 0 no se cierra nunca por tamaño. Lo comprobé con un fichero de tres trazas de 200 líneas y una línea suelta:
| Agente y ajuste | Entradas en la salida |
|---|---|
Promtail 3.6.11, sin max_lines |
7 (cada traza partida en 128 + 72) |
Alloy 1.20.0, max_lines = 0 convertido |
4 |
Alloy 1.20.0, sin max_lines |
7 |
Si tus trazas de Java o Python superan las 128 líneas, tras la migración llegarán a Loki en una sola entrada mucho mayor. Revisa entonces el límite de tamaño de línea de Loki, max_line_size, antes del corte.
Cómo comparar Promtail y Alloy en paralelo sin mezclar datos
Antes de apagar nada, ejecuta Alloy junto a Promtail con una etiqueta extra que separe sus flujos. Añadí agent = "alloy-shadow" a external_labels en una copia de la configuración convertida y le di a ese Alloy su propio volumen de datos, sin acceso al fichero de posiciones de Promtail. En sus logs lo dice claro: "will not convert the legacy positions file as it is not valid or does not exist".
Para comparar, la gráfica se queda corta. Escribí verify.py, un script de 39 líneas que pide a la API de Loki todas las líneas de un selector. Extrae seq y cuenta duplicados y huecos, sin contar los múltiplos de 10 (las líneas de depuración descartadas). En LogQL, {agent=""} selecciona los flujos sin esa etiqueta, es decir, los de Promtail:
python3 verify.py '{job="docker", agent=""}'
python3 verify.py '{job="docker", agent="alloy-shadow"}'
Los dos flujos tenían el mismo conjunto de etiquetas, salvo agent: container, host, job, level, service_name y stream, y 0 duplicados y 0 huecos cada uno. La etiqueta service_name no la pone ningún agente: la añade Loki 3 al recibir los datos. Un detalle revelador: el Alloy en paralelo empezó en seq=1 en los dos trabajos, porque sin posiciones lee cada fichero desde el principio y cada contenedor desde su primera línea.
Cómo conservar las posiciones para no reenviar logs
Los ficheros heredan la posición y los contenedores no, y ahí está la principal dificultad del corte. Promtail guarda las dos cosas en el mismo YAML. Así estaba al pararlo:
positions:
/logs/app.log: "50578"
cursor-592b72106356...: "1790572232"
La primera entrada es un desplazamiento en bytes. La segunda es la posición del contenedor, y no es un desplazamiento sino una hora Unix en segundos enteros. En el primer corte monté ese volumen en solo lectura en la misma ruta, /positions, y Alloy dejó en sus logs "successfully converted legacy positions file to the new format". El fichero continuó exactamente en el byte 50578.
Con Docker fue distinto. La referencia de loki.source.docker[7] explica que el componente guarda sus posiciones en su propio directorio de datos. No tiene ninguna opción para leer las de Promtail ni para empezar por el final. La métrica loki_source_docker_target_entries_total marcó 1045 líneas leídas justo después del arranque: el historial completo del contenedor, que Promtail ya había enviado.
En aquel primer corte, Loki no guardó las copias: verify.py dio 0 duplicados. El bloque de cabecera de Loki[8] descarta una entrada con la misma marca de tiempo y el mismo contenido que otra del mismo flujo, y el iterador de consultas elimina también las repetidas. Es una red de seguridad, no un plan.
El propio código de Loki avisa de que pueden colarse duplicados si el flujo se ha eliminado por inactividad. Y si Alloy genera una sola etiqueta distinta, la copia cae en otro flujo.
La solución que usé es una etapa temporal al principio del loki.process de Docker que tire lo que ya es viejo:
loki.process "docker" {
forward_to = [loki.write.default.receiver]
stage.drop {
older_than = "1m"
drop_counter_reason = "promtail_cutover"
}
older_than descarta las líneas cuya marca de tiempo tenga más de un minuto. Elige un valor mayor que lo que vaya a durar el corte, o perderás las líneas escritas mientras ningún agente leía. Quita la etapa cuando Alloy haya guardado sus posiciones: si la dejas, un reinicio de Alloy después de una caída larga tiraría logs legítimos.
Cómo hacer el corte y demostrar que no se pierde ni se duplica ninguna línea
El corte son cinco pasos, y los ejecuté con un script, cutover.sh, que monta el laboratorio desde cero:
- Arranca Loki, los generadores y Promtail, y deja que Promtail envíe durante 180 s.
- Para Promtail con
docker compose stop, que guarda sus posiciones al salir. - Espera 10 s sin agente, para simular una ventana de corte real.
- Arranca Alloy con
final.alloyy el volumen de posiciones de Promtail montado en/positions. - Cuenta con LogQL y con
verify.py.
El servicio de Alloy en compose.yaml es corto. Lo que importa son los volúmenes:
alloy:
image: grafana/alloy:v1.20.0
command: ["run", "--server.http.listen-addr=0.0.0.0:12345",
"--storage.path=/var/lib/alloy/data",
"/etc/alloy/config.alloy"]
volumes:
- ./alloy/final.alloy:/etc/alloy/config.alloy:ro
- app-logs:/logs:ro
- promtail-positions:/positions:ro
- alloy-data:/var/lib/alloy/data
- /var/run/docker.sock:/var/run/docker.sock:ro
alloy-data guarda las posiciones nuevas: si lo pierdes, Alloy vuelve a leer todo. El socket de Docker va en solo lectura, pero da acceso completo a la API de Docker, así que trátalo como un permiso de administrador.
Con los generadores parados, esta consulta LogQL devuelve cuántas líneas hay en Loki para cada trabajo:
sum(count_over_time({job="docker"}[1h]))
El número esperado se calcula sin Loki: la secuencia más alta menos las líneas de depuración. Estos fueron los resultados del corte del 28 de septiembre de 2026:
| Trabajo | Secuencia máxima | Esperadas | En Loki | Duplicadas | Perdidas |
|---|---|---|---|---|---|
app-files |
1438 | 1295 | 1295 | 0 | 0 |
docker |
1442 | 1298 | 1298 | 0 | 0 |
Alloy leyó 1442 líneas del contenedor, su historial completo desde seq=1, y la etapa temporal tiró 773 con el motivo promtail_cutover. Las del último minuto antes del corte sí se reenviaron, y la tabla muestra que Loki no las guardó dos veces. En Grafana, la misma cuenta por minuto se mantiene plana al pasar de un agente a otro:

Si ya usas Grafana con Docker, repite esa consulta con tus propias etiquetas antes y después del corte. En producción no tendrás un número de secuencia, pero una aplicación que escriba a ritmo estable deja una gráfica igual de clara.
Qué alternativas tienes si no quieres pasar a Alloy
Alloy es el camino con menos trabajo si vienes de Promtail, porque es el único con un conversor. La documentación de Loki admite otros clientes: el driver de logging de Docker para Loki, Fluent Bit, Vector o el OpenTelemetry Collector enviando por OTLP. Ninguno lee tu promtail.yaml, así que con cualquiera de ellos reescribes los pipelines a mano.
Para una vista rápida de los logs en directo sin montar ningún agente, Dozzle lee el socket de Docker, pero no guarda histórico ni sustituye a Loki. Si quieres ver cómo encaja Alloy con Tempo y Mimir, el artículo sobre el stack de Grafana para observabilidad abierta lo repasa. Las métricas de Alloy también pueden ir a un Prometheus en Docker.
Preguntas frecuentes
¿Puedo seguir usando Promtail después del 2 de marzo de 2026?
Funciona, pero sin parches ni soporte. Grafana dice que no habrá más actualizaciones, y la última imagen en Docker Hub es la 3.6.11 de mayo de 2026. Cada vulnerabilidad nueva en sus dependencias se quedará sin corregir.
¿Puedo arrancar Alloy con mi promtail.yaml sin convertirlo?
Sí, con alloy run --config.format=promtail. Lo probé con la misma configuración: arrancó y empezó a leer /logs/app.log, y el descubrimiento de Docker falló solo porque en esa prueba no monté el socket. La guía oficial lo presenta como paso transitorio y desaconseja en producción la opción que salta los errores de conversión, --config.bypass-conversion-errors.
¿Alloy usa el mismo fichero de posiciones que Promtail?
No. Guarda las suyas dentro de --storage.path, una por componente. Para ficheros puede importar las de Promtail con legacy_positions_file; para contenedores Docker no hay importación y empieza desde el principio del log.
Conclusión
alloy convert hace bien el trabajo pesado: tradujo mis dos trabajos, sus relabels y sus pipelines sin errores, y conectó el fichero de posiciones de Promtail por su cuenta. Lo que no te dice es lo que más duele en producción: multiline pierde el límite de 128 líneas, las variables de entorno quedan como texto literal y los contenedores Docker se releen enteros.
Mi recomendación: convierte, revisa la salida con la tabla de este artículo, ejecuta Alloy en paralelo con una etiqueta propia y haz el corte con una etapa older_than temporal. Después cuenta: si la secuencia máxima menos las líneas descartadas no coincide con lo que hay en Loki, algo se ha perdido o duplicado.
Fuentes: [1] Documentación de Loki: página de Promtail y aviso de fin de vida[1], [2] Guía de Alloy para migrar desde Promtail[4], [3] Referencia del comando convert de Alloy[9], [4] Notas de la versión 1.20.0 de Grafana Alloy[10], [5] Referencia de loki.source.file[5], [6] Referencia de loki.source.docker[7], [7] Referencia de loki.process[11], [8] Código de la etapa multiline de Alloy 1.20.0[6], [9] Código de la etapa multiline de Promtail 3.6.11[12], [10] Código de deduplicación de Loki 3.7.8[8], [11] Etiquetas de la imagen grafana/promtail en Docker Hub[2].
Fuentes
- página de Promtail en la documentación de Loki
- grafana/promtail
- Grafana Alloy
- guía oficial de migración desde Promtail
- referencia de loki.source.file
- código de la etapa multiline de Alloy 1.20
- referencia de loki.source.docker
- bloque de cabecera de Loki
- Referencia del comando convert de Alloy
- Notas de la versión 1.20.0 de Grafana Alloy
- Referencia de loki.process
- Código de la etapa multiline de Promtail 3.6.11
Código fuente
Accede a todo el código fuente de este artículo en GitHub.
Ver en GitHub