catalog / docker.io/grafana/alloy

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)

toolcontainer-sec-derive 0.7.0
observerdrop-test
validated imagedocker.io/grafana/alloy@sha256:32913cbfac65…
validated date2026-07-16
confidencemoderate
validated viadrop-test, ci-smoke
workloadprofiles/workloads/alloy.sh
workload sha2565a5396d750b6…
ig versionv0.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.

featureevidence / reason
driventhe exercised collection pipelinethe committed pipeline config runs clean at zero caps
not drivenprocess / eBPF collectorsconfigurable 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.

RemovedVerdictObserved
CHOWNremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
DAC_OVERRIDEremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
FSETIDremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
FOWNERremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
MKNODremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
NET_RAWremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
SETGIDremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
SETUIDremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
SETFCAPremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
SETPCAPremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
NET_BIND_SERVICEremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
SYS_CHROOTremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
KILLremovablecorrect: alloy ready, pipeline live (82 alloy_ metrics), storage.path writable
AUDIT_WRITEremovablecorrect: 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_optno-new-privileges:true
mountsalloy-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

toolcontainer-sec-derive 0.7.0
observerdrop-test
validated imagedocker.io/grafana/alloy@sha256:32913cbfac65…
validated date2026-07-04
confidencehigh
validated viadrop-test, ci-smoke
workloadprofiles/workloads/alloy.sh
workload sha2565a5396d750b6…
ig versionv0.51.0

Drop-test evidence

Each candidate removed in turn, the container restarted, and the workload re-verified.

RemovedVerdictObserved
/tmpremovablecorrect: 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.

commandrun
/etc/alloy/config.alloy
--storage.path=/var/lib/alloy/data
security_optno-new-privileges:true
mountsalloy-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

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"}