Skip to content

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

Description

@paulpengtw

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions