DeployEasy
DockerCơ bản

Docker bị Killed hoặc Exit Code 137 trên VPS: Cách xác định đúng nguyên nhân

Hướng dẫn kiểm tra Docker Exit Code 137, OOMKilled, RAM, swap và cách tối ưu quá trình build trên VPS cấu hình thấp.

· 8 phút đọc· 1.565 từ
Mục lục bài viết

Khi chạy docker build, docker compose up hoặc build một dự án Next.js trên VPS, bạn có thể thấy tiến trình dừng đột ngột với chữ Killed, container thoát bằng mã 137, hoặc log không nói rõ lỗi nằm ở đâu. Trên VPS chỉ có khoảng 1 GB RAM, nguyên nhân thường gặp là hệ thống thiếu bộ nhớ và Linux buộc phải kết thúc một tiến trình để tự bảo vệ.

Tuy nhiên, Exit Code 137 không tự động đồng nghĩa với thiếu RAM. Mã này cho biết tiến trình nhận tín hiệu SIGKILL — tín hiệu số 9. Nó có thể đến từ OOM Killer, từ lệnh docker kill, từ việc dừng cưỡng bức sau khi hết thời gian chờ, hoặc từ một hệ thống quản lý bên ngoài. Vì vậy, cách sửa tốt nhất là thu thập bằng chứng trước khi thêm swap hoặc tăng RAM.

Môi trường demo: Ubuntu Server, Docker Engine, Docker Compose và một ứng dụng Node.js. Output trong ảnh là mô phỏng từ lab có thể tái hiện; tên container và số liệu trên VPS của bạn sẽ khác.

Luồng kiểm tra Exit Code 137

1. Dấu hiệu thường gặp

Bạn có thể gặp một trong các tình huống sau:

RUN npm run build
...
Killed
ERROR: process "/bin/sh -c npm run build" did not complete successfully

Hoặc container đã chạy rồi tự dừng:

docker compose ps -a
NAME        IMAGE          STATUS
web         my-web:1.0     Exited (137) 25 seconds ago

137 được hiểu theo công thức 128 + 9, trong đó 9SIGKILL. Đây là tín hiệu không thể bị ứng dụng bắt để xử lý hoặc ghi log hoàn chỉnh, nên đôi khi log ứng dụng kết thúc rất đột ngột.

2. Xác nhận container có bị OOM hay không

Đầu tiên, tìm container đã thoát bằng mã 137:

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

Sau đó đọc trạng thái chi tiết:

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

Kết quả có thể là:

ExitCode=137 OOMKilled=true Error=

Nếu OOMKilled=true, Docker xác nhận tiến trình trong container đã bị cơ chế OOM kết thúc. Nếu giá trị là false, vẫn cần kiểm tra log kernel vì container có thể nhận SIGKILL từ nơi khác.

Trên Ubuntu, dùng một trong các lệnh sau:

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

Ví dụ dòng log:

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

Terminal demo xác minh OOMKilled

3. Kiểm tra RAM, swap và tiến trình đang chiếm bộ nhớ

Chạy:

free -h
swapon --show

Nếu VPS 1 GB RAM không có swap, kết quả thường gần giống:

               total        used        free      shared  buff/cache   available
Mem:           956Mi       831Mi        42Mi        18Mi        83Mi        71Mi
Swap:             0B          0B          0B

Xem tiến trình dùng nhiều RAM nhất:

ps aux --sort=-%mem | head -n 12

Nếu container còn chạy, theo dõi trực tiếp:

docker stats --no-stream

Cần phân biệt ba con số:

  • free: RAM hoàn toàn chưa dùng.
  • available: ước lượng RAM có thể cấp thêm mà chưa cần swap mạnh.
  • buff/cache: bộ nhớ cache có thể được kernel thu hồi khi cần.

Vì vậy, không nên chỉ nhìn cột free rồi kết luận VPS hết RAM. Cột available, log OOM và trạng thái OOMKilled đáng tin cậy hơn.

4. Cách xử lý nhanh khi VPS đang thiếu RAM

Dừng dịch vụ không cần thiết

Kiểm tra trước khi dừng:

sudo systemctl --type=service --state=running

Chỉ dừng dịch vụ mà bạn hiểu rõ. Không nên dừng ngẫu nhiên các dịch vụ hệ thống hoặc database đang chứa dữ liệu thật.

Với môi trường lab, có thể dừng một stack Docker không dùng:

docker compose -p old-project down

Dọn container đã dừng và cache build cũ sau khi đã kiểm tra:

docker container prune
docker builder prune

Các lệnh prune có thể xóa tài nguyên không còn được tham chiếu. Hãy đọc danh sách và xác nhận trước khi đồng ý.

Tạo swap cho VPS nhỏ

Swap không nhanh bằng RAM, nhưng có thể giúp VPS tránh bị OOM ngay khi build tăng bộ nhớ trong thời gian ngắn.

Ví dụ tạo swap 2 GB:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Kiểm tra:

free -h
swapon --show

Để tự kích hoạt sau khi khởi động lại, thêm vào /etc/fstab:

echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Trước khi thêm, nên kiểm tra tránh ghi trùng:

grep -n '/swapfile' /etc/fstab

Swap là lớp đệm, không phải cách biến VPS 1 GB thành VPS 3 GB có cùng hiệu năng. Nếu ứng dụng thường xuyên dùng quá nhiều bộ nhớ, giải pháp lâu dài vẫn là tối ưu ứng dụng hoặc nâng RAM.

5. Giảm bộ nhớ trong lúc build

Build ở CI rồi chỉ kéo image về VPS

Với VPS 1 GB, cách ổn định hơn là:

Git push → GitHub Actions build image → Container Registry → VPS pull image

VPS chỉ cần:

docker compose pull
docker compose up -d

Cách này chuyển công việc build nặng sang runner CI và giữ VPS tập trung vào việc chạy ứng dụng.

Dùng Dockerfile nhiều giai đoạn

Ví dụ ứng dụng Node.js:

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]

Multi-stage build chủ yếu giúp image cuối gọn hơn. Nó không đảm bảo bước build dùng ít RAM, nhưng giúp bạn tách rõ dependency, build và runtime để tối ưu từng giai đoạn.

Giảm số tác vụ chạy đồng thời

Trong monorepo, nhiều package build song song có thể làm RAM tăng nhanh. Tùy công cụ, hãy giảm concurrency. Ví dụ với npm workspaces, có thể build lần lượt từng ứng dụng thay vì khởi chạy tất cả cùng lúc.

Không nên sao chép tùy chọn giảm concurrency từ một công cụ khác khi chưa đọc tài liệu, vì mỗi build tool dùng tham số riêng.

6. Có nên đặt giới hạn RAM cho container?

Giới hạn giúp một container không chiếm toàn bộ máy, nhưng giới hạn quá thấp có thể khiến container bị OOM sớm hơn.

Ví dụ trong Compose:

services:
  api:
    image: deployeasy-api:1.0
    mem_limit: 512m

Sau khi áp dụng:

docker compose up -d
docker inspect api --format '{{.HostConfig.Memory}}'

Hãy đo bằng docker stats trước khi chọn con số. Database, Node.js và reverse proxy có đặc điểm sử dụng bộ nhớ khác nhau; không có một mức giới hạn phù hợp cho mọi dự án.

Không nên tùy tiện tắt OOM Killer. Khi cả host cạn RAM, việc ngăn kernel kết thúc tiến trình có thể làm toàn bộ VPS mất phản hồi.

7. Xác minh sau khi sửa

Thực hiện theo thứ tự:

free -h
swapon --show
docker compose build --no-cache web
docker compose up -d
docker compose ps
docker inspect web --format 'ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}'

Theo dõi thêm:

docker stats

Một lần build thành công chưa có nghĩa lỗi đã được xử lý hoàn toàn. Hãy quan sát cả lúc build, lúc ứng dụng khởi động và lúc nhận request đầu tiên.

8. Checklist xử lý Exit Code 137

  • Xác định đúng container thoát bằng mã 137.
  • Kiểm tra .State.OOMKilled bằng docker inspect.
  • Kiểm tra log kernel bằng journalctl -k hoặc dmesg.
  • Xem available RAM, swap và top tiến trình dùng RAM.
  • Dừng dịch vụ không cần thiết một cách có kiểm soát.
  • Tạo swap nếu VPS nhỏ và chưa có swap.
  • Chuyển build sang CI nếu VPS không đủ tài nguyên.
  • Theo dõi lại bằng docker stats.

9. Câu hỏi thường gặp

Exit Code 137 có phải lúc nào cũng do thiếu RAM?

Không. Nó có nghĩa tiến trình nhận SIGKILL. OOM Killer là nguyên nhân rất phổ biến, nhưng bạn vẫn cần kiểm tra OOMKilled, log kernel và lịch sử thao tác.

Thêm swap có làm VPS nhanh hơn không?

Không. Swap chậm hơn RAM. Nó chủ yếu giúp hệ thống có thêm khoảng đệm để tránh bị kết thúc tiến trình ngay lập tức.

VPS 1 GB có chạy Next.js, API và PostgreSQL cùng lúc được không?

Có thể chạy một dự án nhỏ đã tối ưu, nhưng build tại chỗ thường rất chật vật. Nên build ở CI, giới hạn dịch vụ nền và theo dõi tài nguyên thực tế.

Nguồn tham khảo

Đọc tiếp