Kỹ thuật frontend hiện đại không còn giống như một thập kỷ trước. Bạn không chỉ đơn thuần là viết HTML và CSS. Một dự án điển hình hiện nay đi kèm với một runtime Node.js cụ thể, một phiên bản trình quản lý gói được khóa chặt, một mớ hỗn độn các công cụ build, và các pipeline triển khai yêu cầu mọi thứ phải khớp nhau một cách hoàn hảo. Đâu đó giữa lệnh npm install đầu tiên và bản build production cuối cùng, những sự khác biệt nhỏ bắt đầu len lỏi vào. Một đồng nghiệp chạy Node 20. Bạn chạy Node 18. Một công cụ CLI toàn cục trên máy bạn che lấp đi một dependency còn thiếu trên máy của họ. Và rồi câu nói mà không ai muốn nghe xuất hiện: "Nó vẫn chạy tốt trên máy tôi mà."
Docker xứng đáng có một vị trí trong bộ công cụ frontend của bạn vì nó loại bỏ sự không chắc chắn đó. Nó đóng gói ứng dụng của bạn cùng với runtime, các thư viện hệ thống và các dependency chính xác mà nó cần. Cho dù bạn đang lập trình trên Windows, đóng gói từ macOS hay triển khai lên một instance cloud Linux, hành vi của ứng dụng vẫn luôn đồng nhất.
Tại sao các nhà phát triển Frontend nên quan tâm
Những vấn đề nan giải mà Docker giải quyết không hề trừu tượng. Chúng xuất hiện trong mỗi sprint.
Xung đột phiên bản gây lãng phí thời gian. Một dự án khách hàng cũ cần Node 18, dự án cá nhân của bạn cần Node 20, và công việc tại startup mới lại yêu cầu Node 22. Nếu không có container, bạn phải quản lý việc này thông qua các trình quản lý phiên bản (version managers). Cách đó hiệu quả cho đến khi nó không còn nữa. Một sự sai lệch nhỏ về phiên bản npm có thể thay đổi cách giải quyết các peer dependencies, khiến bạn gặp phải một bản build lỗi trong khi nó vẫn chạy bình thường với người khác. Khi một framework công bố bản phát hành mới, vòng lặp phản hồi không nên là hai giờ đồng hồ cài đặt lại. Nó chỉ nên là một thay đổi file duy nhất và khởi động lại container.
Các package toàn cục là một nguồn gây ra sự xung đột ngầm khác. Bạn có thể đã cài đặt Angular CLI, Expo hoặc Prisma toàn cục từ sáu tháng trước. Một lập trình viên mới cài đặt cùng công cụ đó từ đầu và nhận được một phiên bản khác. Đột nhiên, các script build của bạn đưa ra những cảnh báo mà không xuất hiện ở bất kỳ nơi nào khác. Docker khắc phục điều này bằng cách giữ mọi thứ ở phạm vi dự án (project-local). Bạn định nghĩa phiên bản Node trong Dockerfile của mình. Các dependency được cài đặt bên trong container, tách biệt hoàn toàn với hệ điều hành máy chủ (host OS). Laptop của bạn có thể là macOS, Windows hoặc Ubuntu; ứng dụng vẫn sẽ thấy một môi trường hoàn toàn giống nhau trong mọi lần chạy.
Lợi ích về onboarding là rất khó để bỏ qua. Nhân viên mới không cần một file readme dài ba trang hướng dẫn cài đặt Homebrew, thiết lập nvm aliases và sửa các lỗi phân quyền toàn cục. Họ chỉ cần cài đặt Docker, clone repository và chạy một câu lệnh duy nhất. Một quá trình thiết lập vốn mất cả buổi chiều nay chỉ còn tính bằng phút. Và khi họ chuyển sang dự án khác, không có gì còn sót lại. Không có các công cụ toàn cục bị bỏ rơi. Không có các trình quản lý phiên bản tranh giành quyền ưu tiên trong PATH. Máy tính cá nhân của họ luôn được giữ sạch sẽ.
Image và Container: Những khái niệm cơ bản
Nếu Docker còn mới mẻ với bạn, các thuật ngữ này thực ra đơn giản hơn bạn tưởng. Một Docker image là một bản thiết kế (blueprint). Nó chứa mã nguồn, runtime Node.js, lockfile và mọi dependency cần thiết để chạy ứng dụng. Một container là một instance đang hoạt động được tạo ra từ image đó. Hãy coi image như một công thức nấu ăn và container là món ăn thực tế. Bạn có thể nướng cùng một chiếc bánh hàng trăm lần từ một công thức. Bạn có thể khởi chạy các container giống hệt nhau từ một image mà không cần lo lắng về những gì đã được cài đặt trên máy tính chủ.
Một Dockerfile thực tế
Hãy cùng xem xét một điểm bắt đầu cụ thể. Nếu bạn đang
