opencontainers / opencontainers/cgroups
systemd/v2: swap disabled via MemorySwap == Memory is not exported as MemorySwapMax, so daemon-reload resets memory.swap.max to max
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 29
- Forks
- 32
- PR merge metrics
- No merged PRs in 30d
Description
Description
With the systemd cgroup driver on cgroup v2, disabling swap by setting MemorySwap == Memory (Docker --memory 8g --memory-swap 8g) does not survive a systemctl daemon-reload.
ConvertMemorySwapToCgroupV2Value returns 0 for that case (memorySwap - memory). The two drivers then diverge:
fs2/memory.gospecial-cases it and writesmemory.swap.max=0:
https://github.com/opencontainers/cgroups/blob/main/fs2/memory.go#L44-L55 (// memory and memorySwap set to the same value -- disable swap)systemd/v2.gogenV2ResourcesPropertiesonly exports the property whenswap != 0, so the scope'sMemorySwapMaxstaysinfinitywhile the cgroup file says0:
https://github.com/opencontainers/cgroups/blob/main/systemd/v2.go#L230-L237
The swap != 0 guard collapses "unset" and "explicitly zero" into the same case. Because systemd owns the scope, the next daemon-reload re-applies the unit's properties and rewrites memory.swap.max to max. The container silently regains unlimited swap; nothing on the container side changes (docker inspect still shows MemorySwap == Memory). docker update --memory X --memory-swap X restores 0 through the fs write, and the next reload resets it again.
This is not an exotic operator action: on 2026-09-05 an unattended kernel package upgrade triggered 15 daemon-reloads and every container on the host lost its swap ceiling.
Steps to reproduce
Ubuntu 24.04, systemd 255.4, cgroup v2, Docker 29.7.1 with runc 1.3.6 (vendors opencontainers/cgroups v0.0.4; the guard is unchanged on main), cgroupdriver=systemd.
$ docker run -d --name t --memory 256m --memory-swap 256m ubuntu sleep infinity
$ docker exec t cat /sys/fs/cgroup/memory.swap.max
0
$ sudo systemctl daemon-reload
$ docker exec t cat /sys/fs/cgroup/memory.swap.max
max
$ docker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.MemorySwap}}' t
268435456 268435456
Observed on a long-running container: memory.swap.max went 0 -> max across a single reload. Recreating the container only repeats the transient fs write.
Expected behaviour
The systemd driver should express the explicit zero on the scope, the way fs2 does for the cgroup file, e.g. in genV2ResourcesProperties:
if swap != 0 || r.MemorySwap > 0 {
// swap == 0 && r.MemorySwap > 0: memory and memorySwap set to the same value -- disable swap
properties = append(properties, newProp("MemorySwapMax", uint64(swap)))
}
so that MemorySwapMax=0 is set and re-applied by systemd on every reload.
Workaround
A host-wide prefix drop-in /etc/systemd/system/docker-.scope.d/50-no-swap.conf with [Scope] / MemorySwapMax=0 makes systemd own the zero, but it applies to every container on the host regardless of its own swap request, so it is not a substitute for a fix.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in systemd/v2.go at genV2ResourcesProperties and compare the handling with fs2/memory.go. Reproduce the issue on cgroup v2 by creating a container with equal memory and memory-swap limits, then run daemon-reload. Done means the systemd scope retains memory.swap.max=0 after reload, with coverage for the explicit zero case if the surrounding tests provide a place for it.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100