Mình đã mất code bốn lần trước khi quy trình deploy Next.js lên VPS này thành hình. Không lần nào do Next.js. Cả bốn đều do mình. Site bạn đang đọc chạy Payload CMS 3.86, Next.js 15.5 và Postgres trên đúng một con VPS, và người bấm nút là một người không đọc code.
Vì sao mình không dùng nền tảng dựng sẵn
Vercel đưa một site Next.js lên mạng nhanh hơn mình rất nhiều. Bấm vài nút là xong tên miền và HTTPS, push lên là nó tự build. Mình vẫn thuê một con máy và tự làm lấy.
Chuyện bắt đầu từ hình dạng của site. Admin panel của Payload CMS phải chạy cùng máy với database, cron job cũng nằm ở đó. Tách ra ba nơi thì mình được cái tiện lúc deploy và mất cái tiện lúc debug. Người không đọc code như mình cần cái tiện thứ hai hơn.
Còn dữ liệu thì mình muốn giữ hết. Bài viết, ảnh, tài khoản đều nằm trong database trên máy mình thuê hằng tháng, và không ai đổi điều khoản giữa chừng rồi bắt mình chuyển nhà.
Cả cụm chạy bằng container. Database và ảnh đã tải lên nằm trong volume, nên xoá container đi dựng lại vẫn không mất dữ liệu.
Nói cho công bằng thì deploy Next.js lên VPS đắt hơn về công. Bạn nhận thêm phần việc nền tảng vốn làm hộ: vá hệ điều hành, gia hạn HTTPS, dọn ổ đầy, dựng lại tiến trình chết. Đổi lại, lúc có chuyện mình chỉ mở đúng một chỗ ra xem.
Ban đầu mình tưởng chọn VPS là chọn đường khó cho ra vẻ. Hoá ra đó là cách duy nhất để cả cụm này sống chung một chỗ mà mình vẫn cầm chìa khoá.
HTTPS thì mình không nhét vào ứng dụng, nó nằm ở reverse proxy phía trước. Next.js phía sau không biết gì về chứng chỉ, nên tới hạn gia hạn mình không phải build lại app.
Thứ tự các bước deploy Next.js lên VPS
Commit trước, build sau. Sáu bước mình chạy mỗi lần, đúng thứ tự này:
- Sửa xong thì commit, rồi push lên remote.
- Chuyển về branch chính. Không deploy từ branch nào khác.
- Lấy một bản sạch từ branch đó, không lấy từ thư mục đang mở dở.
- Build, deploy lên VPS, restart container.
- So commit SHA đang chạy với commit SHA trên remote.
- Gọi health check endpoint, đọc nội dung nó trả về.

Cả quy trình deploy Next.js lên VPS này mua đúng một câu trả lời: thứ đang chạy ngoài kia có phải thứ mình vừa sửa không. Năm bước đầu tồn tại để câu trả lời đó không phải do mình đoán.
Next.js có trang tài liệu riêng cho việc tự build và tự chạy trên máy của mình, xem tài liệu deploy của Next.js. Phần cú pháp lệnh nằm hết ở đó nên mình không chép lại.
Người gõ lệnh trong terminal cũng không phải mình. Mình mô tả việc bằng tiếng Việt, agent chạy, mình đọc log và quyết định. Đọc log với mình là tìm xem lệnh nào dừng giữa chừng, chứ không phải hiểu từng dòng một. Agent đó là Claude Code, mình viết riêng một bài Claude Code là gì.
Bốn cái bẫy mình đã dính
Build khi code chưa commit
Sửa xong, build thẳng trên VPS, thấy chạy đúng thì để đó. Code chưa được commit. Lần build kế tiếp lấy nguồn từ git, và bản sửa biến mất sạch.
Không có thông báo lỗi nào. Chỉ có một tính năng tự nhiên quay về trạng thái cũ, và mình ngồi tự hỏi đã sửa hay chưa. Đây đúng kiểu hỏng mà mình hay dính nhất: máy không kêu, mình cũng không biết mà tìm, chuyện này mình viết kỹ hơn ở bài tự động hoá sai chỗ. Dính nhiều lần rồi mình mới chịu viết nó thành luật: chưa commit và chưa push lên remote thì chưa gọi là xong.
Hai phiên làm chung một branch
Hai phiên làm việc mở cùng một thư mục, cùng một branch. Phiên bên này pull bản mới về. Mọi thứ phiên bên kia chưa commit bị xoá trắng trong một lệnh.
Từ đó mình tách hẳn ra: một việc một branch riêng, một phiên làm một branch, không ai mở chung thư mục với ai. Nghe thừa cho tới lần đầu mất việc.
Build từ thư mục đang dở
Thư mục làm việc lúc nào cũng có thứ chưa commit. Build thẳng từ đó thì bản chạy trên VPS khác bản trên remote. Lúc yên thì không sao. Lúc có sự cố, cái mình mở ra đọc trên GitHub không phải cái đang chạy, và mình đi tìm lỗi sai chỗ. Nên bây giờ mình chỉ build từ branch chính, và luôn lấy bản sạch thay cho thư mục đang mở.
Một dòng đường dẫn ổ C còn sót
Mình đổi máy làm việc từ Windows sang Mac. Mọi thứ chạy bình thường cho tới lúc bấm deploy. Cổng kiểm tra trước khi deploy chặn đứng, báo một lỗi mình đọc không hiểu.
Thông báo đó không nhắc gì tới đường dẫn. Nguyên nhân là một file cấu hình vẫn trỏ vào đường dẫn ổ C của máy Windows cũ. Trên Mac, đường dẫn đó đơn giản là không tồn tại.
Mình không ghi lại mất bao lâu để tìm ra. Chỉ nhớ là suốt quãng đó không deploy được gì, và toàn bộ công sức đổi lấy đúng một dòng sửa. Từ lần đó, hễ cổng kiểm tra báo một lỗi vô nghĩa, mình đi soi môi trường trước rồi mới soi code.
Cách chứng minh bản vừa deploy là bản đúng
Chạy được không có nghĩa là đúng bản. Trang vẫn mở, container vẫn sống, mà nội dung bên trong là bản cũ. Nên mỗi lần deploy Next.js lên VPS, máy phải trả lời mình hai câu trước khi được báo xong.
Commit SHA trên VPS có trùng commit SHA trên remote không. Lệch là dừng, không tranh luận, không đổ cho cache.
Health check endpoint có trả về đúng nội dung mình chờ không. Trả về sai, hoặc không trả gì, thì lần deploy đó coi như hỏng.
Hỏng thì hệ thống tự rollback, không đợi mình ngồi quyết. Mình ngủ, nó vẫn rollback được.
Rollback rẻ hơn mọi thứ khác

Mỗi lần deploy mình đều gắn một tag. Có chuyện thì mình deploy lại tag ngay trước đó, coi như lần vừa rồi chưa từng xảy ra. Trong cả quy trình, đó là thứ rẻ nhất mà cứu mình nhiều nhất.
Admin panel có nhịp riêng khi deploy vì nó đụng tới database. Lần deploy nào kèm migration thì rollback không còn rẻ như vậy nữa. Nếu bạn cũng chạy Payload, đọc tài liệu chính thức của Payload CMS trước khi tự deploy.
Ai không nên deploy Next.js lên VPS
Có hai nhóm nên bỏ qua toàn bộ bài này.
- Người chỉ có một trang tĩnh, không database, không cron job.
- Đội chưa có ai chịu trách nhiệm khi VPS hỏng lúc nửa đêm.
Tiền thuê máy nhìn thì rẻ. Khoản đắt là thời gian bạn ngồi đọc log lúc hai giờ sáng, đúng vào hôm bạn có việc khác và không ai đổi ca cho bạn được. Nền tảng dựng sẵn rẻ hơn nhiều so với thời gian tự vá.
Chỗ mình vẫn chưa yên tâm
Máy tự rollback được, nhưng nó chỉ tỉnh vào đúng lúc mình bấm deploy. Site hỏng vào lúc mình đang ngủ thì vẫn phải có người mở máy lên xem. VPS không tự khoẻ lại, nó nằm im và trả lỗi cho tới khi có người tới.
Mỗi cái kiểm tra trong quy trình này đều thay cho một thứ mình biết chắc sẽ quên. Sau bốn lần mất code mình không cẩn thận hơn, chỉ là bây giờ có máy nhớ hộ.
Những ghi chép cùng loại mình để ở Vận hành.



