kritsana.devpassing
runartifactsdocsworkhistorydispatch

© 2026 Kritsana Prasit — built from source on a self-hosted runner

resume.pdfrssdispatch

back to artifacts
category: devopsdate: 16 Nov 2026read: 5 min readsource: posts/coolify-umami-analytics-zero-visitors.mdx

Umami บน Coolify ขึ้น 0 Visitors: เจอ 3 บั๊กซ้อนกัน (503, Docker ARG และ Domain) แก้ทีละชั้น

ตั้ง Umami แบบ self-host บน Coolify แล้วหน้า dashboard ขึ้นแต่เลข 0 ไล่ดูทีละชั้นจนเจอว่าเป็นบั๊ก 3 ตัวซ้อนกัน: domain ผูกผิด app ทำให้เว็บหลักล่มด้วย 503, Dockerfile ไม่ได้ประกาศ ARG ให้ NEXT_PUBLIC_* และ Umami เองยังไม่ได้ผูก domain พร้อมวิธีตรวจแต่ละชั้นด้วย curl

Umami บน Coolify ขึ้น 0 Visitors: เจอ 3 บั๊กซ้อนกัน (503, Docker ARG และ Domain) แก้ทีละชั้น

เรื่องนี้เริ่มจากสิ่งที่ดูธรรมดามาก: ติดตั้ง Umami แบบ self-host บน Coolify สร้าง website ใน dashboard ก๊อป tracking code มาวาง แล้วรอดูตัวเลข — แต่ผ่านไปทั้งวัน ทุกช่องขึ้น 0 Visitors, Views, Bounce rate ไม่มีอะไรเข้ามาเลย

ตอนไล่หาสาเหตุ ผมพบว่ามันไม่ใช่บั๊กเดียว แต่เป็น 3 บั๊กซ้อนกัน และแต่ละตัวบังตัวถัดไปไว้ ถ้าแก้แค่ตัวเดียวอาการก็ยังเหมือนเดิม จึงเขียนบันทึกไว้เป็นลำดับการตรวจ เผื่อใครเจอแบบเดียวกัน

สรุป 3 บั๊กที่ทำให้ Umami ขึ้น 0 Visitors: domain ผูกผิด app, Dockerfile ไม่มี ARG และ Umami ยังไม่ผูก domain

ระบบที่ใช้อยู่

  • เว็บหลัก kritsana.dev เป็น Next.js 16 deploy บน Coolify ด้วย Dockerfile (multi-stage, output: 'standalone')
  • Umami 3.0.3 รันเป็น Service Stack ใน Coolify มาพร้อม PostgreSQL 16 (แยกอีกตัวจากเว็บหลัก)
  • DNS อยู่ที่ Cloudflare: kritsana.dev เป็น CNAME ผ่าน proxy, ส่วน umami.kritsana.dev เป็น A record แบบ DNS only (ไม่ผ่าน proxy)
  • เว็บหลักโหลด script ของ Umami ผ่าน next/script ใน layout.tsx โดยอ่านค่าจาก env 2 ตัว: NEXT_PUBLIC_UMAMI_URL และ NEXT_PUBLIC_UMAMI_WEBSITE_ID

โค้ดตรงนั้นเขียนไว้ว่า ถ้าตัวใดตัวหนึ่งไม่มีค่า จะไม่ render อะไรเลย เพื่อไม่ให้ dev หรือ deploy ที่ยังไม่ตั้งค่ามี request ไปหา third-party:

tsx
const umamiSrc = process.env.NEXT_PUBLIC_UMAMI_URL;
const umamiWebsiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
 
{umamiSrc && umamiWebsiteId && (
  <Script src={umamiSrc} data-website-id={umamiWebsiteId} strategy="afterInteractive" />
)}

ดีไซน์นี้ปลอดภัยดี แต่มีด้านที่ต้องระวัง: ถ้าค่าหายไป มันจะเงียบ ไม่มี error ไม่มี warning

บั๊กที่ 1: เว็บหลักล่ม เพราะ domain ผูกผิด app

ก่อนจะถึงเรื่อง analytics เจอปัญหาที่ใหญ่กว่า — เปิด kritsana.dev แล้วได้ HTTP 503 ทั้ง kritsana.dev และ www:

bash
$ curl -i https://kritsana.dev/
HTTP/2 503
server: cloudflare
no available server

ข้อความ no available server ไม่ได้มาจาก Cloudflare และไม่ใช่ Next.js แต่เป็นคำตอบของ Traefik (reverse proxy ที่ Coolify ใช้) เมื่อไม่มี router ไหนตรงกับ request

เมื่อเปิดหน้า Configuration ของ app เว็บหลัก ช่อง Domains ถูกตั้งเป็น https://umami.kritsana.dev — คือผมเผลอใส่ domain ของ Umami ลงใน app ผิดตัว

แผนภาพการ route: browser ผ่าน Cloudflare ไป Traefik ที่จับคู่ Host header ถ้าไม่ตรงกับกฎ ตอบ 503 ทั้งที่ app ปกติ

Traefik ทำงานโดยดู Host header ของ request แล้วหา router ที่ตรง ตอนนั้น app เว็บหลักมีกฎแค่ Host(umami.kritsana.dev) ดังนั้น request ที่มา Host: kritsana.dev จึงไม่ตรงกับอะไรเลย ทั้งที่ container ตัว app เองรันอยู่และ healthy

อีกเบาะแสที่ยืนยัน: URL แบบ sslip.io ของ app ก็ตอบ 404 เช่นกัน แปลว่าไม่มี router ที่ match จริง ๆ ส่วน DNS กับ Cloudflare ปกติดี

วิธีแก้: ที่ app เว็บหลัก ตั้ง Domains เป็น https://kritsana.dev,https://www.kritsana.dev แล้ว Save และ Redeploy

บทเรียน: เมื่อเจอ 503 no available server บน Coolify ให้ไปดู Domains ของ resource ก่อนเสมอ ไม่ต้องเริ่มที่ DNS หรือ container

บั๊กที่ 2: Dockerfile ไม่ได้ประกาศ ARG ทำให้ env ไม่เข้า build

พอเว็บกลับมา (200) แต่ Umami ยังเป็น 0 ลองดู HTML ที่ production ส่งออกมา:

bash
curl -s https://kritsana.dev/ | grep -i umami

ผลคือ ไม่เจออะไรเลย — ไม่มี <script> ของ Umami ในหน้าเลย แปลว่าปัญหาอยู่ก่อนที่ browser จะได้ส่งข้อมูลด้วยซ้ำ

สาเหตุ: ตัวแปรที่ขึ้นต้นด้วย NEXT_PUBLIC_ ถูก ฝังลงใน bundle ตอน next build ไม่ใช่อ่านตอน runtime และการที่เราใส่ใน Coolify Environment Variables ยังไม่พอ เพราะ Dockerfile แบบ multi-stage ต้องรับค่าผ่าน ARG ถึงจะส่งต่อเข้าไปใน stage ที่รัน pnpm build

แผนภาพ build-time env: ค่าจาก Coolify ต้องผ่าน ARG และ ENV ใน Dockerfile ก่อนถึง pnpm build ถ้าขาด ARG ค่าจะถูกทิ้งและ HTML ไม่มี script tag

Dockerfile ของผมประกาศ ARG ให้เฉพาะ NEXT_PUBLIC_BASE_URL เท่านั้น ส่วน Umami 2 ตัวไม่เคยถูกประกาศ ผลคือ pnpm build เห็นทั้งคู่เป็น undefined เงื่อนไขใน layout.tsx เป็น false และไม่ render script

วิธีแก้: เพิ่มก่อนบรรทัด RUN pnpm build:

dockerfile
# Read at prerender by src/app/layout.tsx; without ARG the vars never reach `pnpm build`.
ARG NEXT_PUBLIC_UMAMI_URL
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
ENV NEXT_PUBLIC_UMAMI_URL=$NEXT_PUBLIC_UMAMI_URL
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
 
RUN pnpm build

แล้วที่ Coolify → Environment Variables ของ app ตรวจว่าตัวแปร 2 ตัวนี้ติ๊ก Build Variable เปิดอยู่:

  • NEXT_PUBLIC_UMAMI_URL = https://umami.kritsana.dev/script.js
  • NEXT_PUBLIC_UMAMI_WEBSITE_ID = ค่า Website ID จากหน้า Settings ของเว็บใน Umami

ข้อควรจำ: เพราะค่าถูกฝังตอน build การกด Restart ไม่ช่วยอะไร ต้อง Redeploy ให้ build ใหม่

ส่วนวิธีตรวจ ไม่ต้องรอดูตัวเลขใน Umami เลย แค่ curl ดู HTML:

bash
curl -s https://kritsana.dev/ | grep -io '<script[^>]*umami[^>]*>'

ถ้าเห็น tag พร้อม data-website-id แปลว่าชั้นนี้ผ่านแล้ว

บั๊กที่ 3: Umami เองยังไม่ได้ผูก domain

ต่อให้ HTML มี tag แล้ว ข้อมูลก็ยังส่งไปไม่ถึง เพราะเมื่อลอง curl ฝั่ง Umami:

bash
curl -I https://umami.kritsana.dev/script.js     # 503
curl -X POST https://umami.kritsana.dev/api/send  # 503

ทั้งหน้า script และ endpoint ที่รับข้อมูล (/api/send) ตอบ 503 อาการเดียวกับบั๊กที่ 1 — Traefik ไม่มีกฎสำหรับ host นี้

เปิดหน้า Service ของ Umami ใน Coolify พบว่า Domains ยังเป็นค่าเริ่มต้นแบบ http://umami-xxxx.sslip.io:3000 ไม่มี umami.kritsana.dev อยู่เลย (ส่วน Postgres ที่มาด้วยกัน running healthy ปกติ)

ใน Coolify มีกล่อง warning เตือนไว้ว่า service นี้ ต้องใช้ port 3000 และทุก domain ต้องใส่ port นั้นด้วย ซึ่งเป็นพอร์ตของ container ที่ Coolify ใช้ route ไม่ใช่พอร์ตที่เปิดออกภายนอก

วิธีแก้: ตั้ง Domains ของ Umami เป็น

text
https://umami.kritsana.dev:3000
  • ต้องคง :3000 ไว้
  • ต้องเป็น https เพื่อให้ Traefik ขอใบรับรอง Let's Encrypt ให้เอง
  • กด Save แล้ว Restart service

ที่ฝั่ง Cloudflare record umami เป็น DNS only อยู่แล้ว ซึ่งทำให้ Let's Encrypt ออกใบรับรองได้เอง ข้อแลกเปลี่ยนคือ IP ของเซิร์ฟเวอร์จะมองเห็นได้จาก DNS (ถ้าเปิด proxy ต้องตั้ง SSL mode เป็น Full)

เรื่องที่พลาดได้อีกอย่างคือ tracking code ที่ Umami สร้างให้ใน dashboard ใช้ URL แบบ http://…sslip.io/script.js ถ้านำไปใช้ตรง ๆ กับเว็บที่เป็น https browser จะบล็อกเป็น mixed content เราจึงต้องใช้ URL แบบ https://umami.kritsana.dev/script.js แทน

ตรวจชั้นนี้ด้วย:

bash
curl -I https://umami.kritsana.dev/script.js   # ต้องได้ 200

ลำดับการตรวจที่ผมจะใช้ครั้งหน้า

ถ้า Umami ขึ้น 0 ให้ไล่จาก "ต้นทางของข้อมูล" ไปปลายทาง ใช้ curl อย่างเดียวก็จบ ไม่ต้องรอตัวเลข:

  • 1. เว็บหลักตอบ 200 ไหม — curl -I https://เว็บของคุณ/ ถ้า 503 ไปดู Domains ของ app ใน Coolify
  • 2. HTML มี script tag ไหม — curl -s https://เว็บของคุณ/ | grep -i umami ถ้าไม่มี ไปดู ARG ใน Dockerfile และ Build Variable
  • 3. Umami ส่ง script ได้ไหม — curl -I https://umami.ของคุณ/script.js ถ้า 503 ไปดู Domains ของ Umami และอย่าลืม :3000
  • 4. เปิดเว็บจริงใน incognito ปิด extension ที่บล็อก tracker แล้วดูใน dashboard ว่ามี visitor เข้ามา

ถ้าครบ 4 ข้อแล้วยังไม่เข้า ค่อยสงสัย adblock ฝั่งผู้ใช้ หรือ Cloudflare Rocket Loader ที่ไปแก้ script

ตัดต่อมาสั้น ๆ

  • 503 no available server บน Coolify = Traefik ไม่มี router ที่ตรง Host ให้ดู Domains ของ resource ก่อน
  • NEXT_PUBLIC_* เป็นค่า build-time ใน multi-stage Dockerfile ต้องมี ARG + ENV และติ๊ก Build Variable ใน Coolify ไม่งั้นค่าถูกทิ้งเงียบ ๆ และต้อง Redeploy ไม่ใช่ Restart
  • Umami บน Coolify ต้องใช้ :3000 ต่อท้าย domain และใช้ https เพื่อให้ได้ใบรับรอง
  • เลข 0 ใน dashboard บอกอะไรไม่ได้ เพราะมีหลายชั้นที่พังเงียบ ๆ ให้ตรวจด้วย curl ทีละชั้นเร็วกว่าเดา
  • เมื่อมีบั๊กหลายตัวซ้อนกัน การแก้ตัวแรกแล้วอาการไม่หาย ไม่ได้แปลว่าแก้ผิด แค่ตัวถัดไปยังบังอยู่

contents

  • ระบบที่ใช้อยู่
  • บั๊กที่ 1: เว็บหลักล่ม เพราะ domain ผูกผิด app
  • บั๊กที่ 2: Dockerfile ไม่ได้ประกาศ ARG ทำให้ env ไม่เข้า build
  • บั๊กที่ 3: Umami เองยังไม่ได้ผูก domain
  • ลำดับการตรวจที่ผมจะใช้ครั้งหน้า
  • ตัดต่อมาสั้น ๆ

related documents

devops · 3
  • AWS Emulator: ให้ AI Agent เขียนและทดสอบโค้ด AWS ได้ในเครื่องตัวเอง โดยไม่ต้องแตะบัญชีจริง

    13 Nov 2026 · 5 min read

  • เรื่องเล่าจากดาต้าเซ็นเตอร์: สัญญาณเตือนควันไฟหลอกทำให้น้ำดับเพลิงท่วมห้องเซิร์ฟเวอร์

    13 Nov 2026 · 3 min read

  • What is OpenClaw and How to Install OpenClaw in Docker

    06 Apr 2025 · 5 min read

comments