docker.io/grafana/alloy
validated pin current tags: v1.16.2
alloy (Grafana Alloy) — capabilities AND read-only-filesystem, derived by drop-test. capabilities: cap_drop:[ALL] + cap_add:[] — ZERO-cap. alloy is a non-root distroless service (uid 473) on the unprivileged :12345 writing only to its storage.path, so no capability is load-bearing. Published explicitly so a user of the stock image (default 14 caps) sees the 14 -> 0 reduction, not just the filesystem dimension. filesystem: read_only:true, tmpfs:[] — storage.path is a PERSISTENT VOLUME (NOT tmpfs); the derived tmpfs minimum is EMPTY. filesystem: read_only:true, tmpfs:[]. Under a read-only rootfs with only its storage.path location writable, alloy loads its pipeline, becomes ready, and runs — with a read-only /tmp (drop-tested NOT required). Under the image default (--storage.path=/var/lib/alloy/data) that location is /var/lib/alloy, a PERSISTENT VOLUME (WAL / component state; must survive restarts, NOT tmpfs). alloy is DISTROLESS (no shell/curl), so it is validated from a sidecar probe sharing its network namespace, not `docker exec` — see the criteria doc.
Use it
services:
alloy:
image: docker.io/grafana/alloy:v1.16.2
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
read_only: true
This profile has multiple dimensions and is applied as a unit. Dimensions can interact — a capability can be required only because of a sibling read-only/tmpfs recommendation — so where they do, the minimum was derived under the sibling dimension's context. See the criteria doc and each dimension's recorded invocation below before applying them separately.
Dimension: capabilities
cap_drop: ALL (no cap_add)
| tool | container-sec-derive 0.7.0 |
|---|---|
| observer | drop-test |
| validated image | docker.io/grafana/alloy@sha256:32913cbfac65… |
| validated date | 2026-07-16 |
| confidence | moderate |
| validated via | drop-test, ci-smoke |
| workload | profiles/workloads/alloy.sh |
| workload sha256 | 5a5396d750b6… |
| ig version | v0.51.0 |
Feature coverage (ledger)
A drop-test proves the minimum for what the workload exercised. Privilege-relevant features it did not drive are listed honestly — using one may need more than this minimum.
| feature | evidence / reason | |
|---|---|---|
| driven | the exercised collection pipeline | the committed pipeline config runs clean at zero caps |
| not driven | process / eBPF collectors | configurable pipelines can need SYS_PTRACE/BPF — a surface the workload does not bound; the reason coverage grades partial |
Drop-test evidence
Each candidate removed in turn, the container restarted, and the workload re-verified.
| Removed | Verdict | Observed |
|---|---|---|
CHOWN | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
DAC_OVERRIDE | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
FSETID | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
FOWNER | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
MKNOD | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
NET_RAW | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
SETGID | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
SETUID | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
SETFCAP | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
SETPCAP | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
NET_BIND_SERVICE | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
SYS_CHROOT | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
KILL | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
AUDIT_WRITE | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
Recorded invocation (run_config)
The minimum is valid for this invocation; a different user:, volume state, or entrypoint can change it.
| security_opt | no-new-privileges:true |
|---|---|
| mounts | alloy-data:/var/lib/alloy |
Workload script (profiles/workloads/alloy.sh)
#!/usr/bin/env bash
# Workload exerciser for the alloy (Grafana Alloy) reference image.
#
# alloy is DISTROLESS (no shell/curl), so it is probed from a SIDECAR container
# sharing alloy's network namespace (`docker run --network container:<target>`) and
# reaching it on localhost:12345 — not `docker exec`. The probe image is a pinned,
# multi-arch curl.
#
# Under the image default invocation alloy loads its pipeline and writes its data
# under --storage.path; alloy fatals at startup if that path isn't writable, so
# "ready + a live pipeline" exercises the storage.path write. Returns 0 on success.
#
# Required env: ALLOYCONTAINER (target container name or id).
set -euo pipefail
: "${ALLOYCONTAINER:?ALLOYCONTAINER must be set}"
C="${ALLOYCONTAINER}"
# curlimages/curl:8.11.1 — multi-arch, digest-pinned (test-only probe image).
PROBE="${CSD_PROBE_CURL_IMAGE:-curlimages/curl@sha256:c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69}"
sc() { docker run --rm --network "container:${C}" "$PROBE" "$@"; }
deadline=$((SECONDS + 45))
until [ "$(sc -s -o /dev/null -w '%{http_code}' http://localhost:12345/-/ready 2>/dev/null)" = 200 ]; do
if (( SECONDS >= deadline )); then
echo "alloy /-/ready never returned 200 in 45s" >&2
exit 1
fi
sleep 1
done
n="$(sc -s http://localhost:12345/metrics 2>/dev/null | grep -c '^alloy_')"
if [ "${n:-0}" -lt 1 ]; then
echo "no alloy_ metrics — pipeline not running" >&2
exit 1
fi
Dimension: filesystem
read_only: true, no tmpfs
| tool | container-sec-derive 0.7.0 |
|---|---|
| observer | drop-test |
| validated image | docker.io/grafana/alloy@sha256:32913cbfac65… |
| validated date | 2026-07-04 |
| confidence | high |
| validated via | drop-test, ci-smoke |
| workload | profiles/workloads/alloy.sh |
| workload sha256 | 5a5396d750b6… |
| ig version | v0.51.0 |
Drop-test evidence
Each candidate removed in turn, the container restarted, and the workload re-verified.
| Removed | Verdict | Observed |
|---|---|---|
/tmp | removable | correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable |
Recorded invocation (run_config)
The minimum is valid for this invocation; a different user:, volume state, or entrypoint can change it.
| command | run/etc/alloy/config.alloy--storage.path=/var/lib/alloy/data |
|---|---|
| security_opt | no-new-privileges:true |
| mounts | alloy-data:/var/lib/alloy |
Workload script (profiles/workloads/alloy.sh)
#!/usr/bin/env bash
# Workload exerciser for the alloy (Grafana Alloy) reference image.
#
# alloy is DISTROLESS (no shell/curl), so it is probed from a SIDECAR container
# sharing alloy's network namespace (`docker run --network container:<target>`) and
# reaching it on localhost:12345 — not `docker exec`. The probe image is a pinned,
# multi-arch curl.
#
# Under the image default invocation alloy loads its pipeline and writes its data
# under --storage.path; alloy fatals at startup if that path isn't writable, so
# "ready + a live pipeline" exercises the storage.path write. Returns 0 on success.
#
# Required env: ALLOYCONTAINER (target container name or id).
set -euo pipefail
: "${ALLOYCONTAINER:?ALLOYCONTAINER must be set}"
C="${ALLOYCONTAINER}"
# curlimages/curl:8.11.1 — multi-arch, digest-pinned (test-only probe image).
PROBE="${CSD_PROBE_CURL_IMAGE:-curlimages/curl@sha256:c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69}"
sc() { docker run --rm --network "container:${C}" "$PROBE" "$@"; }
deadline=$((SECONDS + 45))
until [ "$(sc -s -o /dev/null -w '%{http_code}' http://localhost:12345/-/ready 2>/dev/null)" = 200 ]; do
if (( SECONDS >= deadline )); then
echo "alloy /-/ready never returned 200 in 45s" >&2
exit 1
fi
sleep 1
done
n="$(sc -s http://localhost:12345/metrics 2>/dev/null | grep -c '^alloy_')"
if [ "${n:-0}" -lt 1 ]; then
echo "no alloy_ metrics — pipeline not running" >&2
exit 1
fi
Evidence & provenance
- Validation criteria — per-image scenarios and pass criteria
- Profile source in the repository
- Version history — every previously published pin, with the full profile as validated against it
Raw profile YAML
# alloy (Grafana Alloy) — capabilities AND read-only-filesystem, derived by
# drop-test.
#
# capabilities: cap_drop:[ALL] + cap_add:[] — ZERO-cap. alloy is a non-root
# distroless service (uid 473) on the unprivileged :12345 writing only to its
# storage.path, so no capability is load-bearing. Published explicitly so a
# user of the stock image (default 14 caps) sees the 14 -> 0 reduction, not
# just the filesystem dimension.
#
# filesystem: read_only:true, tmpfs:[] — storage.path is a PERSISTENT VOLUME
# (NOT tmpfs); the derived tmpfs minimum is EMPTY.
#
# filesystem: read_only:true, tmpfs:[]. Under a read-only rootfs with only its
# storage.path location writable, alloy loads its pipeline, becomes ready, and runs
# — with a read-only /tmp (drop-tested NOT required). Under the image default
# (--storage.path=/var/lib/alloy/data) that location is /var/lib/alloy, a PERSISTENT
# VOLUME (WAL / component state; must survive restarts, NOT tmpfs).
#
# alloy is DISTROLESS (no shell/curl), so it is validated from a sidecar probe
# sharing its network namespace, not `docker exec` — see the criteria doc.
schema_version: "1.6"
image: docker.io/grafana/alloy
reference_url: https://tmatens.github.io/container-security-profiles/profiles/docker.io/grafana/alloy.html
applies_to:
tags: ["v1.16.2"]
status: validated
dimensions:
capabilities:
cap_drop: [ALL]
cap_add: []
derivation:
tool: container-sec-derive
tool_version: "0.7.0"
sidecar_schema_version: "1.5"
observer: drop-test
validated_image: docker.io/grafana/alloy@sha256:32913cbfac652d15fa84d256a74e5ee3f71575961bb19d34796ce3838bfba693
validated_date: "2026-07-16"
duration_seconds: 30
confidence: moderate
workload: profiles/workloads/alloy.sh
workload_sha256: "5a5396d750b6381e9712bb46ca156ccaa5b4931e6dbedd87fc1836ce107ed736"
validated_via: [drop-test, ci-smoke]
observation_backend:
ig_version: v0.51.0
gadgets: []
run_config:
user: ""
command: []
entrypoint: ""
network: ""
pid: ""
devices: []
security_opt: ["no-new-privileges:true"]
mounts: ["alloy-data:/var/lib/alloy"]
env: []
features:
- name: "the exercised collection pipeline"
driven: true
why: "the committed pipeline config runs clean at zero caps"
- name: "process / eBPF collectors"
driven: false
why: "configurable pipelines can need SYS_PTRACE/BPF — a surface the workload does not bound; the reason coverage grades partial"
drop_test:
checks:
- {removed: CHOWN, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: DAC_OVERRIDE, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: FSETID, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: FOWNER, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: MKNOD, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: NET_RAW, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: SETGID, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: SETUID, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: SETFCAP, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: SETPCAP, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: NET_BIND_SERVICE, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: SYS_CHROOT, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: KILL, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
- {removed: AUDIT_WRITE, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}
filesystem:
read_only: true
tmpfs: []
derivation:
tool: container-sec-derive
tool_version: "0.7.0"
sidecar_schema_version: "1.5"
observer: drop-test
validated_image: docker.io/grafana/alloy@sha256:32913cbfac652d15fa84d256a74e5ee3f71575961bb19d34796ce3838bfba693
validated_date: "2026-07-04"
duration_seconds: 5
confidence: high
workload: profiles/workloads/alloy.sh
workload_sha256: "5a5396d750b6381e9712bb46ca156ccaa5b4931e6dbedd87fc1836ce107ed736"
validated_via: [drop-test, ci-smoke]
observation_backend:
ig_version: v0.51.0
gadgets: []
run_config:
user: ""
command: ["run", "/etc/alloy/config.alloy", "--storage.path=/var/lib/alloy/data"]
entrypoint: ""
network: ""
pid: ""
devices: []
security_opt: ["no-new-privileges:true"]
mounts: ["alloy-data:/var/lib/alloy"]
env: []
drop_test:
checks:
- {removed: /tmp, required: false, observed: "correct: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable"}