Chuẩn bị môi trường build và cấu trúc thư mục
Tạo cấu trúc dự án cho Docker build
Chúng ta cần một thư mục gốc chứa tất cả tài nguyên tĩnh (HTML, JS, WASM) đã build từ phần 5. Thư mục này sẽ đóng vai trò là context cho Dockerfile.
Tạo thư mục docker để chứa file Dockerfile và cấu hình Nginx, tách biệt với source code để dễ quản lý.
mkdir -p docker/nginx && cd docker
Di chuyển các file build (index.html, main.js, model.wasm, engine.wasm) vào thư mục nginx để Nginx có thể serve.
cp ../dist/* nginx/
Verify: Kiểm tra nội dung thư mục nginx phải có đầy đủ file HTML, JS và WASM.
ls -la nginx/
Viết Dockerfile đa tầng (Multi-stage)
Cấu trúc Dockerfile để giảm kích thước image
Sử dụng 2 stages: Stage 1 dùng Node.js để copy tài nguyên, Stage 2 dùng Nginx Alpine siêu nhẹ để chạy production. Điều này giúp loại bỏ Node.js khỏi image cuối cùng, giảm kích thước xuống dưới 30MB.
Tạo file Dockerfile trong thư mục docker với nội dung chính xác sau:
FROM node:18-alpine AS builder
WORKDIR /app
# Copy các file build đã sẵn sàng (từ phần 5)
COPY ./nginx /app/dist
# Stage 2: Production environment
FROM nginx:1.25-alpine
# Copy config Nginx tùy chỉnh
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
# Copy tài nguyên tĩnh từ stage builder
COPY --from=builder /app/dist /usr/share/nginx/html
# Tạo directory log để tránh lỗi permission
RUN mkdir -p /var/log/nginx && chown -R nginx:nginx /var/log/nginx
# Mở cổng 80
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Verify: Chạy lệnh build để kiểm tra cú pháp Dockerfile. Nếu không có lỗi, image sẽ được tạo thành công.
docker build -t realtime-ai-wasm:latest .
Cấu hình Nginx cho WASM và Frontend
Thiết lập MIME type và Cache cho WebAssembly
Nginx mặc định chưa nhận diện file .wasm. Chúng ta phải khai báo MIME type application/wasm để trình duyệt biết cách xử lý file này. Đồng thời, thiết lập cache header để trình duyệt không tải lại file WASM nặng nề mỗi khi refresh trang.
Tạo file nginx.conf trong thư mục docker với cấu hình tối ưu:
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Cấu hình MIME type cho WebAssembly
types {
application/wasm wasm;
}
# Serve file WASM với cache lâu (1 năm)
location ~* \.wasm$ {
expires 365d;
add_header Cache-Control "public, no-transform";
add_header Vary "Accept-Encoding";
# Tối ưu truyền tải cho file lớn
chunked_transfer_encoding off;
}
# Serve các file tĩnh khác (JS, CSS, PNG, etc.)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public";
}
# Serve HTML (không cache để luôn nhận update mới nhất)
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# Fallback cho SPA (Single Page Application)
location / {
try_files $uri $uri/ /index.html;
}
# Cấu hình gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript application/wasm;
gzip_min_length 1000;
}
Verify: Sau khi build lại image, chạy container và dùng curl kiểm tra header response của file WASM.
docker run -d --name ai-test -p 8080:80 realtime-ai-wasm:latest
curl -I http://localhost:8080/model.wasm
Kết quả mong đợi: Header Content-Type phải là application/wasm và Cache-Control có thời gian expires dài.
Tối ưu hóa Cache và Hiệu năng
Đảm bảo Brotli compression cho WASM
File WASM thường rất lớn. Nginx Alpine mặc định chỉ có gzip. Để giảm băng thông hơn nữa, cần bật module Brotli (brotli). Tuy nhiên, image nginx:alpine mặc định không có brotli. Chúng ta cần build một custom image hoặc dùng image có sẵn brotli.
Để đơn giản và nhẹ nhất trong tutorial này, ta sẽ build một stage trung gian để cài đặt brotli vào Nginx Alpine.
Cập nhật lại Dockerfile với stage cài đặt brotli:
FROM node:18-alpine AS builder
WORKDIR /app
COPY ./nginx /app/dist
# Stage: Build Nginx với Brotli
FROM alpine:3.18 AS nginx-builder
RUN apk add --no-cache nginx nginx-brotli
RUN mkdir -p /etc/nginx/conf.d
# Stage 3: Final Production
FROM alpine:3.18
RUN apk add --no-cache nginx nginx-brotli
WORKDIR /etc/nginx/conf.d
COPY --from=nginx-builder /etc/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=nginx-builder /usr/sbin/nginx /usr/sbin/nginx
COPY --from=nginx-builder /usr/bin/nginx /usr/bin/nginx
COPY --from=nginx-builder /usr/share/nginx/html /usr/share/nginx/html
# Copy custom config
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Cập nhật nginx.conf để bật Brotli cho file WASM:
...
# Thêm vào file nginx.conf
brotli on;
brotli_comp_level 6;
brotli_types application/wasm application/javascript text/plain text/css;
...
location ~* \.wasm$ {
expires 365d;
add_header Cache-Control "public, no-transform";
brotli on;
}
Verify: Build lại image và kiểm tra header Content-Encoding khi request file WASM.
docker build -t realtime-ai-wasm:brotli .
docker run -d --name ai-test-brotli -p 8081:80 realtime-ai-wasm:brotli
curl -I http://localhost:8081/model.wasm
Kết quả mong đợi: Header Content-Encoding phải là br (brotli) hoặc gzip.
Build và Test Container Local
Chạy test end-to-end trên Localhost
Thực hiện build image cuối cùng với cấu hình Brotli và chạy container để test toàn bộ luồng hoạt động.
docker build -t realtime-ai-wasm:final .
docker run -d --name ai-app -p 8080:80 realtime-ai-wasm:final
Truy cập trình duyệt tại http://localhost:8080 để xem ứng dụng Frontend.
Mở Developer Tools (F12) -> Tab Network, lọc file .wasm, refresh trang.
Verify: Kiểm tra các chỉ số sau trong Network tab:
- Size file thực tế tải về (Transfer Size) phải nhỏ hơn nhiều so với file trên disk (ví dụ: file 5MB chỉ tải về 1.5MB).
- Content-Type phải là
application/wasm.
- Cache-Control phải tồn tại (tránh tải lại khi refresh lần 2).
- Không có lỗi CORS hay 404 trong Console.
docker logs ai-app
Kiểm tra log Nginx để đảm bảo không có lỗi 500 hay 403 khi serve file WASM.
docker stop ai-app && docker rm ai-app
Điều hướng series:
Mục lục: Series: Xây dựng nền tảng Real-time AI với WebAssembly, TensorFlow Lite và Kubernetes
« Phần 5: Phát triển ứng dụng Frontend tích hợp WebAssembly
Phần 7: Triển khai ứng dụng lên Kubernetes Cluster »