DeployEasy
DockerIntermediate

How to Fix Docker Exit Code 137 on a VPS

Diagnose OOM kills, RAM pressure, swap, and heavy Docker builds before changing a small VPS or restarting production services.

· 5 min read· 912 words
Table of contents

How to Fix Docker Exit Code 137 on a VPS

When docker build, docker compose up, or a Node.js build suddenly prints Killed, the container may exit with code 137. On a small VPS, the most common cause is memory pressure, but the number alone is not proof of an out-of-memory kill.

Exit code 137 is 128 + 9: the process received SIGKILL. The Linux OOM killer, docker kill, a forced stop, or an external supervisor can all produce the same result. Collect evidence first, then choose the smallest safe fix.

What to check first

Find containers that exited with code 137:

docker ps -a --filter "exited=137"

Inspect the container state:

docker inspect web \
  --format 'ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}}'

If the output says OOMKilled=true, Docker confirms that the container was terminated by an out-of-memory condition. If it is false, continue with the kernel and service logs because the process may have received SIGKILL elsewhere.

Confirm an OOM event on Ubuntu

Search recent kernel messages:

sudo journalctl -k --since "30 minutes ago" | grep -Ei 'oom|killed process|out of memory'

On systems where dmesg is available:

sudo dmesg -T | grep -Ei 'oom|killed process|out of memory'

A line such as this confirms the kernel killed a process:

Out of memory: Killed process 18420 (node) total-vm:2097152kB, anon-rss:812340kB

Inspect RAM, swap, and active processes

Run these commands before stopping services:

free -h
swapon --show
ps aux --sort=-%mem | head -n 12
docker stats --no-stream

Pay attention to available, not only free. Linux uses unused memory for cache, and that cache can often be reclaimed. Compare the result with the OOM log and the container’s OOMKilled field.

Reduce memory pressure safely

Stop work that is not needed

List running services first:

sudo systemctl --type=service --state=running
docker compose ls

Stop only a stack you understand:

docker compose -p old-project down

Do not randomly stop a database, reverse proxy, or monitoring service that may contain production data or be required by another application.

Add a swap file when appropriate

Swap can absorb short memory spikes, but it is not a replacement for enough RAM and it can make a heavily loaded server slow:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

Check that /swapfile is not already configured before appending to /etc/fstab. On a busy production server, schedule this change and monitor disk space and latency.

Limit container memory deliberately

For a Compose service, set a limit that leaves room for the host and other services:

services:
  worker:
    image: ghcr.io/example/worker:sha-abc123
    mem_limit: 512m
    restart: unless-stopped

A limit prevents one container from consuming the whole host, but a limit that is too low will cause the container itself to be killed. Measure the application before selecting it.

Avoid building large images on a tiny VPS

The most reliable fix for frontend or Node.js builds is to build in CI and pull a ready-made image:

GitHub Actions builds the image

GHCR stores the image

The VPS pulls and runs the image

Use a multi-stage Dockerfile, .dockerignore, dependency caching, and npm ci. Keep build tools out of the runtime image. This reduces both build memory and runtime attack surface.

Check for other causes of SIGKILL

If there is no OOM evidence, inspect the deployment runner, systemd, and Docker events:

docker events --since 30m
sudo journalctl -u docker --since "30 minutes ago"
sudo journalctl -u your-service --since "30 minutes ago"

Also check whether a deployment timeout, health-check supervisor, or manual docker kill issued the signal. Do not add swap blindly when the process is being deliberately stopped by another system.

Verify after the fix

Start the service and watch it rather than assuming the problem is solved:

docker compose up -d web
docker compose ps
docker compose logs --tail=100 -f web

In another terminal, watch memory while the application warms up:

watch -n 2 'free -h; docker stats --no-stream'

Then call the health endpoint and confirm the public route through Nginx. Record the old and new memory usage so the next capacity decision is based on data.

Rollback and prevention

Keep the previous image tag available. If the new release consumes too much memory, redeploy the last known-good tag before investigating further:

export IMAGE_TAG=sha-LAST_KNOWN_GOOD
docker compose pull web
docker compose up -d web
curl --fail http://127.0.0.1:3000/health

Prevent repeat incidents with image builds outside the VPS, resource monitoring, a tested health check, a small swap buffer where appropriate, and alerts for available memory and disk space.

Frequently asked questions

Is Exit Code 137 always an out-of-memory error?

No. It means SIGKILL. OOMKilled=true and kernel logs make an OOM diagnosis much stronger.

Will adding swap always fix the issue?

No. Swap helps with short spikes, but sustained memory demand still requires reducing the workload or increasing RAM.

Can I increase Docker memory without changing the VPS?

You can set per-container limits, but limits only control allocation. They do not create additional host memory.

Conclusion

Treat Exit Code 137 as a symptom. Inspect Docker state, kernel logs, RAM, swap, and deployment events; then move heavy builds to CI, tune resource limits, and keep a rollback image ready.

Continue reading