Deployment guide

Grafana Alloy on a VPS: install it and migrate from Promtail

Deploy on a VPS Cloud →

Tutorial

Grafana Alloy on a VPS: install it and migrate from Promtail

Security & Monitoring12 min read15 steps

Promtail no longer receives any updates: Grafana has declared it end of life since March 2, 2026. If your VPS still ships its logs to Loki through that agent, any vulnerability found tomorrow will stay open. Grafana Alloy takes its place and also collects system metrics, which removes one more exporter from your server. This guide installs Alloy as a systemd service, connects it to Loki and Prometheus, migrates an existing Promtail configuration and handles the permission errors that Alloy does not always report.

Contents· Why replace Promtail with Alloy now1/13
  1. 01Why replace Promtail with Alloy now
  2. 02What a single agent changes on your server
  3. 03Sized prerequisites for Alloy on a VPS
  4. 04Install Alloy and connect logs and metrics
  5. 05Alloy and Docker: read containers without opening everything
  6. 06Migrating from Promtail: the real risk is positions
  7. 07Migrate a Promtail configuration without loss or duplicates
  8. 08Promtail and Alloy, side by side
  9. 09Post-install configuration: UI, secrets and storage
  10. 10Harden the service without breaking collection
  11. 11Troubleshooting: the errors Alloy does not shout about
  12. 12Error messages and fixes
  13. 13An up-to-date agent on a server that belongs to you

Why replace Promtail with Alloy now

The official Promtail page leaves no room for doubt: the agent has been end of life since March 2, 2026, commercial support has ended and all development continues in Grafana Alloy. A collection agent is a privileged process that runs permanently: leaving it without fixes is not sustainable.

Alloy is not just a renamed Promtail. It is a single collector, configured through components: a log source, a system metrics exporter, a sender to Loki, another to Prometheus. On a VPS that ran Promtail and node_exporter side by side, one service is now enough. It can also relay OTLP if you instrument your applications, a topic covered in our Tempo and OpenTelemetry guide.

This guide does not reinstall Loki or Prometheus: the server side is described in the Loki and Grafana guide and in the Grafana and Prometheus guide. Here, we focus on the agent.

What a single agent changes on your server

  • One service to watch — logs and metrics go through the same alloy process, with a single journal to read when something breaks.
  • Updates that still arrive — Alloy is published in Grafana's apt and rpm repositories: an apt upgrade keeps it current like the rest of the system.
  • A tooled migration — the alloy convert command translates an existing Promtail configuration and produces a report of what it could not convert.
  • Built-in system metrics — the prometheus.exporter.unix component replaces node_exporter without an extra binary or port to open.
  • A local debugging interface — the UI on port 12345 shows the component graph and their values, reachable through an SSH tunnel.
  • Output to your own stack or to Grafana Cloud — the same file sends to a self-hosted Loki or to a managed service by changing one URL.

Sized prerequisites for Alloy on a VPS

Alloy installs on Debian, Ubuntu, AlmaLinux or Rocky Linux from Grafana's official repositories. The package creates an alloy system user, a systemd service of the same name and the configuration file /etc/alloy/config.alloy.

For resources, the Alloy documentation gives two orders of magnitude: about 1 core and 120 MiB of memory per MiB/s of ingested logs, and about 11 GiB of memory per million active Prometheus series. A standalone VPS exposes a few thousand series and rarely writes more than a few hundred KiB of logs per second. Our editorial recommendation: reserve half a vCPU and 256 to 512 MB of RAM for the agent, plus a few GB of disk under /var/lib/alloy for read positions and the send buffer.

Finally, you need a destination: a Loki reachable on /loki/api/v1/push, and a Prometheus started with the --web.enable-remote-write-receiver flag, which opens the /api/v1/write endpoint. No inbound port needs to be opened on the agent's VPS: Alloy pushes, it is not polled.

Install Alloy and connect logs and metrics

  1. Add the Grafana repository on Debian or Ubuntu

    sudo mkdir -p /etc/apt/keyrings
    sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
    sudo chmod 644 /etc/apt/keyrings/grafana.asc
    echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
    sudo apt-get update && sudo apt-get install alloy

  2. Or install it on AlmaLinux and Rocky Linux

    Import the key with wget -q -O gpg.key https://rpm.grafana.com/gpg.key then sudo rpm --import gpg.key. Create /etc/yum.repos.d/grafana.repo with baseurl=https://rpm.grafana.com, repo_gpgcheck=1, gpgcheck=1 and enabled=1, as the Alloy Linux installation page shows, then run sudo dnf install alloy.

  3. Start the service and check its user

    sudo systemctl enable --now alloy
    sudo systemctl status alloy
    systemctl cat alloy | grep User

    The last command should print User=alloy. The package does not set a Group=: read access therefore comes from that user's secondary groups, which matters for what follows.

  4. Declare the log destination

    Open /etc/alloy/config.alloy and add the sender to Loki:

    loki.write "default" {
    endpoint {
    url = "http://127.0.0.1:3100/loki/api/v1/push"
    }
    }

    Replace the address with your Loki's. If it is remote, use HTTPS and a basic_auth block.

  5. Collect journald and log files

    loki.source.journal "system" {
    forward_to = [loki.write.default.receiver]
    labels = {"job" = "systemd-journal"}
    }
    local.file_match "nginx" {
    path_targets = [{"__path__" = "/var/log/nginx/*.log", "job" = "nginx"}]
    }
    loki.source.file "nginx" {
    targets = local.file_match.nginx.targets
    forward_to = [loki.write.default.receiver]
    }

    By default, the journal source reads /var/log/journal and /run/log/journal, and only goes back seven hours on first start.

  6. Collect system metrics

    prometheus.exporter.unix "node" { }
    prometheus.scrape "node" {
    targets = prometheus.exporter.unix.node.targets
    forward_to = [prometheus.remote_write.default.receiver]
    }
    prometheus.remote_write "default" {
    endpoint {
    url = "http://127.0.0.1:9090/api/v1/write"
    }
    }

    If node_exporter was already running on the machine, stop it after the switch so the same series are not collected twice.

  7. Validate before reloading

    sudo alloy validate /etc/alloy/config.alloy
    sudo systemctl reload alloy
    sudo journalctl -u alloy -f

    A syntax error or a wrongly referenced component is reported by validate before the service loads the configuration.

  8. Grant read access

    id alloy should list the adm and systemd-journal groups. The package adds them when they exist; after a binary install or a removed group, restore them with sudo usermod -aG adm,systemd-journal alloy then sudo systemctl restart alloy. For an application folder owned by another user, the documentation prefers an ACL to one more group: sudo setfacl -R -m u:alloy:rx /var/log/monapp and sudo setfacl -R -d -m u:alloy:rx /var/log/monapp.

  9. Check end to end, then connect your dashboards

    Write a marker line with logger -t alloy-test "ligne de test alloy", then look for it in Grafana, Explore tab, with the query {job="systemd-journal"} |= "ligne de test alloy". On the metrics side, the node_load1 query should return a series for your VPS. Both answer: reuse the dashboards from our Grafana and Prometheus guide, which read the same node_* series.

Alloy and Docker: read containers without opening everything

To read your containers' logs, Alloy installed on the host combines discovery.docker and loki.source.docker, both pointed at unix:///var/run/docker.sock. The alloy user must then be able to open that socket, which in practice means sudo usermod -aG docker alloy. Do it knowingly: Docker's documentation reminds you that the docker group grants root-level privileges. Your log agent effectively becomes an administrator of the machine.

You can also run Alloy itself in a container, with the grafana/alloy image, the file mounted on /etc/alloy/config.alloy and the --storage.path=/var/lib/alloy/data argument on a persistent volume. Mounting the socket as :ro does not limit calls to the Docker API: a socket is still a socket. And for host metrics, the container must receive the machine's filesystem, /proc and /sys as mounts.

On a single VPS, the systemd package remains the simplest to audit.

Migrating from Promtail: the real risk is positions

Promtail keeps a positions file that records, for each tracked file, where it stopped reading. Alloy stores its own elsewhere, in the folder set by --storage.path. The conversion knows this: the loki.source.file block produced by alloy convert carries a legacy_positions_file argument that points to the old file, so reading resumes at the right place.

This mechanism has a documented weak spot. In grafana/alloy issue #5493, a user describes the error error reading legacy positions file followed by permission denied: Alloy could not read the old file, ignored it and re-sent every log on the server from the beginning. The maintainer considered it legitimate to stop Alloy in that case, and the issue is closed, but do not bet on your version: check permissions before the first start.

The other risk is the opposite: Promtail and Alloy running together send every line twice. The order below avoids both.

Migrate a Promtail configuration without loss or duplicates

  1. Locate the configuration and the positions file

    Open the Promtail configuration, often /etc/promtail/config.yml, and note the value of positions.filename. In a Docker Compose install, this file lives in a volume: note its real path on the host.

  2. Convert with a report

    alloy convert --source-format=promtail --report=/tmp/promtail-report.txt --output=/tmp/promtail.alloy /etc/promtail/config.yml

    Read the report first. The --bypass-errors option forces a partial conversion, but the documentation warns that behavior may then differ from the original.

  3. Check that the alloy user can read the old file

    grep legacy_positions_file /tmp/promtail.alloy
    sudo -u alloy cat /var/lib/promtail/positions.yaml

    Adapt the path to the one noted above. If the second command fails, copy the file into a folder readable by alloy and fix the path in the converted configuration.

  4. Stop Promtail before starting Alloy

    sudo systemctl stop promtail, or docker compose stop promtail for a container stack. Promtail then writes its last positions, and no line is sent twice during the switch.

  5. Switch the configuration

    Copy the converted blocks into /etc/alloy/config.alloy, run sudo alloy validate /etc/alloy/config.alloy, then sudo systemctl restart alloy. Immediately check sudo journalctl -u alloy | grep -i positions: no error reading legacy positions file line should appear.

  6. Monitor for a week, then uninstall

    Compare log volume in Grafana before and after the switch, and follow Alloy's loki_write_sent_entries_total counter. Keep the Promtail configuration a few days, then sudo systemctl disable promtail and remove the package or the Compose service.

Promtail and Alloy, side by side

Scroll the table

CriterionPromtailGrafana Alloy
StatusEnd of life since March 2, 2026, no more updatesActively developed, successor designated by Grafana
Signals collectedLogs onlyLogs, metrics, OTLP traces and profiles
System metricsnode_exporter to install separatelyBuilt-in `prometheus.exporter.unix` component
ConfigurationYAMLAlloy component syntax, conversion with `alloy convert`
Read positionsFile set in the configuration`--storage.path` folder, resumed through `legacy_positions_file`
DebuggingService journalService journal and local UI on port 12345
Service userDepends on the install method`alloy`, `adm` and `systemd-journal` groups added by the package

Post-install configuration: UI, secrets and storage

The debugging interface listens on 127.0.0.1:12345 by default. Leave it that way and open it through a tunnel: ssh -L 12345:127.0.0.1:12345 [email protected], then http://localhost:12345 in your browser. To expose it anyway, add --server.http.listen-addr=0.0.0.0:12345 to CUSTOM_ARGS, but only behind a firewall that filters that port.

Launch options live in /etc/default/alloy on Debian and Ubuntu, and in /etc/sysconfig/alloy on AlmaLinux and Rocky. That is also where you put secrets: declare a LOKI_PASSWORD variable, read it in the configuration with sys.env("LOKI_PASSWORD"), and keep the password out of config.alloy.

For a remote Loki, never send plain HTTP over the Internet: put Loki behind a TLS reverse proxy with authentication, as described in our Loki guide. Finally, watch the space in /var/lib/alloy: if Loki is unreachable, that is where data waiting to be sent piles up.

Harden the service without breaking collection

The Alloy documentation proposes a drop-in file, created with sudo systemctl edit alloy so it survives package upgrades: NoNewPrivileges=yes, ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes and ReadWritePaths=/var/lib/alloy. With ProtectSystem=strict, add ReadOnlyPaths=/var/log/journal and ReadOnlyPaths=/run/log/journal, otherwise the journal source reads nothing. Finish with sudo systemctl daemon-reload and sudo systemctl restart alloy. Prefer a per-folder ACL to adding groups, and only grant the docker group if you actually collect containers.

Troubleshooting: the errors Alloy does not shout about

Most Alloy failures on a VPS are not crashes. The service is active, the interface is green, and Grafana stays empty. The reason is documented by the maintainers themselves in grafana/alloy issue #2994, closed in favor of #6340: a component's health means it is running, not that it is sending.

Two reflexes are enough to get out of this fog. Read the service journal with sudo journalctl -u alloy --since "10 min ago", and query the agent's internal metrics with curl -s http://127.0.0.1:12345/metrics | grep loki_write. The loki_write_sent_entries_total counter should rise; loki_write_dropped_entries_total counts lines dropped after every send attempt failed.

Error messages and fixes

  • The journal source starts, but no line arrives — the loki.source.journal documentation says it: without the adm and systemd-journal groups, the component starts without error and collects nothing. Fix it with sudo usermod -aG adm,systemd-journal alloy, then restart the service, because an added group only takes effect at the next launch.
  • failed to tail the file: open /var/log/…: permission denied — the file exists but alloy cannot read it. Grant an ACL on the folder with setfacl -R -m u:alloy:rx and its -d variant for files later created by rotation.
  • No message, no line for a whole folder — grafana/alloy issue #6542 describes logs never sent without any error. A maintainer confirms there that an unreadable file is logged, but an inaccessible folder is not. Test with sudo -u alloy ls /var/log/monapp.
  • error reading legacy positions file … permission denied — Alloy did not read Promtail's positions and may re-send everything from the beginning (issue #5493). Stop the service, make the file readable for alloy, then start again.
  • failed to identify the read-write layer ID … mount-id: permission denied — an error from the cAdvisor component under systemd (issue #4068). The docker group is not enough; users report that a drop-in with AmbientCapabilities=CAP_DAC_READ_SEARCH and CapabilityBoundingSet=CAP_DAC_READ_SEARCH solves it without running the service as root. It is a community workaround, to test before rolling it out.
  • The UI is green, but Grafana stays empty — check loki_write_sent_entries_total and the test line written by logger. If the counter stalls, the failure is upstream, on the source side.

An up-to-date agent on a server that belongs to you

Alloy needs what shared hosting does not give: a systemd service, read access to /var/log and the journal, sometimes the Docker socket. On a VPS, you choose your distribution, keep your Loki and Prometheus stack, and decide which permissions the agent gets. If you would rather not administer the system yourself, the VPS administration option exists for the server.

The official documentation remains the reference for each component: migrating from Promtail, Linux permissions and the Promtail end-of-life page. For application tracing, continue with our Tempo and OpenTelemetry guide.

A VPS where your collection agent has the permissions it needs

On a ServOrbit.com VPS, you have root access, a choice of OS between Ubuntu, Debian, AlmaLinux and Rocky, and control over systemd, groups and ACLs. You install Alloy, Loki or Prometheus the way you want, and the VPS administration option exists for the server.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab