Docker Container Memory Limits: A Practical Guide (2026)

This post contains affiliate links. If you purchase through our links, we may earn a commission at no extra cost to you.

By default, a Docker container can eat as much RAM as the Linux kernel lets it — there’s no ceiling unless you set one. That’s fine on a 64GB dedicated box. It’s a real problem on the $5–6/month, 1GB VPS most indie hackers actually deploy on, where a single memory leak can take down your app, your database, and your reverse proxy in one shot. Getting docker container memory limits right isn’t about memorizing a flag — it’s about picking a number that won’t crash the box or waste half of it.

Terminal showing docker stats output for three containers — app, postgres, and nginx — sharing a 1GB VPS
Terminal showing docker stats output for three containers — app, postgres, and nginx — sharing a 1GB VPS

Why Docker Containers Need a Memory Limit

Docker’s own documentation is blunt about this: test your application to understand its real memory requirements, run it only on hosts with adequate resources, and limit how much memory any single container can consume. Skip that last step and you’re one bad deploy away from an outage that has nothing to do with your code — just a container that grew without a ceiling.

This matters more on small infrastructure than on large. A 64GB server can absorb a leaking container for hours before anyone notices. A 1GB VPS running an app, a database, and a proxy has no slack at all — one runaway process degrades or kills everything else sharing the box.

How Docker Enforces Memory Limits

Docker doesn’t invent its own memory enforcement — it hands the job to Linux cgroups. When you run docker run -m 512m nginx, Docker writes that value into the container’s cgroup memory controller (memory.max on cgroup v2 hosts, memory.limit_in_bytes on cgroup v1). Cross that ceiling and the kernel’s out-of-memory killer picks a process inside that cgroup and kills it — no warning, no cleanup.

You can read the live numbers directly instead of trusting docker stats alone: docker exec mycontainer cat /sys/fs/cgroup/memory.current shows current usage, and cat /sys/fs/cgroup/memory.max shows the configured ceiling. Worth knowing: docker stats reports memory after subtracting page cache, so the number you see is closer to real application memory than the cgroup’s raw accounting — which is why docker stats can look fine right up until the kernel kills something.

How to Limit Docker Memory: Flags and Syntax

The core flag is -m or --memory, and it accepts a number plus a suffix — b, k, m, or g. The minimum Docker allows is 6m; anything smaller throws an error.

# Limit a container to 512MB
docker run -m 512m nginx

# Limit a container to 2GB
docker run --memory 2g my-app

Three related flags shape how that limit behaves, not just what it is:

  • --memory-swap sets the combined RAM + swap ceiling, not swap alone. --memory=300m --memory-swap=1g gives the container 300MB of RAM plus 700MB of swap. Setting --memory-swap equal to --memory disables swap entirely — the common choice when you’d rather fail fast than thrash to disk:
# No swap — hit the OOM killer instead of slowing to a crawl
docker run --memory=512m --memory-swap=512m my-webapp
  • --memory-reservation is a soft floor below the hard cap. Docker only tries to push usage back toward it when the host is under pressure; the container can burst above it freely otherwise. This mirrors the “reservation vs. limit” pattern Kubernetes and Compose both use.
  • --oom-kill-disable stops the kernel from killing the container on OOM. Docker’s guidance is explicit: only use this alongside --memory — without a cap, a container with OOM-kill disabled can consume all host memory and take everything else down with it.

Docker Memory Limit Example: docker-compose.yml

Most indie hacker stacks aren’t a single container — they’re an app, a database, and a proxy running together, which is exactly where docker run flags stop being practical. Compose handles this through the deploy.resources block:

services:
  app:
    image: my-app:latest
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '1.0'
          memory: 1G
  postgres:
    image: postgres:16
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M
  nginx:
    image: nginx:alpine
    deploy:
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M

limits.memory is the hard cap, reservations.memory is the soft floor Docker tries to guarantee. One caveat worth knowing: the deploy key’s resource limits were historically Swarm-only in older Compose releases. Current Compose v2 generally honors them on a plain docker compose up, but if your limits don’t seem to apply, confirm your installed Compose version actually supports this outside Swarm mode before assuming your YAML is wrong. Check live usage per service with docker compose stats app.

docker-compose.yml file with memory limits highlighted for app, postgres, and nginx services
docker-compose.yml file with memory limits highlighted for app, postgres, and nginx services

Diagnosing an OOM Kill (Exit Code 137)

Exit code 137 is 128 + 9 — SIGKILL. It means the container’s main process was killed abruptly, with no chance to clean up. The usual cause is the OOM killer, triggered by either the container’s own --memory limit or the host running out of RAM generally.

Work through it in order:

  1. Check if Docker itself recorded the kill: docker inspect <container_id> --format='{{.State.OOMKilled}}'. true confirms it exceeded its memory limit.
  2. If OOMKilled is false but the exit code is still 137, check dmesg — the host, not just the container, may have run out of memory.
  3. If 137 happens during docker stop, the cause usually isn’t memory at all — the app isn’t handling SIGTERM fast enough within Docker’s stop timeout, and Docker escalates to SIGKILL. That’s a shutdown-handling bug, not a sizing bug.
  4. For suspected leaks, watch docker stats or the cgroup files over time instead of reacting to one kill event.

A mistake that shows up across nearly every source on this topic: treating “just raise the limit” as the universal fix. It’s the right move only when the container is genuinely under-provisioned. If the real cause is a memory leak, an unbounded cache, or a shutdown bug, raising the limit just delays the next crash.

Why Node.js and Java Containers Get Killed Even When They “Fit”

Setting a cgroup limit doesn’t tell a language runtime how much memory it’s allowed to use — and this mismatch is the single most common cause of confusing OOM kills in application containers.

Node.js: older versions size the V8 heap based on total host memory, not the container’s cgroup limit, so a container can get OOM-killed while using only 50–60% of its stated cap because V8 never felt enough pressure to garbage-collect. Node.js 20+ made heap sizing container-aware by default, but plenty of production images still run older LTS releases or set NODE_OPTIONS=--max-old-space-size incorrectly. A workable rule of thumb: allocate 60–75% of the container’s memory limit to the V8 heap, leaving the rest for buffers and native memory — a 512MB container might use --max-old-space-size=350, keeping roughly 160MB in reserve. Think of it as container limit = heap + buffers/native memory + safety margin, not container limit = heap.

Java: the fix is -XX:+UseContainerSupport (on by default in modern JDKs) paired with -XX:MaxRAMPercentage, which sizes the heap as a percentage of the detected container limit instead of a fixed -Xmx value that may badly over- or under-shoot the actual cgroup ceiling.

The underlying principle applies to any runtime that manages its own heap independently of the OS: the cgroup limit constrains total resident set size — heap plus everything else — so a runtime that doesn’t know about that ceiling will guess wrong, and it usually guesses too high.

Diagram showing a 512MB container split into V8 heap, buffers, and safety margin
Diagram showing a 512MB container split into V8 heap, buffers, and safety margin

Choosing the Right Docker Container Memory Limit for Your VPS

Here’s where nearly every guide on this topic quietly gives up. The standard advice — “test under realistic load, watch docker stats, set the limit slightly above observed peak” — is technically correct and answers nothing useful if you’re staring at a fresh $6/month VPS and three containers you haven’t deployed yet.

The honest answer is that sizing is situational: it depends on your VPS’s total RAM, how many containers are sharing it, and what each one actually does. Rather than manually watching docker stats for days and doing napkin math across every service on the box, you can plug your VPS size and container count into our Docker RAM Calculator and get a starting allocation to test from — it turns “I have 1GB and three containers” into actual --memory values for your app, database, and proxy.

If you’re consistently hitting memory pressure even after right-sizing each container, the fix isn’t a bigger number pulled out of thin air — it’s more headroom on the box itself. For context, here’s what that costs as of mid-2026:

ProviderPlanRAMPrice
Vultr Cloud Compute (Regular)Entry1GB$5/month
Vultr Cloud Compute (High Performance)Entry NVMe1GB$6/month
Hetzner CX232 vCPU, 40GB NVMe, 20TB traffic4GB~€5.49/month

Prices last checked: 2026-07-16.

For most Docker-on-a-budget setups, jumping from a 1GB box to Hetzner’s 4GB CX23 buys you real headroom for less than doubling your bill — worth it the moment you’re regularly seeing exit code 137 across multiple containers rather than just one misbehaving service. If you’d rather stay in the Vultr ecosystem and just bump plan size, Vultr currently gives new accounts $300 in free credit for 30 days — enough runway to test a larger instance without committing to it.

🎁 Get $300 free credit when you sign up (valid for 30 days, limited-time offer)Claim your credit →

We’ve also covered the deployment side directly — our full Docker on Vultr setup guide walks through getting the daemon running before you start tuning memory limits, and our Vultr $6 vs Hetzner CX23 comparison breaks down that entry-tier pricing decision in more depth.

Related reading: deploying Docker on a Vultr VPS, sizing RAM for a Minecraft server.

FAQ: Docker Container Memory Limits

How do I check a running container’s current memory usage?

Run docker stats <container_name> for a live view, or docker exec <container_name> cat /sys/fs/cgroup/memory.current for the raw cgroup number without page-cache adjustments.

What’s the difference between --memory and --memory-reservation?

--memory is a hard cap — cross it and the OOM killer fires. --memory-reservation is a soft floor Docker only enforces when the host is under memory pressure; the container can burst above it freely otherwise.

Does raising the memory limit always fix exit code 137?

No. It fixes genuine under-provisioning, but not memory leaks, unbounded caches, or shutdown-handling bugs — those will resurface at any limit you set.

Do I need to set memory limits in docker-compose.yml if I’m not using Swarm?

Current Compose v2 releases generally honor the deploy.resources block on a plain docker compose up, but this was Swarm-only in older versions. Check your installed Compose version if the limits don’t seem to apply.

Why does my Node.js container get OOM-killed when docker stats shows it’s under the limit?

Older Node versions size the V8 heap off total host memory, not the container’s cgroup limit, so the process can be killed before V8 ever feels pressure to garbage-collect. Node 20+ fixes this by default — check your base image version first.

Where to Start

Set a hard --memory limit on every container you run in production — an unset limit isn’t a safety default, it’s a missing one. Start with --memory-swap equal to --memory so you fail fast instead of thrashing to disk, add --memory-reservation if you’re running multiple containers that need to share headroom fairly, and check your runtime’s heap flags (--max-old-space-size, -XX:MaxRAMPercentage) before assuming a cgroup limit alone will protect you. If you’re not sure what numbers to start with, run your VPS size and container list through the Docker RAM Calculator rather than guessing — it’s faster than watching docker stats for a week and safer than finding out the hard way at 2 a.m.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top