Migrating to Grafana Alloy from Promtail and Agent

Photo by Sm faysal via Wikimedia Commons (CC BY-SA 4.0)
Yes. Promtail entered long-term support in February 2025 and reached end-of-life on 2 March 2026. After that date it receives no bug fixes or security patches. Grafana's recommended replacement is Grafana Alloy, and it provides an automated conversion path via the alloy convert command.
Grafana Alloy is the successor to Grafana Agent and is a full distribution of the OpenTelemetry Collector, 100% OTLP-compatible. Grafana Agent (Static, Flow, and Operator) reached end-of-life on 1 November 2025, and all new feature development now happens only in Alloy. Alloy keeps Agent Flow's component model and configuration syntax, so migration from Flow is mostly a rename.
Use the built-in convert subcommand: run alloy convert with --source-format=promtail and an --output path pointing at your target config file. The converter targets the Promtail v2.8.x schema and works on a best-effort basis, printing diagnostics for anything it cannot map cleanly. Review the output and resolve those diagnostics by hand before restarting the service.
Alloy uses a component-based configuration syntax that originated under the name River, an HCL-like language. Instead of YAML pipeline blocks, you declare named components and wire them together with the forward_to argument, which points one component's output at another's receiver. The resulting reference graph is the pipeline itself.
Yes. Because Alloy is an OpenTelemetry Collector distribution, one process handles logs, metrics, traces, and profiles. The otelcol.* component namespace covers OTLP receivers, processors, and exporters, and it interoperates with native Loki and Prometheus components in the same config file, so you can consolidate several agents into one.

Photo by Sm faysal via Wikimedia Commons (CC BY-SA 4.0)
Key Takeaway
Grafana Alloy is Grafana's OpenTelemetry Collector distribution and the only supported successor: Grafana Agent reached end-of-life on 1 November 2025 and Promtail on 2 March 2026. Migrate with the built-in alloy convert command, then rewrite pipelines as connected components using the Alloy configuration syntax.
I run a small fleet of services on a self-hosted VPS, and for years the logging story was Promtail tailing Docker files and shipping to Loki. That story is over. Grafana consolidated Promtail, Grafana Agent Static, and Grafana Agent Flow into a single collector called Alloy, and then set end-of-life dates on everything it replaced. If you are still on Promtail or Agent, you are running unsupported software on borrowed time.
The good news: Alloy is not a rewrite from scratch. It is a genuine OpenTelemetry Collector distribution that is fully OTLP-compatible, so the same binary now handles logs, metrics, traces, and profiles through one config. This post is the migration I actually did — the tooling, the components, and where the automated converter leaves you to finish the work by hand.
The deadlines are the forcing function. Grafana Agent (Static, Flow, and the Operator) hit end-of-life on 1 November 2025, meaning no more security patches or bug fixes. Promtail entered long-term support in February 2025 and reached end-of-life on 2 March 2026. All new feature development happens in Alloy only. Beyond the deadlines, the real win is consolidation: instead of Promtail for logs plus a separate Collector for OTLP traces, one Alloy process runs the whole telemetry pipeline with a single config language.
Do not treat this as optional maintenance. Once a component is end-of-life, a single upstream CVE in a Go dependency has no patch coming. Running Promtail after March 2026 is a security decision, not a convenience one.
Promtail was declarative YAML. Alloy uses a configuration syntax that started life under the name River — an HCL-like language built around components. A component is a named block that does one job: read files, relabel, batch, write to a backend. Each component exposes arguments you set and exported values other components consume. You wire them into a pipeline with the forward_to argument, which points one component's output at the next component's receiver. That reference graph is the pipeline; there is no separate pipelines section like the upstream Collector YAML has.
// alloy: a minimal Loki logging pipeline
// discover local files, process, then write to Loki
local.file_match "applogs" {
path_targets = [{"__path__" = "/var/log/app/*.log", "job" = "app"}]
}
loki.source.file "applogs" {
targets = local.file_match.applogs.targets
forward_to = [loki.process.parse.receiver]
}
loki.process "parse" {
stage.json {
expressions = { level = "level", ts = "timestamp" }
}
stage.labels {
values = { level = "" }
}
forward_to = [loki.write.default.receiver]
}
loki.write "default" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}Do not hand-translate your existing config. Alloy ships a convert subcommand that reads your old Promtail, Agent Static, or Agent Flow file and emits equivalent Alloy syntax. For Promtail it targets the v2.8.x schema. The converter is best-effort: it prints diagnostics for anything it cannot map cleanly, and you resolve those manually. I always convert into a file first so I can read the diff before running anything.
# Promtail -> Alloy (write to a file, review the diagnostics)
alloy convert \
--source-format=promtail \
--output=/etc/alloy/config.alloy \
/etc/promtail/config.yaml
# Grafana Agent Flow -> Alloy
alloy convert --source-format=flow \
--output=/etc/alloy/config.alloy /etc/agent/config.river
# OpenTelemetry Collector -> Alloy
alloy convert --source-format=otelcol \
--output=/etc/alloy/config.alloy /etc/otelcol/config.yaml
# validate before restarting the service
alloy fmt /etc/alloy/config.alloyThis is where Alloy earns its description. Native Prometheus and Loki components (the loki.* and prometheus.* families) coexist with the full OTLP component set under the otelcol.* namespace. If you receive OTLP from an instrumented NestJS service, otelcol.receiver.otlp takes it, otelcol.processor.batch groups it, and otelcol.exporter.otlphttp ships it onward. Bridging is first-class: otelcol.exporter.loki converts OTLP logs into Loki entries, and otelcol.receiver.loki goes the other direction, so you can mix a legacy Loki push path with a modern OTLP path in one file.
// alloy: receive OTLP over gRPC, batch, export to an OTLP/HTTP backend
otelcol.receiver.otlp "default" {
grpc { endpoint = "0.0.0.0:4317" }
http { endpoint = "0.0.0.0:4318" }
output {
metrics = [otelcol.processor.batch.default.input]
logs = [otelcol.processor.batch.default.input]
traces = [otelcol.processor.batch.default.input]
}
}
otelcol.processor.batch "default" {
output {
metrics = [otelcol.exporter.otlphttp.backend.input]
logs = [otelcol.exporter.otlphttp.backend.input]
traces = [otelcol.exporter.otlphttp.backend.input]
}
}
otelcol.exporter.otlphttp "backend" {
client { endpoint = "https://otlp.example.com" }
}Most of the migration is learning which Alloy component replaces each old concept. This is the cheat sheet I kept open the whole time.
| Old concept | Alloy component | Role |
|---|---|---|
| Promtail scrape_configs (file) | loki.source.file | Tail log files by path |
| Promtail pipeline_stages | loki.process | Parse, relabel, extract fields |
| Promtail clients | loki.write | Push entries to Loki |
| Collector otlp receiver | otelcol.receiver.otlp | Ingest OTLP over gRPC or HTTP |
| Collector batch processor | otelcol.processor.batch | Group telemetry before export |
| Agent metrics scrape | prometheus.scrape | Pull Prometheus metrics |
Run the old and new collectors side by side into the same Loki, tagged with different job labels, for a few days. Compare line counts before you decommission Promtail. Converters get labels subtly wrong, and log gaps are invisible until you need the logs.
On my VPS everything is Docker Compose, so Alloy is just another service. Mount the config, expose the debug UI locally, and point it at the same log volumes Promtail used. The run flag matters: --stability.level for non-stable components, and the config path last.
# docker-compose.yml (excerpt)
services:
alloy:
image: grafana/alloy:latest
command:
- run
- --server.http.listen-addr=0.0.0.0:12345
- --storage.path=/var/lib/alloy/data
- /etc/alloy/config.alloy
ports:
- "127.0.0.1:12345:12345" # debug UI, localhost only
volumes:
- ./config.alloy:/etc/alloy/config.alloy:ro
- /var/log:/var/log:ro
- alloy-data:/var/lib/alloy/data
volumes:
alloy-data: {}That is the whole migration in shape: convert mechanically, learn the component graph, verify labels against real traffic, then cut over. The payoff is one agent instead of three, a single config language for logs and traces and metrics, and software that will still receive patches next quarter.