การ Deploy › Docker
เอกสาร/การ Deploy/Docker

Deploy ด้วย Docker

หน้านี้สอนอะไร

สร้าง container image ของเว็บที่มี Easy CMS แล้วรันด้วย Docker Compose แบบใช้ Postgres ใน container ที่สอง หรือแบบ SQLite บน volume image เดียวกันนี้รันได้บน server หรือแพลตฟอร์มใดก็ได้ที่รัน container

ควรอ่านก่อน: เลือกที่ deploy, Migration และการ deploy

container รันอยู่ตลอด server จึงรันงานตามเวลาเองทุกนาที และเก็บ SQLite กับไฟล์ อัปโหลดไว้บน volume ได้ การ build ใน image ยังทำให้ native driver ของ SQLite ตรงกับระบบที่รันจริงด้วย

1. Dockerfile

image จะ migrate ฐานข้อมูลตอน start แล้วจึงเสิร์ฟเว็บ เลือกตามแบบที่ใช้:

Next.js
# Build: ติดตั้งทุกอย่างแล้ว build Next.js ไม่ต้องใช้ฐานข้อมูลตอน build
FROM node:24-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npx next build
 
# Run: แอปที่ build แล้วพร้อม node_modules (CLI easy-cms ใช้รัน migration)
FROM node:24-slim
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
COPY --from=build /app ./
EXPOSE 3000
HEALTHCHECK --interval=30s --start-period=30s \
  CMD node -e "fetch('http://localhost:3000/api/cms/readyz').then(r => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
CMD ["sh", "-c", "npx easy-cms migrate && npx next start"]
Nuxt
FROM node:24-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npx nuxi build
 
FROM node:24-slim
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOST=0.0.0.0
COPY --from=build /app ./
EXPOSE 3000
HEALTHCHECK --interval=30s --start-period=30s \
  CMD node -e "fetch('http://localhost:3000/api/cms/readyz').then(r => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
CMD ["sh", "-c", "npx easy-cms migrate && node .output/server/index.mjs"]
Standalone
# image อย่างเป็นทางการ: easy-cms serve พร้อม adapter ที่ใช้บ่อย ดู "image อย่างเป็นทางการ" ข้างล่าง
FROM ghcr.io/maritonx/easy-cms:1
COPY easy-cms.config.ts ./
COPY easy-cms ./easy-cms
  • script build ต้องไม่ migrate เพราะ image ถูก build โดยไม่มีฐานข้อมูล ให้รัน next build (หรือ nuxi build) ตรงๆ แบบข้างบน
  • health check เรียก /readyz: ฐานข้อมูลตอบและไม่มี migration ค้าง ดูHealth check
  • image แบบ standalone ต้องการแค่ config และ migration ของคุณ ตัว Easy CMS มาจากimage อย่างเป็นทางการ และฟังที่ port 4000
  • ถ้าใช้ pnpm ให้เริ่ม stage build ด้วย RUN corepack enable คัดลอก pnpm-lock.yaml และรัน pnpm install --frozen-lockfile

กันไฟล์บนเครื่องไม่ให้เข้าไปใน image ด้วย .dockerignore:

.dockerignore
node_modules
.next
.output
.env
.pglite
uploads
data
*.db
.git

image อย่างเป็นทางการ

ghcr.io/maritonx/easy-cms รัน easy-cms serve ของเวอร์ชันตาม tag: 1.7.0, 1.7, 1 (1.x ล่าสุด) และ latest สำหรับ linux/amd64 และ linux/arm64 ข้างในมี easy-cms, @easy-cms/core, adapter ของ SQLite และ Postgres, S3 storage, อีเมล SMTP และ sharp สำหรับย่อรูป build จากแพ็กเกจบน npm

ไฟล์ของคุณ/app: คัดลอก easy-cms.config.ts และโฟลเดอร์ easy-cms (migration) ไปไว้ที่นี่
ข้อมูล/app/data เป็น volume: ชี้ SQLite (file:./data/cms.db) และไฟล์ upload บนดิสก์ (upload: { dir: './data/uploads' }) มาที่นี่
Port4000 (PORT)
Usernode ไม่ใช่ root
ตอนเริ่มeasy-cms migrate แล้ว easy-cms serve --trust-proxy ตั้ง EASY_CMS_MIGRATE=false เพื่อข้าม migration
Health check/readyz: ฐานข้อมูลตอบและไม่มี migration ค้าง

แพ็กเกจอื่นที่ config import เช่น plugin ให้ติดตั้งด้วย npm install ใน Dockerfile ของคุณ:

FROM ghcr.io/maritonx/easy-cms:1
RUN npm install @easy-cms/plugin-seo@1 @easy-cms/plugin-redirects@1
COPY easy-cms.config.ts ./
COPY easy-cms ./easy-cms

ใช้เวอร์ชันของแพ็กเกจเหล่านั้นให้ตรงกับ release ของ image ถ้ามีหลาย container บนฐานข้อมูลเดียวกัน ให้ migrate ครั้งเดียวก่อนเริ่ม (ขั้น release หรือ init container ที่รัน easy-cms migrate) แล้วเริ่ม container ด้วย EASY_CMS_MIGRATE=false

2. Compose

With Postgres: compose.yaml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      EASY_CMS_SECRET: ${EASY_CMS_SECRET:?set EASY_CMS_SECRET in .env}
      EASY_CMS_SETUP_CODE: ${EASY_CMS_SETUP_CODE:-}
      DATABASE_URL: postgres://cms:cms@db:5432/cms
    volumes:
      - uploads:/app/uploads
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
 
  db:
    image: postgres:17
    shm_size: 1g
    environment:
      POSTGRES_USER: cms
      POSTGRES_PASSWORD: cms
      POSTGRES_DB: cms
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U cms -d cms"]
      interval: 5s
      retries: 10
    restart: unless-stopped
 
volumes:
  uploads:
  pgdata:
With SQLite: compose.yaml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      EASY_CMS_SECRET: ${EASY_CMS_SECRET:?set EASY_CMS_SECRET in .env}
      EASY_CMS_SETUP_CODE: ${EASY_CMS_SETUP_CODE:-}
      DATABASE_URL: file:/app/data/cms.db
    volumes:
      - data:/app/data
      - uploads:/app/uploads
    restart: unless-stopped
 
volumes:
  data:
  uploads:

shm_size เผื่อพื้นที่ให้ Postgres ทำ parallel query: ค่าเริ่มต้นของ Docker คือ 64 MB ซึ่งไม่พอเมื่อตารางใหญ่ และ query เหล่านั้นจะล้มด้วย "could not resize shared memory segment"

config อ่านฐานข้อมูลจาก DATABASE_URL เช่น postgres({ url: process.env.DATABASE_URL }) หรือ sqlite({ url: process.env.DATABASE_URL }) ถ้าใช้ SQLite ให้รัน container เดียว เพราะไฟล์ SQLite มี server ได้ตัวเดียว ถ้าใช้ image แบบ standalone ให้ map "4000:4000" และ mount volume data ที่ /app/data

ใส่ secret ในไฟล์ .env ข้าง compose.yaml (ไม่ใส่ใน image) แล้ว start:

printf 'EASY_CMS_SECRET=%s\nEASY_CMS_SETUP_CODE=%s\n' "$(openssl rand -hex 32)" "choose-a-phrase" > .env
docker compose up -d --build
docker compose logs -f app

log จะแสดง migration ที่รัน แล้วตามด้วย server เปิด http://localhost:3000/admin แล้วสร้าง admin คนแรกด้วยรหัส setup เปลี่ยนรหัสผ่าน Postgres ก่อนใช้ที่อื่นนอกจากเครื่องของคุณ

3. การอัปเดต

ดึงโค้ดใหม่แล้ว build ใหม่ container ใหม่จะรัน migration ที่ค้างก่อน start และ volume เก็บฐานข้อมูลกับไฟล์อัปโหลดไว้

git pull
docker compose up -d --build

4. HTTPS และ proxy

วาง reverse proxy ไว้ข้างหน้าสำหรับ HTTPS เช่น Caddy, Traefik, nginx หรือของแพลตฟอร์ม proxy จะตั้ง X-Forwarded-For ให้ จึงต้องเชื่อมัน: --trust-proxy (standalone ซึ่งอยู่ใน image ข้างบนแล้ว) หรือ trustProxy: true (route handler ของ Next.js, module ของ Nuxt) จากนั้นตั้ง serverURL เป็นที่อยู่สาธารณะ proxy ควร redirect HTTP ไป HTTPS ด้วย ตัวอย่างการตั้งค่า Caddy อยู่ใน VPS

สำรองข้อมูล

ทุกอย่างอยู่ใน volume: สำรอง volume pgdata หรือ data และ uploads หรือให้ Easy CMS สำรองฐานข้อมูลเอง (backups ใน config เก็บไว้บน volume อื่นหรือใน S3) ดู สำรองข้อมูล

ขั้นต่อไป

  • VPS: รันเว็บเดียวกันโดยไม่ใช้ container
  • โฮสต์อื่นๆ: แพลตฟอร์มที่ build และรัน image นี้ให้