Viết Dockerfile production (multi-stage, non-root user)
🎯 Mục tiêu: Viết Dockerfile multi-stage cho Node.js Express API, so sánh image size giữa single-stage và multi-stage, đảm bảo container chạy với non-root user.
🧰 Công cụ / nền tảng: Docker Desktop (Windows/macOS) hoặc Docker Engine (Linux), VS Code, Git.
📦 Chuẩn bị: Chạy docker version xác nhận Docker đang hoạt động; docker info | grep "Server Version" kiểm tra version.
▶️ Các bước:
# 1. Tạo project Node.js API
mkdir docker-lab066 && cd docker-lab066
git init
cat > package.json << 'EOF'
{
"name": "demo-api",
"version": "1.0.0",
"scripts": {
"start": "node src/index.js",
"build": "mkdir -p dist && cp src/index.js dist/"
},
"dependencies": { "express": "^4.18.2" },
"devDependencies": { "nodemon": "^3.0.0" }
}
EOF
mkdir src
cat > src/index.js << 'EOF'
const express = require('express');
const app = express();
app.get('/healthz', (_, res) => res.json({ status: 'ok', pid: process.pid }));
app.listen(3000, () => console.log('Server running on port 3000'));
EOF
# 2. Dockerfile single-stage (để so sánh size)
cat > Dockerfile.single << 'EOF'
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/index.js"]
EOF
# 3. Dockerfile multi-stage production
cat > Dockerfile << 'EOF'
# ---- Stage 1: builder ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ---- Stage 2: runtime ----
FROM node:20-alpine AS runtime
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --from=builder /app/dist ./dist
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
EOF
# 4. .dockerignore — quan trọng!
cat > .dockerignore << 'EOF'
node_modules
npm-debug.log
.git
.gitignore
*.md
.env
EOF
# 5. Build cả hai để so sánh
docker build -f Dockerfile.single -t demo-api:single .
docker build -f Dockerfile -t demo-api:multi .
# 6. So sánh size
docker images demo-api --format "table {{.Tag}}\t{{.Size}}"
# TAG SIZE
# multi ~120 MB <-- nhỏ hơn nhiều
# single ~1.1 GB
# 7. Chạy và kiểm tra
docker run -d --name api-test -p 3000:3000 demo-api:multi
sleep 2
curl http://localhost:3000/healthz
# {"status":"ok","pid":1}
# 8. Xác nhận KHÔNG chạy root
docker exec api-test whoami
# appuser
# 9. Xem layer history
docker history demo-api:multi
🖥️ Đối chiếu GUI: Docker Desktop → Images → so sánh cột Size. Click vào image để xem layers.
✅ Kết quả mong đợi: demo-api:multi nhỏ hơn demo-api:single ít nhất 5x; curl /healthz trả về JSON; whoami in ra appuser.
🧹 Cleanup:
docker stop api-test && docker rm api-test
docker rmi demo-api:multi demo-api:single
cd .. && rm -rf docker-lab066