44 questions
20 / 44 questions
Docker là nền tảng đóng gói ứng dụng cùng toàn bộ dependency, runtime và cấu hình vào một container chạy nhất quán trên mọi môi trường. Nó giải quyết vấn đề kinh điển "chạy trên máy tôi thì được" bằng cách chuẩn hoá môi trường từ dev → test → production. Container nhẹ, khởi động nhanh và cô lập tiến trình, giúp deploy và scale dễ dàng hơn nhiều so với cài đặt thủ công trên host.
VM ảo hoá cả phần cứng và chạy một OS đầy đủ (guest OS) trên hypervisor — nặng, khởi động chậm (tính bằng phút), tốn nhiều RAM/disk. Container chia sẻ kernel của host OS và chỉ cô lập ở tầng tiến trình (namespaces + cgroups) — nhẹ, khởi động trong mili-giây, tốn ít tài nguyên. Đổi lại, container không cô lập mạnh bằng VM vì dùng chung kernel; muốn chạy kernel khác OS (ví dụ Windows container trên Linux) thì không được.
Image là bản snapshot chỉ đọc, đóng gói code + dependency + runtime — như một class. Container là một instance đang chạy của image, có lớp ghi (writable layer) riêng — nhiều container có thể chạy từ cùng một image. Image bất biến; container thì có vòng đời (create/start/stop/rm).
Dockerfile là file text chứa danh sách các instruction mô tả cách build một image từng bước một. Docker đọc từ trên xuống, mỗi instruction tạo ra một tầng (layer). Đây là cách khai báo và version-control được quy trình đóng gói ứng dụng.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]
Những instruction hay dùng nhất: FROM (chọn base image), WORKDIR (đặt thư mục làm việc), COPY/ADD (đưa file vào image), RUN (chạy lệnh lúc build, tạo layer mới), ENV/ARG (biến), EXPOSE (khai báo port), ENTRYPOINT/CMD (lệnh chạy khi start container), HEALTHCHECK (kiểm tra sức khoẻ) và USER (đổi user). Phân biệt: RUN chạy lúc build, còn CMD/ENTRYPOINT chạy lúc run.
Mỗi instruction trong Dockerfile tạo một layer chỉ đọc; image là chồng các layer xếp lên nhau (union filesystem). Khi build, Docker cache từng layer: nếu instruction và context đầu vào không đổi, nó tái sử dụng layer cũ thay vì build lại. Quan trọng: khi một layer bị invalidate (thay đổi), tất cả layer phía sau cũng phải build lại. Vì vậy nên đặt các bước ít thay đổi (cài dependency) lên trên, bước hay thay đổi (copy source code) xuống dưới.
COPY đơn giản: chỉ sao chép file/thư mục từ build context vào image. ADD làm thêm hai việc: tự động giải nén file tar và tải được URL từ xa. Best practice là ưu tiên COPY vì rõ ràng, dễ đoán; chỉ dùng ADD khi thực sự cần giải nén tar local. Không nên dùng ADD để tải URL — hãy dùng curl/wget trong một RUN để kiểm soát và dọn dẹp tốt hơn.
ENTRYPOINT định nghĩa lệnh cố định luôn chạy khi container start — coi container như một executable. CMD cung cấp lệnh/tham số mặc định, và bị ghi đè hoàn toàn nếu người dùng truyền lệnh khác vào docker run. Khi dùng chung, CMD trở thành tham số mặc định cho ENTRYPOINT.
ENTRYPOINT ["ping"]
CMD ["localhost"]
Chạy docker run img sẽ ping localhost; docker run img google.com sẽ ping google.com.
ARG là biến chỉ tồn tại lúc build — truyền qua --build-arg, không còn trong image runtime. ENV đặt biến môi trường tồn tại cả lúc build và runtime, có mặt bên trong container đang chạy. Dùng ARG cho giá trị build-time (version, mirror); dùng ENV cho cấu hình runtime. Lưu ý: đừng đưa secret qua ARG/ENV vì chúng bị lưu vào lịch sử image.
WORKDIR đặt thư mục làm việc cho các instruction phía sau (RUN, CMD, COPY, ENTRYPOINT) và cũng là thư mục mặc định khi vào container. Nếu thư mục chưa tồn tại, Docker tự tạo. Nên dùng WORKDIR /app thay vì RUN cd /app — vì cd trong RUN chỉ có tác dụng trong đúng layer đó, không lưu lại cho các bước sau.
Không. EXPOSE chỉ là tài liệu/metadata khai báo container lắng nghe trên port nào — nó không tự publish port ra host. Muốn truy cập từ ngoài phải map port khi run bằng -p host:container (hoặc -P để tự map các port EXPOSE tới port ngẫu nhiên). Nói cách khác, EXPOSE mang tính thông tin cho người đọc và cho các công cụ.
Multi-stage build dùng nhiều FROM trong một Dockerfile: một (hoặc nhiều) stage để build, và một stage cuối cùng chỉ chứa artifact cần thiết cho runtime. Lợi ích: image cuối nhỏ và sạch — không kéo theo compiler, dev dependency, source code trung gian. Đây là cách tiêu chuẩn để giảm kích thước và bề mặt tấn công.
FROM node:20 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
Vài kỹ thuật chính: dùng base image nhỏ như alpine hoặc distroless (chỉ có runtime, không có shell); áp dụng multi-stage build để loại bỏ dev dependency; gộp các lệnh RUN và dọn cache (apt-get clean, rm -rf /var/lib/apt/lists/*) trong cùng một layer; dùng .dockerignore để không copy rác; và chỉ cài đúng dependency production (npm ci --omit=dev). Image nhỏ đồng nghĩa deploy nhanh, ít lỗ hổng bảo mật hơn.
.dockerignore liệt kê các file/thư mục loại khỏi build context gửi lên Docker daemon — tương tự .gitignore. Nó giúp build nhanh hơn (context nhỏ), image gọn hơn, và tránh vô tình copy vào những thứ nhạy cảm hay thừa như node_modules, .git, .env, log. Đây là bước tối ưu và bảo mật rất dễ nhưng thường bị bỏ quên.
node_modules
.git
.env
Dockerfile
*.log
Để tận dụng layer caching cho bước cài dependency. Nếu copy package*.json và chạy npm install trước, thì chừng nào dependency không đổi, Docker sẽ tái dùng layer đó dù bạn sửa source code bao nhiêu lần. Ngược lại nếu COPY . . trước rồi mới install, mỗi lần đổi một dòng code sẽ invalidate cache và bắt cài lại toàn bộ node_modules — rất chậm.
COPY package*.json ./
RUN npm ci
COPY . .
Cả hai đều lưu dữ liệu ngoài lớp ghi của container. Named volume do Docker quản lý (nằm trong /var/lib/docker/volumes), portable, phù hợp cho dữ liệu production như database. Bind mount ánh xạ trực tiếp một đường dẫn cụ thể trên host vào container — phụ thuộc cấu trúc thư mục host, hay dùng trong development để mount source code và hot-reload. Volume được Docker khuyến nghị cho hầu hết trường hợp lưu trữ lâu dài.
Named volume là volume có tên do Docker quản lý, tạo bằng docker volume create hoặc tự tạo khi mount. Nó tách biệt vòng đời khỏi container: xoá container thì dữ liệu vẫn còn, container mới có thể mount lại. Ví dụ chạy Postgres với volume:
docker run -d -v pgdata:/var/lib/postgresql/data postgres:16
Ở đây pgdata là named volume, đảm bảo dữ liệu DB không mất khi container bị recreate.
Vì mọi ghi mặc định nằm ở writable layer gắn với vòng đời container — xoá container (docker rm) là mất luôn layer đó. Container được thiết kế ephemeral (dùng rồi bỏ). Muốn data persistence phải lưu ra ngoài bằng volume hoặc bind mount. Nguyên tắc: giữ container stateless, mọi dữ liệu cần bền vững phải nằm trên volume.
Ba driver cơ bản: bridge (mặc định) — tạo mạng ảo riêng, container giao tiếp qua đó và cần map port để ra ngoài; host — container dùng chung network stack của host, không cách ly mạng nhưng nhanh và không cần map port; none — không có network, hoàn toàn cô lập. Với user-defined bridge, các container còn có thể gọi nhau theo tên nhờ DNS nội bộ của Docker — điều mà bridge mặc định không hỗ trợ.
Flag -p host:container publish một port bên trong container ra một port trên host, cho phép truy cập từ bên ngoài. Ví dụ -p 8080:80 nghĩa là truy cập localhost:8080 trên host sẽ được chuyển tới port 80 trong container.
docker run -d -p 8080:80 nginx
Có thể bind theo IP (-p 127.0.0.1:8080:80) để chỉ mở nội bộ, hoặc dùng -P để tự map tất cả port EXPOSE.