How to harden a Memcached container (and is memcached:1.6-alpine safe for untrusted workloads?)¶
A cache sits in the hot path of every request, which is exactly why its container should be
tight. A stock docker run memcached:1.6-alpine starts ahead of most images (it runs non-root
out of the box) but is still not a boundary to trust around an untrusted network. Graded on
IronClaw's seven-dimension containment scale, the default configuration scores 63 of 100,
grade C (partial). Higher is safer. A couple of runtime flags take the same image to 89 of
100, grade B, one point off an A, and the one dimension it cannot reach is the one a cache
needs by definition (clients must connect to it). Here are the exact gaps and fixes from the
scan data.
Graded from a read-only inspect of a running container started from
memcached:1.6-alpinewith plaindocker rundefaults, its entrypoint overridden withsleeppurely to keep it alive. The scan itself executes nothing inside the container. It is the same data behind its isolation scorecard. How scoring works →
Where the default configuration leaks¶
ironctl scan grades seven independent containment boundaries. On a default
docker run memcached:1.6-alpine, three fail or warn:
| Dimension | Verdict | Score | What the scan found |
|---|---|---|---|
| Non-root user (uid != 0) | ✅ PASS | 15/15 | runs as memcache (uid != 0) |
| Dropped capabilities | ❌ FAIL | 4/20 | default capability set retained (CAP_NET_RAW, CAP_MKNOD, and more) |
| Seccomp profile | ✅ PASS | 15/15 | seccomp profile active |
| Network isolation / egress | ⚠️ WARN | 4/15 | network=bridge: outbound egress is possible |
| Read-only root filesystem | ❌ FAIL | 0/10 | root filesystem is writable |
| No docker.sock exposure | ✅ PASS | 15/15 | no control socket mounted |
| No shared host namespaces | ✅ PASS | 10/10 | no host PID/IPC/network sharing |
Memcached already runs as the non-root memcache user, which is why it clears the 48/100 that
root-by-default images sit at. The remaining leaks are the retained capabilities and the
writable root filesystem. Memcached is purely in-memory, it has no data directory to persist,
so there is no reason its root filesystem should be writable at all: a read-only rootfs removes
the persistence surface for free, and dropping the default capabilities takes CAP_NET_RAW and
friends away from any code that lands via a protocol-parsing CVE.
Harden it: the exact --fix remediation¶
ironctl scan my-memcached --fix prints one remediation per failed dimension, then one hardened
run. For memcached:1.6-alpine:
--cap-drop=ALL(Dropped capabilities, +16): drop every Linux capability; add back only what the workload provably needs. Memcached needs none of the defaults.--read-only --tmpfs /tmp(Read-only rootfs, +10): make the root filesystem read-only. Memcached keeps everything in memory, so no writable volume is needed at all, a rare clean win.--user 65532:65532(Non-root user, already passing at +0): non-root already passes as thememcacheuser. Pinning an explicit fixed uid is the auditable choice; it does not change the score.- Scoped network (Network isolation):
--network=nonescores the full 15 but is wrong for a cache, clients must be able to connect to it. Any named or bridge network scores 4 of 15 (a WARN, not a fail): a connection path exists. This is the one dimension a cache cannot max out. Contain it anyway: attach a user-defined network scoped to just its clients, with no default route out, so a compromised cache cannot call arbitrary internet addresses.
Before and after¶
# Before: 63/100, grade C
docker run -d --name memcached memcached:1.6-alpine
# After: 89/100, grade B (scoped private network for its clients)
docker run -d --name memcached-hardened \
--user 65532:65532 \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp \
--network=cache-internal \
memcached:1.6-alpine
Rescan: ironctl scan memcached-hardened reports 89/100 grade B. A 26-point swing with no
custom image build and no volume, just the right flags. The only dimension still short of full
marks is the network (4 of 15), because a cache exists to be connected to; network=none would
score the last points but leave nothing able to reach it. That is the honest ceiling for a cache,
and it is a clean grade B up from the default C.
Verify it on your own cache¶
# install (Homebrew)
brew install ironsecco/ironclaw/ironclaw
# grade your running container, then print the fixes
ironctl scan my-memcached
ironctl scan my-memcached --fix
ironctl scan also reads a docker-compose.yml service or a Kubernetes manifest, so you can
grade the Memcached in your stack, not just a bare docker run.
Keep going¶
- memcached:1.6-alpine isolation scorecard →: the full dimension breakdown.
- Databases and caches, ranked by isolation →: how Memcached compares to Redis, Postgres, MySQL, and the rest.
- How to harden a Redis container →: the same walkthrough for the other in-memory store.
- Scan any container in 10 seconds →: the full
ironctl scanreference. - Run untrusted code in a real sandbox →: IronClaw wraps every AI-agent session in a gVisor/Kata boundary with
network=noneby default.