เมื่อจะนำเว็บที่สร้างด้วย Astro ขึ้นใช้งานจริง (Production) วิธีที่นิยมและคุ้มค่าที่สุดคือ build เว็บให้เป็นไฟล์ Static (HTML, CSS, JS) แล้วเสิร์ฟผ่านเว็บเซิร์ฟเวอร์น้ำหนักเบาอย่าง Nginx ภายใน Docker Container ครับ — บทความนี้จะอธิบายตั้งแต่แนวคิดไปจนถึงไฟล์ตั้งค่าจริงที่ใช้ในโปรเจกต์นี้
NOTE
ทำไมต้อง Static? เพราะ Astro เป็น Zero-JS by default เว็บที่ build ออกมาจึงเป็นไฟล์ HTML ธรรมดาที่ Nginx เสิร์ฟได้เร็วมาก ไม่ต้องรัน Node.js เบื้องหลังตลอดเวลา ทำให้ปลอดภัยกว่าและกิน RAM น้อยกว่า
1. แนวคิด: Multi-stage Build
หัวใจของการ deploy นี้คือเทคนิค Multi-stage Build ที่แยกขั้นตอน “build” ออกจาก “การเสิร์ฟจริง” เพื่อให้ Image สุดท้ายมีขนาดเล็กที่สุด
| Stage | Base Image | หน้าที่ | อยู่ใน Image สุดท้าย? |
|---|---|---|---|
| Build | node:22-alpine | ติดตั้ง dependencies + รัน npm run build ได้ไฟล์ static ที่ /app/dist | ❌ ถูกทิ้ง |
| Production | nginx:alpine | ดึงเฉพาะโฟลเดอร์ dist มาเสิร์ฟ | ✅ |
ผลลัพธ์: Node.js, node_modules และ source code ไม่ติดไปกับ Image จริง เหลือแค่ Nginx + ไฟล์ static ขนาดไม่กี่ MB เท่านั้น
2. Dockerfile
# Stage 1: Build the Astro static site
FROM node:22-alpine AS builder
WORKDIR /app
# คัดลอกไฟล์ package ก่อน เพื่อให้ Docker cache ชั้น dependencies
COPY package*.json ./
RUN npm ci
# คัดลอกโค้ดที่เหลือแล้ว build
COPY . .
RUN npm run build
# Stage 2: Serve using Nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
TIP
การ COPY package*.json ก่อน แล้วค่อย COPY . . เป็นเทคนิคใช้ Layer Cache — ถ้าแก้แค่โค้ด (ไม่แตะ dependencies) Docker จะข้ามขั้นตอน npm ci ที่ช้า ทำให้ rebuild เร็วขึ้นมาก
ทำไมใช้ npm ci แทน npm install? เพราะ npm ci ติดตั้งตาม package-lock.json แบบเป๊ะ ๆ ทำให้ build ได้ผลเหมือนกันทุกครั้ง (Reproducible) เหมาะกับ CI/CD
3. docker-compose.yml
services:
kb-blog:
build:
context: .
dockerfile: Dockerfile
container_name: pcpkb-blog
ports:
- "8080:80"
restart: unless-stopped
อธิบายแต่ละบรรทัด:
build.context: .— สั่ง build จากDockerfileในโฟลเดอร์ปัจจุบันports: "8080:80"— map พอร์ต8080ของเครื่อง → พอร์ต80ใน container (เข้าผ่านhttp://localhost:8080)restart: unless-stopped— ให้ container กลับมารันเองอัตโนมัติเมื่อเครื่อง/Docker รีสตาร์ต (เว้นแต่เราสั่งหยุดเอง)
4. nginx.conf (จุดที่มักลืม)
สำหรับ Single Page ทั่วไปอาจไม่ต้องตั้งค่าอะไรมาก แต่ของเรามีจุดสำคัญที่ทำให้ทำงานถูกต้อง:
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# เปิด Gzip ลดขนาดไฟล์ที่ส่งให้ผู้ใช้
gzip on;
gzip_types text/css application/javascript application/xml;
location / {
try_files $uri $uri/ =404;
}
# Cache ไฟล์ static ยาว 1 ปี
location ~* \.(?:css|js|png|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, no-transform";
}
# หน้า 404 ที่ Astro สร้างไว้
error_page 404 /404.html;
}
IMPORTANT
try_files $uri $uri/ =404; สำคัญมากกับ Astro เพราะ build ออกมาเป็นโฟลเดอร์ (เช่น /kb/welcome/index.html) บรรทัดนี้ช่วยให้เข้า URL แบบ /kb/welcome/ แล้วเจอ index.html ข้างในได้ถูกต้อง
5. คำสั่งที่ใช้บ่อย
NOTE
Docker เวอร์ชันใหม่ใช้ docker compose (เว้นวรรค) ส่วน docker-compose (มีขีด) คือเวอร์ชันเก่า v1 ใช้แทนกันได้
# build แล้วรันขึ้นระบบแบบ background (-d)
docker compose up -d --build
# ดูสถานะ container
docker compose ps
# ดู log แบบ real-time
docker compose logs -f
# หยุดและลบ container
docker compose down
| สถานการณ์ | คำสั่ง |
|---|---|
| แก้โค้ดแล้วอยาก deploy ใหม่ | docker compose up -d --build |
| เว็บล่ม อยากดูสาเหตุ | docker compose logs -f |
| ปิดเว็บชั่วคราว | docker compose stop |
| ลบทิ้งทั้งหมด | docker compose down |
💡 สรุปจำง่าย
- Build → Serve แยกกัน: ใช้ Node build เสร็จแล้วโยน static ให้ Nginx เสิร์ฟ
- Multi-stage = Image เล็ก: ทิ้ง Node.js/source ออก เหลือแค่ Nginx +
dist npm ci+ cache layer: build ซ้ำได้ผลเดิม และเร็วขึ้นtry_files+404.html: ทำให้ routing ของ Astro ทำงานถูกบน Nginx- คำสั่งหลักจำแค่ตัวเดียว:
docker compose up -d --build
ไฟล์ Dockerfile, docker-compose.yml และ nginx.conf ตัวจริงทั้งหมดอยู่ที่รากของโปรเจกต์นี้ครับ — แก้ตามต้องการแล้วสั่ง docker compose up -d --build ได้เลย!