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 asproduction: 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")
}
}
}
ALLOY2. 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
TELOPI3. 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 alloy5. 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."