Memory Swappiness Tuning in Docker: The Hidden Knob Behind Container Performance

Memory Swappiness Tuning in Docker: The Hidden Knob Behind Container Performance

A ground-up guide to how Docker containers decide when to swap memory to disk instead of using RAM — and why getting this one setting wrong can silently wreck a database's latency or crash a service under load.

Picture an office with a small, fast filing cabinet on every desk and a slow, cavernous storage room down the hall. When a desk’s cabinet fills up, someone has to decide which folders get moved to the storage room to make room for new ones — and how eagerly that happens changes everything about how the office feels to work in. Move folders too eagerly, and people are constantly walking to the storage room for files they just had. Move folders too reluctantly, and desks overflow with no room left at all. Linux faces this exact dilemma with RAM (the desk) and swap space (the storage room), and the setting that controls how eagerly the kernel makes that trade-off is called swappiness. Docker gives every container its own version of that dial. This tutorial explains, from first principles, what that dial actually controls, how the kernel enforces it, and how to reason about setting it correctly for real workloads — and, just as importantly, when the dial isn’t actually the thing doing the work you might assume it is.

1What Memory Swappiness Tuning Actually Is

Before diving into cgroups and kernel internals, it’s worth being precise about what “swappiness” means — and what it doesn’t.

The one-sentence definition

Swappiness is a tunable value, from 0 to 100, that tells the Linux kernel how aggressively it should move memory pages out to swap space rather than reclaiming them from the page cache when the system needs to free up RAM. In Docker, memory swappiness tuning means setting that same behavior on a per-container basis, so one container’s appetite for swapping doesn’t have to match the host’s global default, or any other container’s.

Simple Analogy

Think of swappiness as a thermostat for “how much effort to save space.” A value near 0 tells the kernel “keep everything at the desk no matter what, even if it means throwing out less-useful papers (page cache) first.” A value near 100 tells the kernel “happily walk folders down to the storage room (swap) as soon as the desk gets even slightly full, to keep maximum space free.”

Kernel Feature

vm.swappiness

The original, host-wide sysctl setting that has controlled this behavior system-wide since long before containers existed.

Docker Component

–memory-swappiness

Docker’s per-container flag that sets the same value scoped to just that one container’s cgroup.

Enforcement Layer

cgroups memory subsystem

The kernel mechanism that actually applies memory limits and swappiness values to a specific group of processes.

Related Knobs

–memory, –memory-swap, –memory-reservation

Sibling Docker flags that define how much RAM and swap a container may use in total — covered in depth later in this tutorial.

i
Scope check

Swappiness does not decide whether a container can use swap at all — that’s controlled separately by the swap limit. Swappiness only decides how readily the kernel reaches for swap once memory pressure exists, versus reclaiming other reclaimable memory first.

A little history

Swappiness as a concept predates containers entirely — it has been a host-wide Linux kernel tunable for well over two decades, originally added simply so administrators could bias the kernel’s reclaim behavior for a given machine’s workload, long before anyone was running isolated, per-application cgroups. Containers didn’t introduce a new concept here; they simply gave an existing, mature kernel mechanism a narrower, per-workload scope to operate in, which is part of why the underlying reasoning about anonymous versus file-backed pages, covered next, hasn’t changed at all in the container era — only the boundary it’s applied within has.

2Core Concepts: RAM, Swap, and Two Kinds of Pages

Swappiness only makes sense once you understand that the kernel treats memory pages as belonging to two very different families, each with its own reclaim story.

Anonymous pages vs. file-backed pages

Every page of memory a process uses falls into one of two broad categories. File-backed pages hold data that also exists on disk somewhere — a program’s executable code, a memory-mapped file, or the page cache holding recently-read file contents. Because a copy already exists on disk, the kernel can reclaim these pages cheaply: it just discards them, and re-reads from disk later if needed. Anonymous pages, by contrast, hold data that has no disk-backed origin at all — a process’s heap, its stack, dynamically allocated memory. There’s no disk copy to fall back on, so the only way to reclaim an anonymous page under pressure is to write it out to swap space first.

Simple Analogy

File-backed pages are like sticky notes copied from a reference book sitting on the shelf — if you need the desk space back, you can just throw the sticky note away, because the original book still has the information. Anonymous pages are like a handwritten letter that exists nowhere else — if you need the space, you can’t just discard it; you have to physically carry it to the storage room (swap) to keep it from being lost.

Where swappiness fits into this picture

When the kernel needs to free up memory, it has a choice: reclaim file-backed pages (cheap, but might mean re-reading files from disk later, hurting I/O performance) or swap out anonymous pages (writes to swap now, but the anonymous data itself might not be needed again soon). Swappiness is the dial that expresses a preference between these two options. A low value biases the kernel strongly toward reclaiming file-backed page cache first, only swapping anonymous pages as a last resort. A high value tells the kernel to treat anonymous-page swapping as a much more acceptable, earlier option.

0
Swappiness value that avoids swapping anonymous memory almost entirely
60
The traditional Linux host-wide default value
100
Swappiness value that treats swap as an equally eager reclaim target
!
Common Misconception

Setting swappiness to 0 does not mean “swap is completely disabled.” Even at 0, the kernel may still swap anonymous pages under severe enough memory pressure — it simply becomes the last resort rather than an early option. Disabling swap outright is a separate setting entirely.

3Architecture & Components: cgroups Memory Subsystem

Docker doesn’t invent its own memory-management engine. It configures the Linux kernel’s control groups (cgroups) memory subsystem, and simply exposes the relevant settings as friendly CLI flags.

The key virtual files behind the flags

Every container gets its own cgroup — a kernel-managed accounting and limiting boundary — and that cgroup exposes a handful of pseudo-files under the cgroup filesystem that directly control memory behavior. Docker’s CLI flags are, underneath, just writing values into these files on the container’s behalf.

Cgroup interface (conceptual)Docker flagPurpose
memory.limit_in_bytes--memoryHard cap on the RAM the container’s processes may use
memory.memsw.limit_in_bytes--memory-swapCombined cap on RAM + swap the container may use together
memory.swappiness--memory-swappinessHow eagerly this specific cgroup’s anonymous pages are swapped
memory.soft_limit_in_bytes--memory-reservationA soft target the kernel tries to respect under host-wide memory pressure
graph TD
    A["Docker CLI flag"] --> B["dockerd"]
    B --> C["containerd / runc"]
    C --> D["cgroup memory subsystem"]
    D --> E["memory.limit_in_bytes"]
    D --> F["memory.memsw.limit_in_bytes"]
    D --> G["memory.swappiness"]
    D --> H["memory.soft_limit_in_bytes"]
    E --> I["Kernel enforces RAM ceiling"]
    F --> J["Kernel enforces RAM+swap ceiling"]
    G --> K["Kernel biases reclaim toward or away from swap"]
        
FIG 1 — How a Docker memory flag becomes real kernel-enforced behavior

Per-container, not per-process

Because every process a container spawns lives inside that same cgroup, swappiness (and every other memory limit) applies collectively to the whole container — a container running five processes doesn’t get five independent memory budgets, it gets one shared budget and one shared swappiness value that the kernel applies uniformly to reclaim decisions across all of them.

i
Inheritance from the host

If a container’s swappiness is never explicitly set, it inherits the host’s global vm.swappiness value by default — which is exactly why two containers on two different hosts, with identical Docker run commands, can behave differently under memory pressure if the hosts themselves have different swappiness defaults configured.

From Docker command to running container: the translation path

It’s worth naming every layer this setting actually passes through, because troubleshooting often means figuring out which layer dropped or ignored it. A memory-related flag on docker run is first received by the Docker daemon, which translates it into the OCI runtime specification format used to describe how a container should be created. That specification is then handed to a lower-level container runtime, which is responsible for actually creating the cgroup and writing the appropriate values into its memory-control files before the container’s process is started inside it. Each of these layers is a place where a mismatch — an unsupported flag on a given kernel version, a runtime that doesn’t propagate a particular field — can silently prevent a setting from having the effect its name suggests.

Swap accounting must be enabled for any of this to work at all

On cgroup v1 systems in particular, tracking and limiting swap usage per cgroup is an optional kernel feature called swap accounting, and it has historically needed to be explicitly enabled — sometimes via a kernel boot parameter — before the memory-and-swap combined limit becomes meaningful at all. On a host where swap accounting isn’t enabled, Docker may still accept a --memory-swap value without error, but the kernel has no way to actually enforce a separate swap ceiling, which can be a confusing gap between configured intent and real enforcement.

4Internal Working: How the Kernel Reclaims Memory

Swappiness isn’t a simple on/off switch — it’s an input to a scoring algorithm the kernel runs whenever it needs to free memory. Understanding that algorithm, at least conceptually, demystifies why the number 0–100 behaves the way it does.

The LRU lists

The kernel tracks memory pages using Least-Recently-Used (LRU) lists — separate lists for anonymous pages and for file-backed pages, each roughly ordered from “recently used” to “not used in a while.” When memory needs to be freed, the kernel doesn’t reclaim randomly; it scans from the “not recently used” end of these lists, preferring pages that haven’t been touched in a while, on the theory that they’re least likely to be needed again soon.

How swappiness weights the scan

Rather than scanning only one list or the other, the kernel calculates a ratio of how much scanning effort to spend on the anonymous LRU list versus the file-backed LRU list, and swappiness is a direct input to that ratio. A low swappiness value skews the ratio heavily toward scanning (and reclaiming) file-backed pages, leaving anonymous pages alone unless there’s truly nothing else left to reclaim. A high swappiness value flattens that skew, making the kernel willing to scan and swap out anonymous pages proportionally much earlier, even while reclaimable file-backed pages still exist.

Simple Analogy

It’s like a librarian deciding which books to pull from limited shelf space: return-to-source books (file-backed, easy to get again from the main library) versus personal loose notes tucked inside books (anonymous, must be carried to an archive box if removed). Swappiness is the instruction the librarian is given for how many of each type to consider pulling, out of every ten books reviewed.

sequenceDiagram
    participant K as Kernel memory manager
    participant F as File-backed LRU list
    participant A as Anonymous LRU list
    participant S as Swap space
    K->>K: Memory pressure detected in cgroup
    K->>K: Compute scan ratio from swappiness value
    K->>F: Scan and reclaim cold file-backed pages
    alt Still need more memory
        K->>A: Scan cold anonymous pages
        A->>S: Write selected pages out to swap
    end
    Note over K: Higher swappiness shifts this ratio toward the Anonymous path sooner
        
FIG 2 — Swappiness as an input to the kernel’s page-reclaim scan ratio

Pressure is relative to the cgroup, not just the host

Under cgroup-enforced memory limits, this whole reclaim dance can be triggered purely by a single container approaching its own memory limit, even while the host machine as a whole has plenty of free RAM sitting idle. This is a frequent source of confusion: a container can be swapping heavily, or even get OOM-killed, on a host that free -m shows as mostly empty — because the constraint that matters is the container’s cgroup boundary, not the host’s total capacity.

What happens when a swapped page is needed again

Swapping a page out is only half the story — eventually, the process that owned it may try to access that memory again. When that happens, the CPU generates what’s called a page fault: the process’s execution pauses momentarily, the kernel notices the page it needs is out on swap rather than in RAM, and it reads that page back in from swap before letting the process continue. This round trip is exactly where the latency cost of swapping becomes visible to an application — a memory access that would normally take nanoseconds can instead take the time of an actual disk read, which is orders of magnitude slower even on fast SSD-backed swap.

Simple Analogy

It’s the moment someone at the desk suddenly needs that letter they sent down to the storage room earlier. Work pauses while someone walks to fetch it back — a delay that simply wouldn’t exist if the letter had never left the desk in the first place. The more often this round trip happens, the more those small delays add up into a workload that just “feels” slower under load.

Background reclaim vs. direct reclaim

The kernel actually has two modes for this process. A background kernel thread periodically wakes up and proactively reclaims memory while a cgroup is still comfortably under pressure, trying to stay ahead of demand. If allocation requests outpace that background work and memory becomes critically tight, the kernel falls back to direct reclaim — pausing the requesting process itself to free memory synchronously, right there, before it’s allowed to continue. Direct reclaim is considerably more disruptive to an application’s latency than background reclaim, and a well-chosen swappiness value, combined with adequate headroom in the memory limit, is part of what keeps a workload operating in the gentler, background-reclaim regime rather than repeatedly triggering the disruptive one.

5Data Flow & Lifecycle

Tracing memory pressure from a container’s launch to a potential out-of-memory event shows exactly when swappiness enters the picture, and when it’s bypassed entirely.

1

Container launch

Docker creates the container’s cgroup and writes its configured memory limit, swap limit, and swappiness value into the relevant cgroup files before the container’s process starts.

2

Normal operation

While memory usage stays comfortably under the limit, none of this matters yet — pages are allocated freely, and no reclaim or swap activity is triggered by the cgroup.

3

Approaching the limit

As usage nears the container’s memory ceiling, the kernel begins reclaiming memory from within that cgroup specifically, consulting the swappiness value to decide the balance between reclaiming file-backed pages and swapping anonymous ones.

4

Swap activity (if swap is allowed)

If the container’s swap limit permits it, anonymous pages selected by the reclaim algorithm are written out to swap space, and the freed RAM becomes available for new allocations inside the same cgroup.

5

Hitting the combined ceiling

If memory usage keeps growing until it reaches the container’s combined RAM+swap limit, there’s nowhere left to reclaim from — swap can’t absorb any more, and file-backed reclaim alone isn’t enough.

6

OOM killer intervenes

The kernel’s out-of-memory killer selects a process within the container’s cgroup to terminate, freeing its memory forcibly. Swappiness played no further role by this point — the limit itself, not the swappiness preference, determines when this last-resort step triggers.

!
The key distinction to remember

Swappiness shapes how the kernel behaves on the way toward a memory limit. It has no influence on whether a container eventually gets OOM-killed once it truly runs out of both RAM and available swap — that outcome is governed entirely by the memory and swap limits themselves.

6The Docker Flags, Explained in Full

Swappiness rarely gets tuned in isolation — it’s usually set alongside a handful of sibling flags that define the container’s overall memory envelope. Understanding how they interact is essential before touching any of them.

Hard RAM ceiling

–memory

Sets the maximum amount of physical RAM the container’s cgroup may use. This is the primary limit that everything else is defined relative to.

Combined ceiling

–memory-swap

Sets the maximum of RAM plus swap combined. Setting it equal to --memory effectively disables swap for the container; leaving it unset typically doubles the RAM limit as an implicit swap allowance.

The swappiness dial

–memory-swappiness

A value from 0–100 controlling how eagerly the container’s anonymous pages get swapped versus reclaiming file-backed pages first, as covered in the internal-working chapter.

Soft target

–memory-reservation

A lower, “soft” limit the kernel tries to respect specifically under host-wide memory pressure, without being a hard cap the container can never exceed on its own.

Why “swap equals memory” is the most common configuration

A very common production pattern is setting --memory-swap to the exact same value as --memory. Because the combined ceiling then equals the RAM ceiling exactly, there’s no headroom left for swap at all — the container simply cannot swap, no matter what its swappiness value says, because there’s zero swap allowance to write into. In this configuration, swappiness becomes irrelevant; it’s the swap allowance, not the swappiness dial, doing the real work of disabling swap.

–memory-swap settingEffective behavior
Equal to --memorySwap effectively disabled for the container — swappiness has nothing to act on
Greater than --memoryDifference becomes the container’s swap allowance — swappiness governs how eagerly it’s used
UnsetDocker typically allows the container an implicit swap allowance equal to the RAM limit
Set to -1Unlimited swap, bounded only by the host’s own swap space and configuration

The same settings in Compose and orchestrated deployments

Outside of direct docker run invocations, these same underlying limits are typically expressed through a service definition’s resource block in tools like Docker Compose, or through equivalent resource-request fields in higher-level orchestrators. The names and syntax differ across tools, but every one of them is ultimately configuring the same cgroup memory subsystem described in this tutorial — which is exactly why understanding the underlying mechanism pays off regardless of which specific tool or YAML schema a team happens to be using on top of it.

How memory-reservation behaves differently from a hard limit

It’s easy to initially confuse --memory-reservation with --memory, since both look like memory ceilings. The difference is enforcement strength: --memory is a hard boundary the container’s cgroup is never allowed to exceed, full stop. --memory-reservation is a softer target that only gets actively enforced when the host itself is under broader memory pressure — a container can legitimately exceed its reservation during a normal spike, and only gets nudged back toward it if the host as a whole needs the memory back for other workloads.

7The cgroup v1 vs. cgroup v2 Nuance

This is one of the most important — and most frequently missed — practical details in the entire topic, and it changes depending on which cgroup version a host is running.

What changed between versions

Docker’s --memory-swappiness flag was designed against the original cgroup v1 memory subsystem, which exposed a per-cgroup memory.swappiness file that could be tuned independently of the host’s global setting. When the kernel and major Linux distributions moved to cgroup v2, that per-cgroup swappiness control was not carried over in the same form — cgroup v2’s memory controller does not expose an equivalent per-cgroup swappiness setting in the way v1 did.

!
Practical consequence

On a host running cgroup v2 (the default on most current Linux distributions), Docker’s --memory-swappiness flag is effectively unsupported and is typically ignored, sometimes with a warning. Anyone tuning swappiness per-container needs to first check which cgroup version their Docker host is actually using, or the setting may silently have no effect at all.

Aspectcgroup v1cgroup v2
Per-container swappinessSupported via memory.swappinessNot exposed per-cgroup in the same way
Docker flag behavior--memory-swappiness works as documentedFlag generally ignored / unsupported
Effective swappiness sourcePer-container value if set, else host defaultHost-wide vm.swappiness generally governs behavior
Where to checkContainer’s cgroup memory filesHost-level sysctl configuration

What this means for tuning strategy

On cgroup v2 hosts, achieving meaningfully different swap behavior between containers generally has to happen through the swap allowance (via --memory-swap) rather than through swappiness itself, since the finer-grained per-container dial isn’t available in the same form. This is a good example of why understanding the underlying mechanism matters more than memorizing a flag — the flag’s real-world effect depends on infrastructure details that aren’t visible from the Docker command line alone.

Figuring out which version a host is actually running

Because so much of this chapter hinges on cgroup version, it’s worth knowing that this isn’t something Docker hides deliberately — it’s simply a property of how the host’s Linux distribution and kernel were configured, generally as part of the base OS setup rather than anything Docker-specific. Most current mainstream Linux distributions have defaulted to cgroup v2 for some time now, while older or more conservatively-configured hosts, and some specialized environments, may still run cgroup v1. When in doubt, checking the host’s cgroup configuration directly — rather than assuming based on distribution age alone — is the reliable way to know which behavior to expect before spending time tuning a flag that might not apply.

8The OOM Killer: What Happens When Limits Are Exceeded

Swappiness tuning is ultimately about buying time and shaping behavior before this final mechanism ever has to act — so it’s worth understanding on its own terms.

How the kernel chooses a victim

When a cgroup genuinely runs out of both reclaimable memory and available swap, the kernel’s out-of-memory (OOM) killer selects a process to terminate in order to free memory immediately. Within a container’s cgroup, this selection is typically based on an internal “badness” score influenced by how much memory each process is using, among other factors — the intent is to kill the process that will free the most memory for the least overall disruption, though in a single-process container, there’s often only one realistic candidate.

Simple Analogy

The OOM killer is the office’s last-resort rule when both the desk and the storage room are completely full and a new, urgent folder absolutely must be filed: something already there gets thrown out entirely, chosen by whichever item is judged least costly to lose, rather than everything grinding to a halt.

Container-scoped, not host-scoped

Crucially, this OOM event happens within the container’s own cgroup boundary when a per-container memory limit is set — the host can be sitting on gigabytes of free RAM while one specific, tightly-limited container gets a process killed purely because it exceeded its own individual ceiling. This is precisely the behavior that makes per-container memory and swap tuning meaningful in the first place: it isolates one workload’s memory misbehavior from affecting its neighbors.

i
Where to see it happen

An OOM-killed container process is visible both through the host’s kernel log, which records the event and which process was selected, and through Docker’s own container status, which typically reports an OOM-related exit reason when inspected after the fact.

Why single-process containers make this predictable

Most containers, by convention, run a single primary application process rather than the many competing processes a traditional multi-user host would have. This actually simplifies OOM behavior in practice: rather than the kernel weighing many candidate processes with different badness scores against each other, an OOM event inside a typical container usually means that container’s one main process is the one being terminated, and the container as a whole exits as a direct result. This predictability is part of why container orchestration systems can build reliable restart-on-failure behavior around OOM events — the failure mode is comparatively simple and consistent compared to a shared, multi-tenant host without this isolation.

9Advantages, Disadvantages & Trade-offs

There is no universally “correct” swappiness value — only a trade-off that should be matched deliberately to a workload’s actual tolerance for latency versus its tolerance for being killed under pressure.

Low Swappiness (near 0)

  • Keeps a working process’s active memory resident in fast RAM for longer.
  • Avoids the latency spike of reading swapped-out pages back from disk.
  • Well suited to latency-sensitive workloads like databases and in-memory caches.
  • Risk: less buffer against sudden memory spikes, making an OOM kill more likely once the true limit is reached.

High Swappiness (near 100)

  • Provides more breathing room before a hard OOM kill, by using swap earlier as a buffer.
  • Can help batch or background workloads tolerate temporary memory spikes gracefully.
  • Trade-off: introduces disk I/O latency whenever swapped pages are accessed again.
  • Poor fit for workloads sensitive to tail latency, since a single swapped-out page fault can stall a request noticeably.

Framing it as a spectrum, not a binary choice

Because swappiness is a full 0–100 range rather than an on/off switch, the practical tuning exercise for most teams is finding a middle value appropriate to a specific workload’s tolerance, rather than reflexively setting it to one extreme. A background batch-processing container that can tolerate occasional slow page faults is a very different case from a low-latency API server where even rare, brief stalls violate a service-level target — the “right” value genuinely differs between them.

Swap medium matters too

The severity of the latency trade-off described above depends heavily on what the swap space physically sits on. Swap backed by a fast NVMe SSD imposes a far smaller penalty per page fault than swap backed by a spinning hard disk or a slow network-attached volume — the same swappiness value can therefore produce meaningfully different real-world latency impact depending purely on the underlying hardware a host’s swap space lives on. This is worth checking before drawing conclusions from a benchmark run on different hardware than production actually uses.

“Swappiness doesn’t make memory pressure go away — it decides which kind of pain the workload experiences when pressure arrives.”

10Security Considerations

Swap and memory-limit tuning aren’t purely performance topics — a few real security implications are easy to overlook.

Swap can leave sensitive data on disk

Once a page of memory is swapped out, its contents — which could include anything a running process held in memory, from application secrets to decrypted data — now exist on disk, potentially outliving the process itself if swap space isn’t securely wiped or encrypted. Workloads that handle particularly sensitive in-memory data sometimes deliberately run with swap disabled for exactly this reason, independent of any performance consideration.

Memory limits as a noisy-neighbor defense

Per-container memory and swap limits, tuned deliberately, are one of the more effective tools for preventing a single misbehaving or compromised container from starving every other workload on a shared host of memory — without them, one runaway process can pressure the entire host’s memory management, affecting unrelated containers that had nothing to do with the problem.

Denial-of-service via memory exhaustion

A container with generous or unbounded memory and swap limits is a larger attack surface for memory-exhaustion style denial-of-service behavior, whether malicious or accidental — tight, deliberately chosen limits (with an appropriately matched swappiness) reduce the blast radius available to any single workload.

Swap-induced slowdowns as an availability concern, not just a performance one

It’s worth framing severe swap thrashing as more than a performance nuisance — a container repeatedly stalling on page faults under heavy swap pressure can become effectively unresponsive to health checks or requests, which downstream systems and orchestrators will often interpret as an availability failure. In that sense, badly-tuned swappiness combined with an overly generous swap allowance can produce outcomes that look, from the outside, indistinguishable from an outage, even though the process never actually crashed.

Compliance and data-handling requirements

Some regulatory or contractual data-handling standards place explicit constraints on where sensitive data may be persisted, even transiently — and swap space, being a form of disk persistence, can fall within scope of those constraints even though it’s usually thought of purely as a performance mechanism. Teams operating under such requirements sometimes need to treat “swap must remain disabled for this workload” as a compliance decision rather than only a performance one, which is worth flagging explicitly to anyone tuning these settings purely from a latency-optimization mindset.

!
Common Mistake

Leaving memory and swap limits unset “to be safe,” on the assumption that unlimited headroom avoids problems. In practice it just moves the risk from a contained, predictable OOM event inside one container to an unpredictable, host-wide memory-pressure event that can affect every workload sharing that host.

11Monitoring, Logging & Metrics

Swappiness misconfiguration tends to show up as one of two symptoms: unexplained latency spikes, or containers dying with no obvious application-level cause. Both have a predictable, small set of places to look, and building the habit of checking them together — rather than any single metric in isolation — is what actually turns this from guesswork into a repeatable diagnostic process.

Live overview

Per-container resource stats

Docker’s built-in live stats view shows current memory usage against a container’s configured limit — the first place to confirm whether a container is actually approaching its ceiling, and a natural starting point before digging into any of the more detailed sources below.

Detailed accounting

cgroup memory.stat

The cgroup’s own detailed memory statistics break down usage by category — including how much has actually been swapped, how much is page cache, and how much is genuinely active anonymous memory — giving a much finer picture than a single aggregate number ever could.

System-wide view

Host-level swap and memory pressure tools

Standard host memory-reporting tools reveal overall swap activity across the machine, useful for spotting whether pressure is isolated to one container or affecting the host broadly enough that a fix belongs at the host level rather than any single container’s configuration.

Post-mortem evidence

Kernel log / dmesg

OOM-killer events are recorded in the kernel log with details about which cgroup and process were involved — the definitive source of truth after a container has unexpectedly died, and usually the fastest way to confirm or rule out memory as the root cause of an incident.

SymptomLikely swappiness/memory-related cause
Occasional request latency spikes under loadAnonymous pages being swapped and then read back in, stalling the process briefly
Container exits unexpectedly with no application errorOOM killer terminated a process after hitting the combined memory+swap ceiling
High swap usage on the host with low per-container memory usage reportedAggregate swap activity from many containers, worth checking per-container breakdowns individually
Setting --memory-swappiness appears to have no effectHost is likely running cgroup v2, where the per-container flag is generally unsupported

Building a habit around these signals

None of these individual metrics is meaningful in isolation — a container showing nonzero swap usage isn’t automatically a problem, and a container with zero swap usage isn’t automatically well-tuned either. The useful practice is correlating swap activity with application-level latency metrics over the same time window: if swap usage rises and request latency rises in step, that’s a strong, concrete signal that swappiness (or the surrounding memory limits) genuinely need attention, rather than a change made on instinct alone.

12Design Patterns & Anti-patterns

Most swappiness-related incidents trace back to a small number of recurring misunderstandings about how the setting interacts with its sibling limits.

ANTI-PATTERN-01 Avoid
Problem

Setting a low swappiness value on a latency-sensitive container while leaving --memory and --memory-swap unset or very generous, expecting the low swappiness alone to “protect performance.”

Why It’s Harmful

Swappiness only shapes behavior on the way toward a limit — it does nothing to prevent the container from growing unbounded in the first place. Without a real memory ceiling, the workload can still consume excessive host resources long before swappiness ever becomes relevant.

Correct Approach

Set a deliberate, workload-appropriate --memory limit first, then tune swappiness (where the cgroup version supports it) to shape behavior specifically as that limit is approached.

ANTI-PATTERN-02 Avoid
Problem

Applying the exact same swappiness value uniformly across every container on a host, regardless of workload type — for example, using the same setting for a stateless web server and a stateful in-memory cache.

Why It’s Harmful

Different workloads have genuinely different tolerances for swap-induced latency versus OOM risk, as covered in the trade-offs chapter. A one-size-fits-all value optimizes for neither case well, and tends to be chosen more out of convenience than workload analysis.

Correct Approach

Treat swappiness (and the surrounding memory limits) as a per-service configuration decision, informed by each service’s actual latency sensitivity and memory-spike tolerance.

Good pattern: matching memory-reservation to expected steady-state usage

Setting --memory-reservation to a value close to a container’s expected normal memory footprint — below the hard --memory ceiling — gives the kernel a soft target to lean toward reclaiming back to under general host memory pressure, without imposing a hard cap the container can never legitimately exceed for a brief, real spike.

ANTI-PATTERN-03 Avoid
Problem

Copying a swappiness value from a blog post, forum answer, or another team’s configuration without validating it against this container’s own workload behavior and hardware.

Why It’s Harmful

As covered earlier, the right value depends on latency tolerance, swap medium, cgroup version, and whether the application already manages its own memory — a value that worked well for one context can be actively counterproductive in another that looks superficially similar.

Correct Approach

Treat any externally-sourced starting value as a hypothesis to validate under this specific workload’s real or simulated load, using the monitoring signals covered earlier, rather than a final answer.

13Best Practices & Common Mistakes

A practical checklist distilled from everything covered so far.

Do

Set a real --memory limit before touching swappiness

Swappiness only matters relative to an actual ceiling — tune the ceiling first, the reclaim behavior second.

Do

Check the host’s cgroup version first

Confirm whether the host runs cgroup v1 or v2 before assuming --memory-swappiness will have any effect at all.

Do

Match swappiness to workload latency sensitivity

Low values for latency-critical services; higher values are more tolerable for background or batch workloads.

Avoid

Leaving memory and swap limits fully unset in production

Unbounded containers shift risk from a contained, predictable event to an unpredictable, host-wide one.

Avoid

Assuming swap is disabled just because swappiness is low

Swap allowance and swappiness are separate settings; a low swappiness value with a generous swap allowance can still swap under real pressure.

Avoid

Guessing at values without monitoring

Treat swappiness tuning as an empirical exercise backed by real memory and latency metrics, not a one-time guess left unrevisited.

Do

Revisit tuning after workload or hardware changes

A value validated on one instance type, kernel version, or traffic pattern isn’t guaranteed to still be correct after a migration, upgrade, or significant load-profile shift — treat it as a setting worth periodically re-checking rather than a permanent fact.

Do

Document the reasoning, not just the number

A swappiness value on its own tells a future reader nothing about why it was chosen; recording the workload’s latency tolerance and the cgroup version it was tuned against saves real time during the next incident review.

14Real-World & Industry Examples

Memory swappiness tuning shows up across a range of real deployment contexts, each with a slightly different rationale.

In-memory databases and caches

Systems built around keeping their entire working set in RAM are classic candidates for a very low swappiness value, since any swap activity directly undermines the performance guarantee the system is designed to provide in the first place. Many such systems also ship their own explicit guidance recommending swap be disabled outright in production, precisely because their internal memory management, discussed earlier, already assumes full control over what stays resident.

Kubernetes and orchestrated environments

Kubernetes has historically discouraged or outright disallowed swap on nodes running kubelet-managed workloads, treating memory requests and limits as the primary tool for resource management instead — a broader ecosystem trend toward memory limits (rather than swappiness tuning) as the main lever, with swap support evolving as a more recent, carefully-gated feature rather than a default assumption. Teams migrating containerized workloads from a Docker-centric setup to such an orchestrator often need to re-examine any swap-dependent tuning they’d previously relied on.

Batch and CI workloads

Build agents and batch-processing containers that experience short, tolerable memory spikes are a reasonable fit for a higher swappiness value combined with a modest swap allowance, trading a little latency during rare spikes for a lower chance of an outright OOM kill mid-job — a mid-build failure is often more disruptive to a pipeline than a slightly slower build.

Multi-tenant hosting platforms

Providers running many unrelated customer workloads on shared hosts rely on the combination of memory limits, swap allowances, and swappiness as part of their broader resource-isolation strategy, ensuring one tenant’s memory behavior doesn’t degrade the experience of every other tenant sharing that machine. Per-tenant defaults here are often deliberately conservative, favoring predictability across many unrelated workloads over squeezing maximum performance out of any single one.

15A Practical Tuning Walkthrough

Pulling the previous chapters together into a repeatable way of reasoning about a new workload, without prescribing a single “correct” number.

1

Classify the workload’s latency sensitivity

Decide honestly whether occasional, brief page-fault stalls are tolerable for this specific service, or whether they would violate a real latency expectation. A service with a published or contractual response-time target belongs in a stricter category than an internal batch job nobody is actively waiting on.

2

Measure actual steady-state memory usage

Observe the container’s normal memory footprint under realistic load before choosing a hard limit, rather than guessing a round number. A limit set purely from intuition, without reference to real observed behavior, is one of the more common sources of both wasted headroom and surprise OOM events.

3

Set --memory with reasonable headroom above steady state

Give the container room for legitimate spikes without leaving the limit so loose that it stops meaningfully protecting the host.

4

Decide the swap allowance deliberately

Choose whether this workload should have any swap buffer at all via --memory-swap, based on the latency-sensitivity classification from step 1.

5

Set swappiness to match, where supported

On cgroup v1 hosts, set a swappiness value consistent with the workload’s tolerance; on cgroup v2 hosts, rely primarily on the swap allowance and host-level defaults instead.

6

Validate under real or simulated load

Watch memory stats, swap activity, and latency together under representative load, and adjust — tuning this correctly is rarely a one-shot exercise.

16OS-Level Swapping vs. Application-Level Memory Management

Swappiness tuning happens at the operating-system level, but many modern applications also manage their own memory internally — and the two layers don’t always agree on what “important” memory looks like.

Two independent memory managers, working blind to each other

A database or cache application typically maintains its own internal notion of “hot” data — a buffer pool, an in-process cache, frequently-accessed index structures — and decides for itself what to keep readily available versus what to evict or spill elsewhere. The Linux kernel’s reclaim algorithm, meanwhile, has no visibility into that application-level logic at all; it only sees raw memory pages and how recently the CPU has touched them. This means the kernel can, in principle, swap out exactly the pages an application considers its most critical, hot data, simply because the kernel’s own recency signal doesn’t align with the application’s own sense of importance.

Simple Analogy

Imagine two different people organizing the same desk: the worker knows which folders are for the client meeting starting in five minutes, but the office assistant tidying up only sees which folders haven’t been touched in the last ten minutes and moves those to storage — even if one of them happens to be exactly the folder needed for that imminent meeting. The two organizers are both acting reasonably from their own vantage point, but they aren’t coordinating.

Why this matters for swappiness choices

For applications that already do sophisticated internal memory management — many production databases fall into this category — it’s often preferable to keep the operating system’s own swapping behavior as conservative as possible (a low swappiness value, or swap disabled outright), and let the application’s own internal logic be the primary decision-maker about what stays resident. Layering an aggressive OS-level swappiness setting on top of an application that already manages its own memory carefully tends to fight against that application’s own tuning rather than complement it.

i
A useful question to ask

Before tuning swappiness for a given container, it’s worth checking whether the application inside it already has its own memory or cache-sizing configuration. If it does, that configuration — sized appropriately relative to the container’s --memory limit — is usually a more effective lever than OS-level swappiness for controlling that application’s actual performance under pressure.

17Frequently Asked Questions

Q1Does setting swappiness to 0 completely disable swap for a container?

No. It makes the kernel avoid swapping anonymous pages as long as possible, but under severe enough pressure the kernel may still swap as a last resort, provided the container has any swap allowance available at all.

Q2What’s the actual, reliable way to disable swap for a container entirely?

Set --memory-swap equal to --memory. This removes any swap headroom at all, which is a more direct and reliable guarantee than relying on swappiness alone.

Q3Why did changing --memory-swappiness have no visible effect on my container?

The host is very likely running cgroup v2, where Docker’s per-container swappiness flag is generally unsupported, and the host’s global swappiness setting governs behavior instead.

Q4Is a higher swappiness value always worse for performance?

Not universally — it depends on the workload. For latency-critical services it’s usually undesirable, but for workloads that can tolerate occasional stalls in exchange for more headroom before an OOM kill, a higher value can be a reasonable, deliberate trade-off.

Q5Does a low swappiness value protect a container from ever being OOM-killed?

No. Swappiness only influences behavior on the way toward the memory and swap limits; once those limits are genuinely exhausted, the OOM killer activates regardless of the swappiness setting.

Q6Should every container in a cluster use the same swappiness value?

Not necessarily — different workloads have different latency and memory-spike tolerances, and treating swappiness as a per-service decision usually produces better outcomes than a single uniform value applied everywhere.

Q7Does swap usage inside a container show up in the host’s overall swap metrics?

Yes — a container’s swap activity is real, host-level swap usage under the hood; the cgroup boundary controls and accounts for it, but the underlying swap space itself is a shared host resource.

Q8Is disabling swap entirely always the safest choice for security-sensitive workloads?

It’s a reasonable and common choice specifically to prevent sensitive in-memory data from being written to disk, though it does trade away the extra headroom swap would otherwise provide before an OOM event — a deliberate security-versus-resilience trade-off rather than a universally “safest” default.

Q9Can an application’s own internal cache sizing make OS-level swappiness tuning unnecessary?

Often, largely — if an application already manages its own memory carefully relative to the container’s limit, OS-level swappiness becomes a secondary, backstop concern rather than the primary tool for controlling that application’s behavior.

Q10Does the physical hardware backing swap space change what swappiness value makes sense?

Yes — the latency penalty of a page fault against swap depends heavily on whether swap sits on fast SSD storage or slower media, so the same swappiness value can be far more or less costly depending purely on the underlying hardware.

18Summary and Key Takeaways

Memory swappiness tuning is, at its core, a single question repeated at kernel scale millions of times a second: when memory runs low, which kind of page gets reclaimed first — the kind that can be cheaply discarded, or the kind that has to be carried out to slower disk-backed swap space? Docker exposes this same decision on a per-container basis by writing into the cgroup memory subsystem’s files, but the real behavior always depends on the surrounding memory and swap limits, and on which cgroup version the host is actually running. Tuned thoughtfully and matched to a workload’s genuine latency tolerance, swappiness is a quiet but meaningful lever between predictable performance and predictable resilience. It is, deliberately, a small piece of a much larger picture — memory limits, swap allowances, application-level memory management, and monitoring all have to be reasoned about together, and no single flag substitutes for understanding how they interact.

Key Takeaways

  • Swappiness (0–100) controls a preference, not a switch — it biases the kernel’s reclaim algorithm between file-backed pages and anonymous pages, it doesn’t turn swap on or off by itself.
  • Docker’s --memory-swappiness writes into the cgroup memory subsystem — the same mechanism the host-wide vm.swappiness setting has always used, just scoped to one container.
  • The swap allowance and swappiness are separate concerns — --memory-swap decides whether swap is even available; swappiness decides how eagerly it’s used once it is.
  • cgroup v2 hosts generally don’t support per-container swappiness — this single fact explains most “the flag didn’t do anything” surprises in the wild.
  • The OOM killer is the true last resort — swappiness shapes the path toward a memory limit, but the limit itself decides whether a process eventually gets killed.
  • There is no universally correct value — the right swappiness setting depends on a workload’s tolerance for swap-induced latency versus its tolerance for being killed under pressure.
  • Tune limits before swappiness — a real, deliberate --memory ceiling matters more to overall stability than the swappiness value layered on top of it.