opencontainers / opencontainers/cgroups

systemd/v2: swap disabled via MemorySwap == Memory is not exported as MemorySwapMax, so daemon-reload resets memory.swap.max to max

Open Beginner friendly
#74 0 comments 0 reactions 0 assignees View on GitHub

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:

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.