Clarify: "loki.source.journal" "legacy_position" behavior when migrating from Promtail to Alloy (avoid duplicate logs)

I’m migrating from Promtail to Grafana Alloy for reading systemd journal logs and want to avoid duplicate log entries in Loki during an in‑place migration.
I’ve read the migration and component docs, but the behavior of loki.source.journal’s legacy_position block with a Promtail positions.yaml file isn’t fully clear to me:

Migration docs:

Current setup

I currently have Promtail scraping the systemd journal:


scrape_configs:
- job_name: journal
  journal:
    max_age: 12h
    labels:
      env: {{ env }}
      instance: {{ inventory_hostname.split('.')[0] }}
      type: journal
  relabel_configs:
  - source_labels: ['__journal_container_name']
    target_label: container_name
  - source_labels: ['__journal__systemd_unit']
    regex: '(loki|promtail|node-exporter)\.service'
    target_label: service

Promtail’s positions file contains a journal cursor like:
cursor-journal: s=dcddee5661cb4e3ea558f754bff3d6fb;i=ff120;b=6b4b547c34e245f183cabbf196059460;m=107d7c7d6061;t=654fa564157de;x=ab7c87cba78afb6a

Planned Alloy config

I want to do an in‑place migration: stop Promtail, start Alloy, and continue from the same journal cursor without re‑reading the last 12h of logs.

My Alloy config for journal looks like:

discovery.relabel "journal" {
  targets = []

  rule {
    source_labels = ["__journal__systemd_unit"]
    regex         = "(loki|promtail|node-exporter|alloy)\\.service"
    target_label  = "service"
  }
}

loki.source.journal "journal" {
  max_age = "12h0m0s"

  legacy_position {
    file = "/path/to/existing/promtail-position's-file/positions.yaml"
    name = "journal"
  }

  relabel_rules = discovery.relabel.journal.rules
  forward_to    = [loki.write.default.receiver]
  labels = {
    env      = "{{ env }}",
    instance = "{{ inventory_hostname.split('.')[0] }}",
    type     = "journal",
  }
}

The docs say:

“The translation of legacy position file will happens if there is no position file already and is a valid yaml file to convert.”[loki.source.journal: See this section, Not able to paste the url due to restriction from Grafna end]

But they don’t specify how this interacts with Promtail’s positions format and journal cursors.

Questions -

  1. Is Promtail’s positions.yaml officially supported as a “legacy” positions file for loki.source.journal?

    • Specifically, will a key like cursor-journal: <cursor> be recognized and used as the starting point for the Alloy journal reader?
  2. What does legacy_position.name need to match?

    • In my example I use name = "journal".
    • Should this match the Promtail job_name (journal), or some other identifier in the positions file?
    • Is there a mapping example for Promtail → Alloy for journal positions?
  3. Duplicate‑log behavior with max_age during migration

    • If I:
      1. Stop Promtail,
      2. Start Alloy with the config above (max_age = "12h0m0s" and legacy_position pointing to Promtail’s positions.yaml),
    • Will Alloy:
      • Always honor the imported journal cursor and not re‑read older entries within the max_age window, or
      • Potentially re‑read some portion of the last 12h, causing duplicates in Loki?
  4. Failure / fallback behavior

    • If the legacy positions file is valid YAML but doesn’t contain a usable journal cursor for the given name, what is the exact behavior?
      • Does Alloy start from “now”, from max_age back, or from the beginning of the journal?
      • Is there a log message I can look for to confirm that the legacy position was successfully imported?
  5. Recommended migration pattern

    • For an in‑place migration from Promtail to Alloy for journal logs, is the recommended pattern:
      • Stop Promtail,
      • Start Alloy with legacy_position configured,
      • Verify in logs/metrics that the legacy position was imported,
      • Then keep max_age as before (12h),
        or is there a better/safer pattern to avoid duplicates?

Any clarification or a small end‑to‑end example (Promtail positions.yamlloki.source.journal with legacy_position) would be very helpful. I’m mainly trying to ensure that I don’t generate duplicate journal entries in Loki during the migration.

Thanks!