TelopiDocs

Docker or Docker Compose

A Linux host that runs Docker. A compose.yaml runs Alloy beside your containers and sends the host's CPU, memory, disks and network.

These are the steps the Connect page shows in the portal, at telopi.com/app, where your values are filled in. Here they are placeholders:

  • <INGEST_URL>: your ingest address, on the portal's home page and on its Connect page.
  • <USERNAME>: the username of an ingest key, on the portal's Ingest keys page, or, if your Grafana takes one ingest login rather than keys, the login's username on Connect. Use one key for each place that sends, so you can replace one without touching the others.
  • <ENVIRONMENT>: a name you choose for this environment, such as production: lower-case letters, digits, ., _ and -. Your dashboards group by it.

Your ingest key's password is never in the code: a step that needs it asks for it when it runs.

1. Write Alloy's configuration

In a new folder on the Docker host, write Alloy's configuration. It has no secret.

mkdir -p telopi-alloy && cd telopi-alloy
cat > config.alloy <<'ALLOY'
// Machines: CPU, load, memory, disks and network of the machine Alloy runs
// on, through Alloy's unix exporter, sent to your Telopi stack every minute.
// Run one Alloy per machine. The password is your ingest key's, read from
// the environment variable TELOPI_PASSWORD.

prometheus.exporter.unix "machines" {
  // In a container, TELOPI_ROOTFS is where the host's / is mounted, so the
  // disks are the host's; unset, as on a machine itself, it is /.
  rootfs_path = coalesce(sys.env("TELOPI_ROOTFS"), "/")
}

prometheus.scrape "machines" {
  targets         = prometheus.exporter.unix.machines.targets
  scrape_interval = "60s"
  forward_to      = [prometheus.remote_write.machines.receiver]
}

prometheus.remote_write "machines" {
  // The environment's name: the Machines dashboard and its alerts group by it.
  external_labels = {
    cluster = "<ENVIRONMENT>",
  }

  endpoint {
    url = "<INGEST_URL>/mimir/api/v1/push"

    basic_auth {
      username = "<USERNAME>"
      password = sys.env("TELOPI_PASSWORD")
    }
  }
}
ALLOY

2. Put the password in .env

In the same folder, paste your ingest key's password when this asks: it is not shown, and it goes in no command, so not in your shell's history. It is written to .env, which only you read.

bash <<'TELOPI'
read -rsp "Ingest key password: " TELOPI_PASSWORD </dev/tty
echo
touch .env
chmod 600 .env
printf "TELOPI_PASSWORD='%s'\n" "$TELOPI_PASSWORD" > .env
TELOPI

3. Add compose.yaml

Save this as compose.yaml in the same folder. Alloy shares the host's network and processes and reads the host's / mounted read-only, so the metrics, disks included, are the host's, not the container's. Its own page listens on 127.0.0.1:12399, not Alloy's usual 12345, so it does not clash with an Alloy already on the host.

services:
  alloy:
    image: grafana/alloy:v1.20.0
    restart: unless-stopped
    command: run --server.http.listen-addr=127.0.0.1:12399 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
    env_file: .env
    environment:
      TELOPI_ROOTFS: /host/root
    network_mode: host
    pid: host
    volumes:
      - ./config.alloy:/etc/alloy/config.alloy:ro
      - alloy-data:/var/lib/alloy/data
      - /:/host/root:ro

volumes:
  alloy-data:

4. Start it

Start Alloy in the background; Docker starts it again after a restart.

docker compose up -d
docker compose ps alloy

5. Check that it arrived

Alloy should be running and log no error (in a container it always logs one about udev, which is harmless and left out). Within two minutes Connect in the portal says Receiving data, and the Machines dashboard in your Grafana fills. A 401 in the log is a wrong username or password.

docker compose logs --since 5m alloy | grep -E "level=error|HTTP status 40[13]" | grep -v "udev device properties" || echo "No errors in the last five minutes."

More

On this page