Sample audit: lab environment, not a client
A container exits 137, and the machine under it still has memory free. The exit code alone does not say whether a memory cap killed it.
Set --memory and --memory-swap to the same value, and set that value above the memory the process has to hold. Then prove the kill with three readings together: exit 137, OOMKilled true, and an oom event. This does not apply when docker inspect shows no memory cap, or when exit 137 arrives with OOMKilled false. Those are different failures. This sample does not size a real application, and it does not name a host to buy.
Written audit — lab memory cap
Client: none. This is a sample of the report shape. No person hired us, and no production system was opened.
Problem: a container dies with exit 137, and it is not clear whether a memory cap caused the kill.
Fee paid: none. No payment date.
Access: the published lab record on Docker memory limits, read 25 Oct 2026. No SSH. No production change. We did not re-run the containers for this sample.
Delivered: 25 Oct 2026.
From: ProDevs team.
What you asked
There is no client sentence to quote. The failure this sample answers is the one the lab recorded: a process asked to hold more memory than its cap, with swap turned off for that container, and the container died while the host still reported tens of gigabytes.
There was no free diagnostic, because there is no client. On a real job the free note would say fit or no-fit before any fee. This document only shows what the written audit looks like when the evidence is a lab we already published.
What we looked at
On 29 September 2026 the lab ran two throwaway alpine containers on a Linux host that already had Docker Engine 28.5.1 (linux/amd64). docker info printed MemoryLimit=true, SwapLimit=true, MemTotal=50514534400, and Total Memory: 47.05GiB. Warnings were null. There was no WARNING: No swap limit support line. The image was already on the host. The daemon was not changed. Both runs set --cpus 0.25 and set --memory-swap equal to --memory, which turns swap off for the container. The payload was anonymous memory, rewritten from NUL bytes to the letter A.
The first attempt stored dd if=/dev/zero in a shell variable. Both containers printed bytes:0 and exited 0. A shell drops NUL bytes from a command substitution, so the variable never held the allocation, and the echo of its length was zero. That attempt did not test the cap. The rerun pipes dd through tr so the variable holds the bytes. The table on the live page is that rerun. The log has no docker stats line and no dmesg line.
| Run | Flags | Allocation | What the log printed |
|---|---|---|---|
| A | --memory 64m --memory-swap 64m --cpus 0.25 |
count 8, block 1048576 | bytes:8388608. Client exit 0. Inspect memory and memory-swap 67108864 and 67108864. OOMKilled false. ExitCode 0. Started 2026-09-29T16:17:26.19210419Z, finished 2026-09-29T16:17:28.705774667Z. |
| B | --memory 32m --memory-swap 32m --cpus 0.25 |
count 48, block 1048576 | No bytes: line. Client exit 137. Inspect memory and memory-swap 33554432 and 33554432. OOMKilled true. ExitCode 137. Started 2026-09-29T16:17:29.21545262Z, finished 2026-09-29T16:17:36.427915673Z. |
The page states the arithmetic: 8 × 1048576 = 8388608, which is the length run A printed. 48 × 1048576 = 50331648, the hold run B asked for. Run B printed no bytes: line. 64 × 1024 × 1024 = 67108864 and 32 × 1024 × 1024 = 33554432, the two inspect pairs.
A 10-minute docker events filter for oom on that container printed no line (events_exit=0). A second query, 30 minutes wide, with no container filter, printed time 1790698656 and action oom. The page states that Unix time is 2026-09-29T16:17:36Z, the same second as run B’s finished timestamp.
We did not open a client VPS, a registry, or an application log. We did not change a running service.
What we could not see
The published record does not name the host. This sample will not invent a hostname.
The same log has no docker stats sample, so this audit cannot say what “ordinary” memory was. The 1.5× planning start on that page was not measured in these two runs. We will not treat it as a result.
There is no dmesg line and no journalctl line. The kernel text around the kill is not in the record.
The payload is a synthetic alpine allocation, not an application import, test suite, or database. A real process can die for a different reason under the same flags.
The lab was docker run. Compose was not run. A Compose file on the page is the same policy written down. It is not a second measurement.
We cannot see what a client’s container held, because no client sent access.
Causes
- High. The hard cap was under the hold, and swap was off. Run B asked to hold 48 × 1,048,576 bytes under a 32 MiB cap with
--memory-swapequal to--memory. It printed nobytes:line, exited 137, andOOMKilledwas true. Run A held 8 × 1,048,576 bytes under a 64 MiB cap with the same swap setting, printedbytes:8388608, exited 0, andOOMKilledwas false. Docker’s memory line for the host was 47.05GiB, so the kill was the 32 MiB ceiling, not an empty machine. Evidence is the inspect pairs and the exit codes on Docker memory limits. - Medium. A short events query hides the
oomevent. Exit 137 means signal 9. It is an out-of-memory kill only when inspect showsOOMKilledtrue and the events stream showsoom. The 10-minute filter on the container printed nothing. The 30-minute query printedoomat1790698656, which the page places in the same second as run B’s finish. Stopping at the empty query would call this kill something else.
The first attempt is not a cause of the kill. It is why the first log was useless. Both containers printed bytes:0 and exited 0 because the shell dropped the NUL bytes. Piping dd through tr made the variable hold the allocation, and only then did the 32 MiB cap kill run B. That is the broken measurement, the retry, and the reading that fixed it.
Shareable fact: On 29 September 2026, a 32 MiB cap with swap off killed a request to hold 48 × 1,048,576 bytes (exit 137, OOMKilled true) on a host Docker reported as 47.05GiB. Caveat: the payload was throwaway alpine, not an application; the 10-minute events query was empty until the window was 30 minutes; and the host name is not in the published record. Source: Docker memory limits.
Findings you can use without us
- On the next exit 137, read the three fields before you change anything else. The page’s inspect line is:
docker inspect mem-cap --format 'memory={{.HostConfig.Memory}} memorySwap={{.HostConfig.MemorySwap}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Status={{.State.Status}} StartedAt={{.State.StartedAt}} FinishedAt={{.State.FinishedAt}}'
- If
OOMKilledis true, raise--memoryand--memory-swaptogether, or hold less. Do not raise only one of them when you want swap to stay off. Equal values are what turned swap off in both runs. - If the events command prints nothing, widen the window before you decide the kill was not an
oom. The narrow filter in this lab was empty. The wider one was not. - Do not use
6mas a size. The page states that6mis the smallest value Docker accepts for--memory. It is not a result from this lab, and it is not a service size. - If you repeat the lab’s own payload, pipe
ddthroughtr. A directdd if=/dev/zerointo a shell variable printsbytes:0and exits 0, which looks like a pass and is not one. - Do not paste API keys, passwords, or tokens into email while you do this.
What not to build
Do not buy a larger machine to explain run B. The host line was already 47.05GiB. The ceiling that fired was 32 MiB.
Do not turn the OOM killer off to stop the crash. The page’s record of this lab is the killer doing what a cap is for. Disabling it was not part of the run, and this sample will not recommend it.
Do not treat this as a Kubernetes limit, a language heap setting, or a move to another orchestrator. The published guide says those are other jobs. They were not this failure.
Do not adopt the page’s 1.5× rule as a measured size from this audit. These two runs did not sample docker stats.
Roadmap
- On the next exit 137, record
OOMKilled,ExitCode, and an events query wide enough to coverFinishedAt. This is the check. It is not a build. You can do it from the command above. - If
OOMKilledis true, set--memoryand--memory-swapto the same value, above the hold the process actually needs, and run that job once. Include this only if a later look at a real host shows the same three signals. This sample did not open such a host. - Leave a host resize, a Compose migration, and any new platform out of this problem. The lab did not show that the machine was short of memory.
Nothing in that list is marked “include in a fixed build.” There is no client system to change.
Go / no-go
No-go for a fixed build.
The failure in the record is local and already measured, and the change is a pair of flags plus one inspect. A throwaway alpine container on a host we are not naming is not a production system we can hand back. A fixed build would need a host someone asked us to change, access they agreed in writing, and a job that is larger than setting the cap. None of that exists here. You keep this sample. Nothing on any system changes because of it.
Credit
No fee was paid, so there is no credit and no deadline.
The public rule, if it applied, is on the services page: the audit fee is credited 100% toward the fixed build if you proceed within 30 days. The paid audit band on that page is ₹15,000–₹25,000. The standard point named there is ₹20,000. Builds for one scoped infra fix sit in the ₹45,000–₹1,00,000 band. The credit reduces the build price. It is not paid out as cash. If you do not start, you keep the findings and the credit ends. If the written findings are not delivered, the audit fee is refunded. This sample delivered a reading of a published lab, not a client audit, so that refund rule has no invoice to attach to.
Arithmetic only, from the edges printed on that page, not a quote:
| Audit fee on the page | Build price on the page | Net |
|---|---|---|
| ₹20,000 (standard) | ₹45,000 | ₹25,000 |
| ₹20,000 (standard) | ₹1,00,000 | ₹80,000 |
That page also prints ₹15,000 against a ₹45,000 build (net ₹30,000) and ₹25,000 against a ₹1,00,000 build (net ₹75,000). A live quote would show one build price, the credit, and the net. This sample does not pick that price. A quote below ₹45,000 is not used to absorb a credit. Half of the net is the start payment and half is the go-live payment, when a quote exists. Work starts only after written acceptance.
The five-working-day audit clock on that page is not running. Day 1 needs a cleared payment and agreed access. This sample has neither.
Fixed-build quote
Not issued.
A go decision would carry a proposal id, an approach limited to the roadmap items marked include, checkable deliverables, the public exclusions, three price lines, the credit dates, a 50/50 split of the net, and a schedule line. This audit’s decision is no-go, so there is no proposal id, no start payment, and no pay link.
The schedule line stays unwritten. There is no public build duration, and this sample will not invent days or a go-live date.
Third-party API, model, and hosting fees are not inside a build price. None were incurred here.
Acceptance of a real quote is a written reply of the form “I accept proposal [id]”, plus the start payment. Silence is not acceptance. Until that reply, nothing changes on the systems in question. This sample has no id to accept.
If this exit 137 is on a host you run, send that one problem to contact@prodevs.in.
