docker.io/valkey/valkey
validated pin current tags: 9
valkey (redis-compatible) — minimum capabilities, derived by drop-test against immich's deployment invocation (valkey is immich's redis). capabilities: cap_drop:[ALL] + cap_add:[SETGID, SETUID]. Both are STARTUP caps: the entrypoint starts as root and drops to the valkey user (uid 999) via `setpriv` — dropping either fails with "setpriv: setresuid/setresgid failed" and the container exits. The declared cap_add additionally grants CHOWN, a genuine over-grant under this invocation: with no data volume, valkey uses the image's own pre-owned /data, so nothing is chowned (see the criteria doc's drop-test evidence). A deployment that binds a foreign-owned data volume would need CHOWN back — the minimum is scoped to the invocation in run_config. filesystem: read_only:true, tmpfs:[]. valkey's RDB/AOF live in /data (not a declared VOLUME in this image, so a persistent bind/named volume in production, never tmpfs). Under a read-only rootfs with /data writable it serves the PING + SET/GET round-trip with no additional tmpfs. /tmp NOT required. Derived by drop-test.
Use it
services:
valkey:
image: docker.io/valkey/valkey:9
cap_drop: [ALL]
cap_add: [SETGID, SETUID]
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 + cap_add: SETGID, SETUID
| tool | container-sec-derive 0.7.0 |
|---|---|
| observer | drop-test |
| validated image | docker.io/valkey/valkey@sha256:8e8d64b405ce… |
| validated date | 2026-07-16 |
| confidence | high |
| validated via | drop-test, ci-smoke |
| workload | profiles/workloads/valkey.sh |
| workload sha256 | d0bfaf961f2b… |
| ig version | v0.51.0 |
Drop-test evidence
Each candidate removed in turn, the container restarted, and the workload re-verified.
| Removed | Verdict | Observed |
|---|---|---|
CHOWN | removable | correct: PING + SET/GET, server uid=999 (non-root) |
DAC_OVERRIDE | removable | correct: PING + SET/GET, server uid=999 (non-root) |
FOWNER | removable | correct: PING + SET/GET, server uid=999 (non-root) |
FSETID | removable | correct: PING + SET/GET, server uid=999 (non-root) |
MKNOD | removable | correct: PING + SET/GET, server uid=999 (non-root) |
NET_RAW | removable | correct: PING + SET/GET, server uid=999 (non-root) |
SETGID | required | container failed to start: setpriv: setresgid failed: Operation not permitted |
SETUID | required | container exited: setpriv: setresuid failed: Operation not permitted |
SETFCAP | removable | correct: PING + SET/GET, server uid=999 (non-root) |
SETPCAP | removable | correct: PING + SET/GET, server uid=999 (non-root) |
NET_BIND_SERVICE | removable | correct: PING + SET/GET, server uid=999 (non-root) |
SYS_CHROOT | removable | correct: PING + SET/GET, server uid=999 (non-root) |
KILL | removable | correct: PING + SET/GET, server uid=999 (non-root) |
AUDIT_WRITE | removable | correct: PING + SET/GET, server uid=999 (non-root) |
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 |
|---|
Workload script (profiles/workloads/valkey.sh)
#!/usr/bin/env bash
# Workload exerciser for the valkey / redis reference image.
#
# Connects to a running container and drives a representative round-trip —
# PING, then SET and GET a key. Returns 0 if every step succeeded. The
# non-root privilege-drop is asserted by the drop-test correctness check (see
# the criteria doc), not here.
#
# Required env: REDISCONTAINER (target container name or id).
set -euo pipefail
: "${REDISCONTAINER:?REDISCONTAINER must be set}"
# Probes run as the valkey user (never root — a root probe's own needs would
# pollute the derived minimum) and capture-then-match (never `producer | grep -q`
# under pipefail: grep's early exit SIGPIPEs the producer and a matching
# response reads as failure).
deadline=$((SECONDS + 30))
until grep -qi PONG <<<"$(docker exec --user valkey "${REDISCONTAINER}" redis-cli ping 2>/dev/null)"; do
if (( SECONDS >= deadline )); then
echo "valkey did not respond to PING in 30s" >&2
exit 1
fi
sleep 1
done
docker exec --user valkey "${REDISCONTAINER}" redis-cli set csd:probe ok >/dev/null
value="$(docker exec --user valkey "${REDISCONTAINER}" redis-cli get csd:probe | tr -d '[:space:]')"
if [ "$value" != ok ]; then
echo "GET returned '${value}' (expected 'ok')" >&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/valkey/valkey@sha256:8e8d64b405ce… |
| validated date | 2026-07-16 |
| confidence | high |
| validated via | drop-test, ci-smoke |
| workload | profiles/workloads/valkey.sh |
| workload sha256 | d0bfaf961f2b… |
| 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: PING + SET/GET (auth=no), server uid=999 (non-root) |
Workload script (profiles/workloads/valkey.sh)
#!/usr/bin/env bash
# Workload exerciser for the valkey / redis reference image.
#
# Connects to a running container and drives a representative round-trip —
# PING, then SET and GET a key. Returns 0 if every step succeeded. The
# non-root privilege-drop is asserted by the drop-test correctness check (see
# the criteria doc), not here.
#
# Required env: REDISCONTAINER (target container name or id).
set -euo pipefail
: "${REDISCONTAINER:?REDISCONTAINER must be set}"
# Probes run as the valkey user (never root — a root probe's own needs would
# pollute the derived minimum) and capture-then-match (never `producer | grep -q`
# under pipefail: grep's early exit SIGPIPEs the producer and a matching
# response reads as failure).
deadline=$((SECONDS + 30))
until grep -qi PONG <<<"$(docker exec --user valkey "${REDISCONTAINER}" redis-cli ping 2>/dev/null)"; do
if (( SECONDS >= deadline )); then
echo "valkey did not respond to PING in 30s" >&2
exit 1
fi
sleep 1
done
docker exec --user valkey "${REDISCONTAINER}" redis-cli set csd:probe ok >/dev/null
value="$(docker exec --user valkey "${REDISCONTAINER}" redis-cli get csd:probe | tr -d '[:space:]')"
if [ "$value" != ok ]; then
echo "GET returned '${value}' (expected 'ok')" >&2
exit 1
fi
App-tier verification
The hardening verified at the service level — the full stack brought up with the minimum applied and driven via its real API.
| service | immich v2.7.5 |
|---|---|
| method | container-sec-derive scripts/apptier_verify.sh |
| check | immich's released stack brought up with redis hardened to this minimum (alongside the hardened postgres), driven via immich's real REST API — admin sign-up, login, upload a photo, read it back, metadata search — all passing. (The stack's app-tier check is shown to catch a too-tight config by over-hardening a leaf; see the postgres profile's over_hardening evidence.) |
| result | validated |
| verified | 2026-07-04 |
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
# valkey (redis-compatible) — minimum capabilities, derived by drop-test against
# immich's deployment invocation (valkey is immich's redis).
#
# capabilities: cap_drop:[ALL] + cap_add:[SETGID, SETUID]. Both are STARTUP caps:
# the entrypoint starts as root and drops to the valkey user (uid 999) via
# `setpriv` — dropping either fails with "setpriv: setresuid/setresgid failed" and
# the container exits. The declared cap_add additionally grants CHOWN, a genuine
# over-grant under this invocation: with no data volume, valkey uses the image's
# own pre-owned /data, so nothing is chowned (see the criteria doc's drop-test
# evidence). A deployment that binds a foreign-owned data volume would need CHOWN
# back — the minimum is scoped to the invocation in run_config.
# filesystem: read_only:true, tmpfs:[]. valkey's RDB/AOF live in /data (not a
# declared VOLUME in this image, so a persistent bind/named volume in
# production, never tmpfs). Under a read-only rootfs with /data writable it
# serves the PING + SET/GET round-trip with no additional tmpfs. /tmp NOT
# required. Derived by drop-test.
#
schema_version: "1.5"
image: docker.io/valkey/valkey
reference_url: https://tmatens.github.io/container-security-profiles/profiles/docker.io/valkey/valkey.html
applies_to:
tags: ["9"]
status: validated
app_tier_verified:
service: immich
service_version: v2.7.5
method: container-sec-derive scripts/apptier_verify.sh
check: >-
immich's released stack brought up with redis hardened to this minimum
(alongside the hardened postgres), driven via immich's real REST API — admin
sign-up, login, upload a photo, read it back, metadata search — all passing.
(The stack's app-tier check is shown to catch a too-tight config by
over-hardening a leaf; see the postgres profile's over_hardening evidence.)
verified_date: "2026-07-04"
result: pass
dimensions:
capabilities:
cap_drop: [ALL]
cap_add: [SETGID, SETUID]
derivation:
tool: container-sec-derive
tool_version: "0.7.0"
sidecar_schema_version: "1.5"
observer: drop-test
validated_image: docker.io/valkey/valkey@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
validated_date: "2026-07-16"
duration_seconds: 120
confidence: high
workload: profiles/workloads/valkey.sh
workload_sha256: "d0bfaf961f2b24c70d247118f97f96abf9e5492f70099b6ad8a3c27466706036"
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: []
env: []
drop_test:
checks:
- {removed: CHOWN, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: DAC_OVERRIDE, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: FOWNER, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: FSETID, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: MKNOD, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: NET_RAW, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: SETGID, required: true, observed: "container failed to start: setpriv: setresgid failed: Operation not permitted"}
- {removed: SETUID, required: true, observed: "container exited: setpriv: setresuid failed: Operation not permitted"}
- {removed: SETFCAP, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: SETPCAP, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: NET_BIND_SERVICE, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: SYS_CHROOT, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: KILL, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
- {removed: AUDIT_WRITE, required: false, observed: "correct: PING + SET/GET, server uid=999 (non-root)"}
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/valkey/valkey@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
validated_date: "2026-07-16"
duration_seconds: 2
confidence: high
workload: profiles/workloads/valkey.sh
workload_sha256: "d0bfaf961f2b24c70d247118f97f96abf9e5492f70099b6ad8a3c27466706036"
validated_via: [drop-test, ci-smoke]
observation_backend:
ig_version: v0.51.0
gadgets: []
run_config:
user: ""
command: []
entrypoint: ""
network: ""
pid: ""
devices: []
security_opt: []
mounts: []
env: []
drop_test:
checks:
- {removed: /tmp, required: false, observed: "correct: PING + SET/GET (auth=no), server uid=999 (non-root)"}