用Copilot Agent Mode三步改造有50+ CVE漏洞的node:14镜像为多阶段alpine构建,体积降85%且漏洞归零。
Bài viết gốc đăng trên NextFuture
Một hướng dẫn trên Dev.to đưa image Node.js từ 912 MB với 50+ CVE Critical/High về 108 MB và 0 vulnerability, bằng cách giao thẳng terminal cho GitHub Copilot Agent Mode. Ba phase, hai prompt, và một cái bẫy mà phần lớn team VN đang mắc.
Bài viết bắt đầu bằng thứ rất dễ gặp trong repo cũ: một image single-stage trên node:14. Kết quả scan bằng docker scout cves theo bài: kích thước ~912 MB, "50+ Critical & High CVEs", và ba rủi ro cụ thể — "outdated base OS packages, running process as root, unnecessary build tools exposed in runtime".
Ba vấn đề đó độc lập nhau. Image nặng làm chậm CI và pull; chạy bằng root là leo thang đặc quyền nếu container bị chiếm; build tool còn trong runtime là bề mặt tấn công không ai cần tới.
Prompt đầu tiên yêu cầu Copilot Agent Mode refactor Dockerfile thành multi-stage build trên node:20-alpine: tách hẳn stage cài dependency khỏi stage runtime, tạo user không phải root, và sinh file .dockerignore. Theo bài, Copilot tự tạo .dockerignore, viết lại Dockerfile thành hai stage, rồi build image.
Kết quả so sánh trong bài: node:14 912 MB, app:alpine 123 MB — mức giảm 85%.
Đây là phần đáng đọc nhất, vì nhiều team dừng lại đúng ở đây và tưởng đã xong. Bài gọi nó là "The Alpine Trap": scan app:alpine vẫn lộ ra vulnerability trong các lớp utility của Alpine. Quan trọng hơn, nếu container bị chiếm thì Alpine vẫn còn package manager (apk) và shell (/bin/sh) để kẻ tấn công dùng tiếp.
Nói cách khác: giảm dung lượng và giảm bề mặt tấn công là hai việc khác nhau. Alpine làm tốt việc thứ nhất và chỉ làm một phần việc thứ hai.
Prompt thứ hai yêu cầu đổi production stage sang gcr.io/distroless/nodejs20-debian12, build thành app:hardened rồi scan lại. Theo bài, Distroless "contain only your application and its runtime dependencies—no package managers, no shell, and no standard Linux utilities", và lần scan cuối cho ra "0 Total Vulnerabilities".
| Base image | Size | CVE | Shell / package manager |
|---|---|---|---|
| Legacy single-stage node:14 | 912 MB | 50+ Critical/High | Có (/bin/bash, apt) |
| Multi-stage Alpine node:20-alpine | 123 MB | Low / Medium | Có (/bin/sh, apk) |
| Hardened Distroless distroless/nodejs20 | 108 MB | 0 | Không (shell-less) |
Đánh đổi cần biết trước khi copy: image không có shell nghĩa là docker exec vào debug cũng không còn. Nếu quy trình vận hành của bạn dựa vào việc ssh/exec vào container để xem log, phải chuyển sang log tập trung và ephemeral debug container trước, không phải sau.
Bài viết đưa ra cho team DevOps, và cả ba đều đúng dù bạn dùng agent nào:
Chạy docker scout cves trên image production nặng nhất của bạn và ghi lại hai số — dung lượng và số CVE Critical/High. Đó là baseline. Không có baseline thì mọi refactor sau đó chỉ là cảm giác.
Đây là kết quả một bài hướng dẫn với một app Node.js cụ thể, không phải benchmark trên nhiều codebase. Con số 912 → 108 MB phụ thuộc vào dependency của app; phần tái lập được là trình tự ba phase và lệnh verify.
This article was originally published on NextFuture. Follow us for more fullstack & AI engineering content.