SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-13

วิธีติดตั้ง Planka บน VPS ด้วย Docker Compose

เรียนรู้วิธีติดตั้ง Planka ระบบ Kanban แบบ self-hosted บน VPS ด้วย Docker Compose พร้อมตั้งค่า Postgres และ Traefik รวมถึงการแก้ปัญหา BASE_URL ที่ทำให้เข้าสู่ระบบไม่ได้

สิ่งที่คุณจะได้รับจากการทำ self-host Planka

การทำ self-host Planka ช่วยให้ทีมของคุณมีกระดาน Kanban ที่ใช้รูปแบบการ์ด รายการ และป้ายกำกับแบบเดียวกับที่ทุกคนคุ้นเคยจาก Trello โดยรันอยู่บน VPS ที่คุณควบคุมเอง คุณจะไม่มีข้อจำกัดเรื่องจำนวนที่นั่งและไม่มีการเรียกเก็บเงินรายผู้ใช้ เนื่องจากค่าใช้จ่ายเดียวคือค่าเช่าเซิร์ฟเวอร์ คู่มือนี้จะแนะนำการติดตั้งด้วย Docker Compose โดยมี Traefik เป็น reverse proxy ด้านหน้า ใช้ Postgres สำหรับจัดเก็บข้อมูล และใช้ named volume สำหรับไฟล์ทุกไฟล์ที่มีการอัปโหลด

ผู้อ่านที่คู่มือนี้มุ่งเน้นคือทีมขนาด 2 ถึง 5 คนที่กำลังย้ายออกจากแผนบริการฟรีของ Trello หากคุณยังตัดสินใจไม่ได้ว่าจะใช้กระดานตัวไหน ให้ลองอ่าน การเปรียบเทียบทางเลือกสำหรับ Trello แบบ self-hosted ก่อน คู่มือนี้สมมติว่าคุณตัดสินใจเลือกแล้วและจะครอบคลุมเฉพาะขั้นตอนการติดตั้งเท่านั้น

คุณจำเป็นต้องมี VPS ที่รัน Docker Engine พร้อมปลั๊กอิน Compose และมี DNS A record ชี้มาที่ VPS นั้น นอกจากนี้คุณต้องมี instance ของ Traefik ที่ทำหน้าที่จัดการ TLS (transport layer security) อยู่บนเครื่องนั้นแล้ว หากคุณยังไม่มี Traefik ให้ตั้งค่า Traefik reverse proxy หน้าแอป Compose หลายตัว ก่อน และอ่าน พื้นฐาน Docker Compose สำหรับ VPS หากไฟล์ด้านล่างนี้ดูไม่คุ้นเคยสำหรับคุณ

Planka ต้องการ VPS ขนาดเท่าใด

โครงการไม่ได้กำหนดเกณฑ์ขั้นต่ำของฮาร์ดแวร์ไว้ ดังนั้นให้ถือว่าตัวเลขใดก็ตามที่คุณพบเป็นเพียงจุดเริ่มต้น ไม่ใช่ค่ามาตรฐานที่ผ่านการวัดผล ตัวเลข 2 vCPU และ 4 GB ที่หน้าเว็บโฮสติ้งมักระบุนั้นเป็นค่าเริ่มต้นที่ผู้ให้บริการแนะนำเพื่อให้ใช้งานได้สะดวก ไม่ใช่ข้อกำหนดที่โครงการได้ทำการทดสอบจริง ซึ่งถือว่าเกินความจำเป็นสำหรับบอร์ดที่มีผู้ใช้งาน 5 คน

สิ่งที่ทำงานจริงมีขนาดเล็ก ได้แก่ กระบวนการ Node.js หนึ่งรายการที่ให้บริการ API และ frontend ที่ build ไว้แล้ว และกระบวนการ Postgres หนึ่งรายการที่เก็บข้อมูล นอกจากนี้ยังมีกระบวนการ proxy ขนาดเล็กอีกหนึ่งรายการที่ทำงานภายใน container ของ Planka เพื่อกรองคำขอขาออก แผนการใช้งาน 1 vCPU และ 2 GB สามารถรองรับบอร์ดที่มีผู้ใช้งาน 2 ถึง 5 คนได้ โดยหน่วยความจำส่วนที่เหลือส่วนใหญ่จะถูกใช้เป็น cache ของ Postgres

ควรประเมินขนาดดิสก์ก่อนหน่วยความจำ เนื่องจากไฟล์แนบเป็นส่วนที่จะเพิ่มขนาดขึ้นเรื่อยๆ ให้วัดค่าจาก instance ของคุณเองแทนการเชื่อข้อมูลในย่อหน้านี้:

docker stats --no-stream
docker system df -v

คำสั่งแรกจะแสดงการใช้งานหน่วยความจำและ CPU แบบเรียลไทม์ของแต่ละ container ส่วนคำสั่งที่สองจะแสดงพื้นที่ที่แต่ละ volume ใช้งานอยู่ ให้เก็บข้อมูลทั้งสองค่าหลังจากผ่านสัปดาห์การทำงานปกติ ไม่ใช่ในวันที่ติดตั้ง เพราะบอร์ดที่ไม่มีการใช้งานจะไม่สามารถบอกข้อมูลการใช้งานจริงของทีมคุณได้

เขียนไฟล์ Compose

สร้างไดเรกทอรีและกำหนดสิทธิ์ความเป็นเจ้าของ เพื่อให้คุณไม่ต้องแก้ไขไฟล์เหล่านี้ผ่าน sudo อีกต่อไป

sudo mkdir -p /opt/planka
sudo chown "$USER":"$USER" /opt/planka
cd /opt/planka

สร้างข้อมูลลับลงในไฟล์ .env ไว้ข้างไฟล์ Compose โดย Compose จะอ่านไฟล์ดังกล่าวโดยอัตโนมัติและแทนที่ค่าต่างๆ ให้

umask 077
{
  printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 64)"
  printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)"
  printf 'ADMIN_PASSWORD=%s\n' "$(openssl rand -hex 12)"
} > .env
chmod 600 .env

การใช้ openssl rand -hex นั้นมีจุดประสงค์ชัดเจน เนื่องจากสตริงเลขฐานสิบหกประกอบด้วยตัวเลขและตัวอักษร a ถึง f เท่านั้น จึงไม่ทำให้สตริงการเชื่อมต่อ DATABASE_URL ที่นำไปวางเกิดข้อผิดพลาด ในขณะที่รหัสผ่านแบบ base64 ที่มีเครื่องหมายทับหรือเครื่องหมาย at อาจทำให้เกิดข้อผิดพลาดในการเชื่อมต่อซึ่งดูเหมือนชื่อโฮสต์ผิดพลาด และนั่นจะทำให้คุณเสียเวลาแก้ไขนานนับชั่วโมง รูปแบบที่กว้างกว่านี้ครอบคลุมอยู่ใน การเก็บข้อมูลลับไว้นอกไฟล์ Compose

ตอนนี้ให้ใช้ docker-compose.yml โดยแทนที่ kanban.example.com ด้วยชื่อโฮสต์ของคุณเองในทั้งสองตำแหน่งที่ปรากฏ

services:
  planka:
    image: ghcr.io/plankanban/planka:2.1.1
    restart: unless-stopped
    volumes:
      - planka-data:/app/data
    environment:
      - BASE_URL=https://kanban.example.com
      - DATABASE_URL=postgresql://planka:${POSTGRES_PASSWORD}@postgres/planka
      - SECRET_KEY=${SECRET_KEY}
      - TRUST_PROXY=true
      - DEFAULT_ADMIN_EMAIL=you@example.com
      - DEFAULT_ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - DEFAULT_ADMIN_NAME=Your Name
      - DEFAULT_ADMIN_USERNAME=admin
    networks:
      - proxy
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.planka.rule=Host(`kanban.example.com`)"
      - "traefik.http.routers.planka.entrypoints=websecure"
      - "traefik.http.routers.planka.tls.certresolver=default"
      - "traefik.http.services.planka.loadbalancer.server.port=1337"
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=planka
      - POSTGRES_USER=planka
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U planka -d planka"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  planka-data:
  db-data:

networks:
  proxy:
    external: true
  internal:

การตัดสินใจสี่ประการในไฟล์นั้นควรค่าแก่การอธิบาย เพราะเป็นจุดที่ผู้คนมักเปลี่ยนแล้วต้องมาเสียใจภายหลัง

  • ไม่มีบล็อก ports: ในบริการ Planka เนื่องจาก Traefik เข้าถึงคอนเทนเนอร์ผ่านเครือข่าย proxy ดังนั้นพอร์ต 1337 จึงไม่ถูกเปิดเผยบนโฮสต์ การเปิดพอร์ตดังกล่าวจะเปิดช่องทางให้ผู้อื่นเลี่ยงผ่านพร็อกซีและใบรับรองของคุณได้
  • loadbalancer.server.port=1337 ระบุพอร์ตภายในคอนเทนเนอร์ Planka ฟังการเชื่อมต่อที่พอร์ต 1337 และตัวอย่าง upstream ทั่วไปจะเข้าถึงได้ที่พอร์ต 3000 เพราะมีการแมปพอร์ตไปยังโฮสต์ แต่ในที่นี้ไม่มีการแมปโฮสต์ จึงต้องระบุพอร์ตของคอนเทนเนอร์ให้ Traefik ทราบ
  • condition: service_healthy ทำงานร่วมกับการตรวจสอบสถานะ (healthcheck) ของ Postgres หากไม่มีส่วนนี้ Planka จะเริ่มทำงานก่อนที่ฐานข้อมูลจะพร้อมรับการเชื่อมต่อ ทำให้การสอบถามข้อมูลครั้งแรกล้มเหลวและโปรแกรมหยุดทำงาน ซึ่งดูเหมือนอาการ crash loop กลไกการทำงานอยู่ใน การตรวจสอบสถานะและการจัดลำดับการเริ่มทำงานของ Compose
  • บริการฐานข้อมูลถูกตั้งชื่อว่า postgres โดยเจตนา เนื่องจาก Planka 2 จะส่งคำขอขาออกผ่านตัวกรองภายในซึ่งมีรายการบล็อกเริ่มต้นคือ localhost,postgres หากคุณเปลี่ยนชื่อบริการ คุณจะนำฐานข้อมูลของคุณออกจากรายการดังกล่าวโดยไม่ตั้งใจ

ตรวจสอบว่า Compose สามารถมองเห็นข้อมูลลับของคุณก่อนที่จะเริ่มดำเนินการใดๆ:

docker compose config | grep -E 'image:|BASE_URL|POSTGRES_USER'

คำสั่งนี้จะแสดงเนื้อหาไฟล์โดยที่ค่า .env ถูกแทนที่เรียบร้อยแล้ว หากค่าว่างเปล่าแสดงว่า Compose ไม่ได้อ่านไฟล์ .env ซึ่งมักเกิดจากการที่คุณรันคำสั่งจากไดเรกทอรีอื่น

หน้าที่จริงของตัวแปร bootstrap สำหรับผู้ดูแลระบบ

ตั้งแต่ Planka 1.13 เป็นต้นมา ระบบจะไม่มีการสร้างบัญชีผู้ดูแลระบบให้โดยอัตโนมัติ ดังนั้นฐานข้อมูลที่ติดตั้งใหม่จะไม่มีผู้ใช้ที่สามารถเข้าสู่ระบบได้ กลุ่มตัวแปร DEFAULT_ADMIN_* จึงเป็นหนึ่งในสองวิธีในการแก้ไขปัญหานี้

เมื่อเริ่มต้นระบบ Planka จะตรวจสอบหาผู้ใช้ที่ตรงกับ DEFAULT_ADMIN_EMAIL หากไม่พบ ระบบจะสร้างบัญชีขึ้นมาใหม่โดยใช้รหัสผ่าน ชื่อที่แสดง และชื่อผู้ใช้ที่กำหนดไว้คู่กัน กระบวนการนี้จะเกิดขึ้นในการบูตครั้งแรกที่พบฐานข้อมูลว่างเปล่า ดังนั้นตัวแปรเหล่านี้จึงทำหน้าที่สร้างบัญชีเริ่มต้น (bootstrap) มากกว่าที่จะใช้จัดการบัญชีในระยะยาว

DEFAULT_ADMIN_EMAIL มีหน้าที่อีกอย่างหนึ่งที่มักทำให้ผู้ใช้สับสน ในขณะที่ตัวแปรนี้ยังคงถูกตั้งค่าไว้ บัญชีที่ระบุชื่อไว้จะไม่สามารถแก้ไขหรือลบผ่านหน้าจออินเทอร์เฟซโดยใครได้เลย นี่คือกลไกป้องกันการล็อกเอาต์ และเป็นเหตุผลว่าทำไมคุณจึงไม่สามารถเปลี่ยนชื่อบัญชีหรือเปลี่ยนที่อยู่อีเมลของบัญชีนั้นใน UI ได้ หากต้องการแก้ไข ให้ลบตัวแปรดังกล่าวออกแล้วรีสตาร์ทระบบ บัญชีนั้นจะกลับมาเป็นบัญชีผู้ดูแลระบบปกติที่คุณสามารถแก้ไขได้เหมือนบัญชีอื่น

บรรทัดรหัสผ่านเป็นส่วนที่ต้องระมัดระวัง ข้อมูลใดๆ ภายใต้ environment: สามารถอ่านได้โดยทุกคนที่สามารถรันคำสั่ง docker inspect บนคอนเทนเนอร์ได้ ดังนั้น DEFAULT_ADMIN_PASSWORD จึงไม่ควรถูกเก็บไว้ในนั้นอย่างถาวร ให้เข้าสู่ระบบ เปลี่ยนรหัสผ่านในอินเทอร์เฟซ ลบบรรทัดดังกล่าวออก แล้วรัน docker compose up -d อีกครั้ง

วิธีที่สะอาดกว่าคือการข้ามการใช้ตัวแปรเหล่านี้ไปเลย ให้ใส่เครื่องหมายคอมเมนต์ปิดกลุ่ม DEFAULT_ADMIN_* ทั้งหมดไว้ แล้วสร้างบัญชีผ่านการโต้ตอบแทน:

docker compose run --rm planka npm run db:create-admin-user

ระบบจะถามข้อมูลอีเมล รหัสผ่าน ชื่อที่แสดง และชื่อผู้ใช้ (ไม่บังคับ) จากนั้นจะเขียนข้อมูลผู้ใช้ลงในฐานข้อมูลโดยตรง รหัสผ่านจะไม่ถูกบันทึกไว้ในไฟล์ Compose หรือสภาพแวดล้อมของคอนเทนเนอร์ ให้ใช้วิธีนี้หากมีบุคคลอื่นที่มีสิทธิ์เข้าถึง shell ของ VPS คำสั่งนี้จะเริ่มการทำงานของ Postgres ก่อนเนื่องจาก depends_on ดังนั้นจึงสามารถใช้งานได้แม้ใน stack ที่ยังไม่เคยถูกเปิดใช้งานมาก่อน

ไม่ว่าจะใช้วิธีใด คุณยังคงต้องจัดการรหัสผ่านของ Planka ด้วยตนเอง และหากนี่เป็นชุดข้อมูลรับรองชุดที่สี่ที่ทีมของคุณต้องดูแล Planka สามารถเปลี่ยนไปใช้วิธีมอบหมายการเข้าสู่ระบบให้กับผู้ให้บริการ OIDC ได้ เช่น Authentik ที่รันเป็นเซิร์ฟเวอร์ single sign-on ของคุณเอง โดยให้เก็บบัญชีผู้ดูแลระบบที่สร้างจาก bootstrap ไว้เป็นบัญชีสำรองสำหรับกรณีฉุกเฉินในวันที่ผู้ให้บริการหลักไม่สามารถใช้งานได้

เหตุใด BASE_URL จึงทำให้การเข้าสู่ระบบล้มเหลวเมื่อไม่ตรงกับชื่อโฮสต์

BASE_URL คือที่อยู่ที่ผู้ใช้พิมพ์ลงในเบราว์เซอร์โดยระบุ scheme และไม่มีเครื่องหมายทับปิดท้าย สำหรับ stack นี้คือ https://kanban.example.com แอป Planka จะสร้างลิงก์และเชื่อมต่อ WebSocket โดยอ้างอิงจากค่านี้ ดังนั้นหาก BASE_URL ไม่ถูกต้อง คุณจะไม่ได้รับข้อความแจ้งเตือนข้อผิดพลาดที่ชัดเจน แต่จะพบกับหน้าเว็บที่โหลดค้างอยู่ตลอดเวลา

กรณีที่พบบ่อยคือ คุณคัดลอกตัวอย่างจากต้นทางโดยปล่อยให้ BASE_URL=http://localhost:3000 เป็นค่าเดิม แล้วเข้าใช้งานเว็บไซต์ผ่าน HTTPS ด้วยโดเมนจริงของคุณ เมื่อส่งแบบฟอร์มเข้าสู่ระบบ ข้อมูลประจำตัวของคุณจะได้รับการยอมรับ แต่หน้าบอร์ดจะไม่แสดงผล หากคุณเปิด console สำหรับนักพัฒนาในเบราว์เซอร์ คุณจะเห็นว่าคำขอไปยัง /socket.io/ ล้มเหลว เนื่องจากไคลเอนต์ได้รับคำสั่งให้เปิดการเชื่อมต่อแบบ live ไปยัง localhost:3000 ซึ่งที่อยู่ดังกล่าวไม่มีอยู่จริงบนเครื่องของคุณ

TRUST_PROXY=true คืออีกส่วนหนึ่งของปัญหาเดียวกัน Planka ทำงานอยู่หลัง Traefik ดังนั้นทุกคำขอจะส่งมาจากที่อยู่ของ proxy ผ่าน HTTP ธรรมดาภายในเครือข่าย Docker หากไม่มี TRUST_PROXY แอปจะเพิกเฉยต่อ header X-Forwarded-Proto และ X-Forwarded-For ที่ Traefik กำหนดไว้ ทำให้แอปเข้าใจว่าการเชื่อมต่อไม่ปลอดภัยและมองว่าไคลเอนต์ทุกรายใช้ IP address เดียวกัน เมื่อตั้งค่านี้แล้ว แอปจะอ่าน header ดังกล่าวและรับทราบ scheme ที่ตรงกับเบราว์เซอร์

Traefik สามารถทำ proxy สำหรับ WebSockets ได้โดยไม่ต้องตั้งค่าเพิ่มเติม ซึ่งเป็นเหตุผลที่แนะนำให้ใช้ในกรณีนี้ สำหรับ nginx นั้น socket.io จำเป็นต้องมีบล็อก location ของตนเองที่ระบุ proxy_set_header Upgrade $http_upgrade และ proxy_set_header Connection "upgrade" มิฉะนั้นคุณจะพบปัญหาหน้าเว็บโหลดค้างเช่นเดิมจากสาเหตุที่ต่างออกไป

การย้ายบอร์ดไปยังชื่อโฮสต์ใหม่ในภายหลังจำเป็นต้องเปลี่ยนสองสิ่งควบคู่กัน คือค่า BASE_URL และกฎ Host() ของ Traefik หากเปลี่ยนเพียงอย่างใดอย่างหนึ่งโดยลืมอีกอย่าง คุณจะกลับไปพบปัญหาหน้าเว็บโหลดค้างอีกครั้ง การให้บริการ Planka ผ่าน subpath เช่น https://example.com/planka สามารถทำได้ตั้งแต่เวอร์ชัน 2.1.0 เป็นต้นไป ซึ่งปล่อยออกมาในเดือนมีนาคม 2026 สำหรับ tag ที่เก่ากว่านี้ ควรใช้ subdomain แยกต่างหากแทน

ตำแหน่งที่ Planka จัดเก็บไฟล์แนบและรูปโปรไฟล์

Planka 2 จัดเก็บทุกสิ่งที่ผู้ใช้อัปโหลดไว้ภายใต้ path เดียวภายใน container คือ /app/data ไม่ว่าจะเป็นไฟล์แนบ รูปโปรไฟล์ผู้ใช้ หรือภาพพื้นหลังของบอร์ด ทั้งหมดจะถูกเก็บไว้ที่นี่ ในเวอร์ชัน 1 จะมีการใช้ไดเรกทอรีแยกกัน 3 แห่ง ดังนั้นหากคัดลอก Compose file มาจากบทความเก่าจะทำให้เกิดการ mount path ที่ไม่มีอยู่จริง และข้อมูลจริงจะไม่ได้ถูก mount ออกมา

การ mount เพียงจุดเดียวนี้คือความแตกต่างระหว่างบอร์ดที่ยังคงอยู่หลังการอัปเกรดกับบ่ายวันที่แสนวุ่นวาย หาก /app/data ไม่ได้อยู่บน volume ไฟล์ที่อัปโหลดจะไปอยู่ใน writable layer ของ container ซึ่ง layer นี้จะถูกทำลายเมื่อมีการสร้าง container ใหม่ และ container จะถูกสร้างใหม่ทุกครั้งที่คุณเปลี่ยน image tag บอร์ดจะกลับมาดูเหมือนปกติ การ์ดทุกใบยังอยู่ครบ แต่ลิงก์ไฟล์แนบทั้งหมดจะใช้งานไม่ได้ เพราะแถวข้อมูลในฐานข้อมูลยังคงชี้ไปยังไฟล์ที่ไม่มีอยู่แล้ว

Named volume ใน Compose file ด้านบนช่วยป้องกันปัญหานี้ได้ การใช้ bind mount ก็ทำได้เช่นกันและช่วยให้สำรองข้อมูลไฟล์ได้ง่ายขึ้นด้วยเครื่องมือทั่วไป แต่ต้องมีขั้นตอนเพิ่มเติมอีกหนึ่งอย่าง เนื่องจากกระบวนการ Node ภายใน container ทำงานด้วย UID 1000 ดังนั้นไดเรกทอรีบน host ที่มี root เป็นเจ้าของจะทำให้เกิดข้อผิดพลาดด้านสิทธิ์เมื่อมีการอัปโหลดครั้งแรก:

sudo chown -R 1000:1000 /opt/planka/data

ข้อดีและข้อเสียของทั้งสองวิธีได้อธิบายไว้ใน bind mounts เทียบกับ named volumes

หากไฟล์แนบมีขนาดใหญ่เกินกว่าพื้นที่ดิสก์ในแผนของคุณ Planka สามารถเขียนไฟล์เหล่านั้นไปยังที่จัดเก็บที่รองรับ S3 แทนได้ ผ่านทาง S3_ENDPOINT, S3_BUCKET และตัวแปร key ที่เกี่ยวข้อง ซึ่งสามารถชี้ไปยัง bucket ที่เช่าใช้บริการอยู่ หรือชี้ไปยัง MinIO object store ที่คุณโฮสต์เอง บนเครื่องอื่น ควรตัดสินใจเรื่องนี้ก่อนที่ทีมงานจะเริ่มใช้งานบอร์ด เพราะการตั้งค่านี้จะมีผลกับการอัปโหลดใหม่เท่านั้น

เริ่มการทำงานของ stack และตรวจสอบความถูกต้อง

docker compose pull
docker compose up -d
docker compose ps

docker compose ps ควรแสดงผล postgres เป็น healthy และ planka เป็น running หาก Planka มีการรีสตาร์ทแบบวนซ้ำ ให้ตรวจสอบการเชื่อมต่อฐานข้อมูลเป็นอันดับแรก ไม่ใช่ตัวแอปพลิเคชัน

docker compose logs -f planka

การเริ่มต้นระบบครั้งแรกที่สมบูรณ์จะดำเนินการ database migrations จากนั้นจะรายงานว่าเซิร์ฟเวอร์กำลังรอรับการเชื่อมต่อที่พอร์ต 1337 ให้ยืนยันว่า schema ถูกสร้างขึ้นจริงโดยการสอบถาม Postgres โดยตรง แทนที่จะเชื่อเพียงแค่ log:

docker compose exec postgres psql -U planka -d planka -c '\dt'

รายการตารางที่รวมถึง board และ card หมายความว่า migrations ทำงานสำเร็จ หากพบข้อความ "Did not find any relations" แสดงว่า Planka ไม่สามารถเชื่อมต่อได้ ให้เปรียบเทียบค่า DATABASE_URL กับค่า POSTGRES_USER และ POSTGRES_PASSWORD ในไฟล์ .env ของคุณ

จากนั้นให้ตรวจสอบเส้นทางการเชื่อมต่อจากเครื่องของคุณเอง ไม่ใช่จาก VPS:

curl -I https://kanban.example.com

HTTP/2 200 หมายความว่า Traefik ถือ certificate อยู่และสามารถเข้าถึง container ได้ หากได้รับข้อความ 404 จาก Traefik แสดงว่า router labels ไม่ตรงกัน ซึ่งมักเกิดจากการที่ container ไม่ได้เชื่อมต่อกับเครือข่าย proxy จากนั้นให้เปิดเว็บไซต์และเข้าสู่ระบบด้วยบัญชีผู้ดูแลระบบ (admin)

สำรองข้อมูลด้วย pg_dump ก่อนการอัปเกรดเวอร์ชันทุกครั้ง

ข้อมูลของบอร์ดจะถูกจัดเก็บไว้ในสองส่วนแยกกัน ดังนั้นการสำรองข้อมูลจึงต้องครอบคลุมทั้งฐานข้อมูล Postgres และ volume ของ planka-data ให้ทำการ dump ฐานข้อมูลในขณะที่ stack กำลังทำงานอยู่

docker compose exec -T postgres pg_dump -U planka -d planka > "planka-db-$(date +%F).sql"

การใช้ -T เป็นสิ่งที่จำเป็น หากไม่มี flag นี้ Compose จะจัดสรร pseudo-terminal ขึ้นมา ซึ่งเลเยอร์ของ terminal จะทำการเขียนบรรทัดใหม่ (line endings) ใน stream ข้อมูล ส่งผลให้ไฟล์ dump ที่ได้เกิดความเสียหายและไม่สามารถกู้คืนได้สำเร็จ ข้อผิดพลาดนี้อาจปรากฏขึ้นหลังจากผ่านไปหลายสัปดาห์ ซึ่งเป็นช่วงเวลาที่เลวร้ายที่สุดในการพบปัญหา

จากนั้นให้สำรองข้อมูลส่วน uploads โดยเริ่มจากการค้นหาชื่อ volume ที่แท้จริงก่อน เนื่องจาก Compose จะใส่ชื่อไดเรกทอรีของโปรเจกต์นำหน้าชื่อ volume เสมอ

docker volume ls | grep planka
docker run --rm -v planka_planka-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/planka-files-$(date +%F).tgz -C /data .

โปรเจกต์นี้ยังมี docker-backup.sh และ docker-restore.sh มาให้ใน repository และเอกสารอย่างเป็นทางการแนะนำให้ตั้งค่าเป็นงาน cron job รายวัน ซึ่งทั้งสองวิธีนี้สามารถใช้งานได้ แต่สิ่งที่ยอมรับไม่ได้คือการมีไฟล์สำรองข้อมูลที่คุณไม่เคยทดสอบกู้คืนมาก่อน ดังนั้นควรลองกู้คืนข้อมูลลงใน VPS สำรองสักครั้งเพื่อยืนยันว่าคุณสามารถเข้าสู่ระบบและเปิดไฟล์แนบได้จริง

ให้รันคำสั่ง dump ทันทีก่อนการเปลี่ยนเวอร์ชันทุกครั้ง การสำรองข้อมูลจากเมื่อคืนไม่ถือว่าเป็นข้อมูลชุดเดียวกับการสำรองข้อมูลก่อนการย้ายระบบ (migration) ที่คุณกำลังจะดำเนินการ

ตรึงแท็กและอ่านบันทึกประจำรุ่น (release notes)

แท็กของอิมเมจทั้งสองรายการในไฟล์นั้นถูกตรึงไว้โดยเจตนา

ghcr.io/plankanban/planka:2.1.1 เป็นรุ่นที่ระบุไว้อย่างชัดเจน ซึ่งเป็นรุ่นปัจจุบัน ณ เดือนสิงหาคม 2026 ส่วน latest จะเปลี่ยนแปลงทุกครั้งที่มีการเผยแพร่จากต้นทาง ดังนั้นการรัน docker compose pull ตามปกติอาจทำให้เกิดการย้ายโครงสร้างฐานข้อมูล (schema migration) ในช่วงเวลาที่คุณไม่ได้เลือก โปรดอ่าน บันทึกประจำรุ่น ก่อนที่คุณจะเปลี่ยนตัวเลขดังกล่าว เนื่องจากเป็นที่ที่ระบุถึงการเปลี่ยนแปลงที่ส่งผลกระทบต่อระบบ (breaking changes) และการแก้ไขด้านความปลอดภัย เวอร์ชัน 2.0.3 ถูกเผยแพร่ในฐานะรุ่นที่เน้นความปลอดภัย ซึ่งเป็นข้อมูลประเภทที่คุณควรทราบก่อนที่จะได้รับผลกระทบโดยไม่ตั้งใจ

postgres:16-alpine ถูกตรึงไว้ที่เวอร์ชันหลัก (major version) ด้วยเหตุผลที่สำคัญกว่า Postgres เขียนไดเรกทอรีข้อมูลในรูปแบบที่ผูกติดกับเวอร์ชันหลัก และเซิร์ฟเวอร์จะปฏิเสธการเปิดไดเรกทอรีที่เขียนโดยเวอร์ชันอื่น หากคุณเขียน postgres:latest แล้วปล่อยให้แท็กเปลี่ยนเป็น 17 คอนเทนเนอร์จะไม่สามารถเริ่มทำงานได้:

FATAL:  database files are incompatible with server
DETAIL:  The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.

ข้อมูลไม่มีการสูญหาย และการรีสตาร์ทก็ไม่สามารถแก้ไขปัญหานี้ได้ การย้ายไปสู่ Postgres เวอร์ชันหลักใหม่หมายถึงการทำ dump ข้อมูลจากเวอร์ชันเก่าและ restore ลงในไดเรกทอรีข้อมูลใหม่บนเวอร์ชันถัดไป นี่เป็นงานที่ต้องวางแผนโดยหยุดการทำงานของ stack ไว้ ไม่ใช่ผลข้างเคียงจากการดึงอิมเมจ (image pull)

หากคุณกำลังย้ายการติดตั้ง Planka 1.x ที่มีอยู่เดิมแทนที่จะเริ่มใหม่ทั้งหมด การอัปเกรดนั้นมีขั้นตอนที่ระบุไว้ใน เอกสารประกอบของโครงการ และไม่มีวิธีใดที่จะย้อนกลับไปใช้เวอร์ชัน 1 ได้หากไม่มีการสำรองข้อมูลไว้ล่วงหน้า

รูปแบบความล้มเหลวและข้อความที่คุณจะพบ

Planka รีสตาร์ทวนซ้ำและ log ระบุถึงฐานข้อมูล ข้อมูลรับรองใน DATABASE_URL ไม่ตรงกับสภาพแวดล้อมของ Postgres โปรดทราบว่า POSTGRES_PASSWORD จะถูกนำไปใช้เฉพาะเมื่อมีการเริ่มต้นไดเรกทอรีข้อมูลเป็นครั้งแรกเท่านั้น ดังนั้นการแก้ไขตัวแปรหลังจากบูตครั้งแรกที่ผิดพลาดจะไม่ส่งผลใดๆ คุณต้องลบ volume db-data ออกแล้วเริ่มใหม่

เข้าสู่ระบบสำเร็จแต่บอร์ดไม่โหลด BASE_URL ไม่ตรงกับที่อยู่ในแถบเบราว์เซอร์ หรือ TRUST_PROXY หายไป คอนโซลของเบราว์เซอร์จะแสดงคำขอที่ล้มเหลวไปยัง /socket.io/

การอัปโหลดล้มเหลวในขณะที่ส่วนอื่นทำงานได้ปกติ เกิดจาก bind mount ที่มี root เป็นเจ้าของ ให้รัน sudo chown -R 1000:1000 บนไดเรกทอรีของโฮสต์แล้วรีสตาร์ทคอนเทนเนอร์

ไฟล์แนบหายไปหลังจากการอัปเกรด /app/data ไม่ได้อยู่บน volume ไฟล์จึงค้างอยู่ในเลเยอร์ของคอนเทนเนอร์ซึ่งถูกแทนที่ไปแล้วระหว่างการอัปเกรด ให้กู้คืนไฟล์จากข้อมูลสำรอง จากนั้นเพิ่ม volume ก่อนที่คุณจะเปลี่ยน image tag อีกครั้ง

Traefik ส่งคืน 404 คอนเทนเนอร์ไม่ได้อยู่บนเครือข่าย proxy หรือกฎ Host() ไม่ตรงกับระเบียน DNS ของคุณ docker compose config จะแสดง labels หลังจากแทนที่ค่าแล้ว ซึ่งเป็นจุดที่สามารถมองเห็นคำผิดได้

การแจ้งเตือนหรือ webhooks ไม่ทำงาน Planka 2 ส่งคำขอ HTTP ขาออกผ่านตัวกรองภายใน และรายการบล็อกเริ่มต้นครอบคลุมถึง localhost และ postgres ดังนั้น webhook ที่มุ่งเป้าไปยังคอนเทนเนอร์อื่นบนโฮสต์เดียวกันอาจถูกบล็อกโดยการออกแบบ ให้ปรับแต่ง OUTGOING_ALLOWED_HOSTS แทนการลบตัวกรองออก

เมื่อระบบทำงานแล้ว ภาระในการดูแลรักษาจะต่ำ ให้ติดตาม release notes และสำรองข้อมูลฐานข้อมูลก่อนการอัปเกรดทุกครั้ง การรีบูตจะทำให้ stack กลับมาทำงานได้เองเนื่องจาก restart: unless-stopped ตราบใดที่บริการ Docker ถูกเปิดใช้งานตอนบูต และ Compose stacks ที่กลับมาทำงานหลังรีบูต จะครอบคลุมกรณีที่ระบบไม่กลับมาทำงานเอง

FAQ

ทำไม Planka ถึงโหลดค้างหลังจากที่ฉันเข้าสู่ระบบ?

ระบบยอมรับข้อมูลประจำตัวแต่การเชื่อมต่อแบบ live ไม่สำเร็จ Planka สร้าง URL ของ WebSocket จาก BASE_URL ดังนั้นหากตัวแปรดังกล่าวยังคงเป็น http://localhost:3000 ในขณะที่คุณเข้าถึงเว็บไซต์ผ่าน https://kanban.example.com เบราว์เซอร์จะพยายามเปิด socket ไปยังที่อยู่ที่ไม่มีอยู่จริงบนเครื่องของคุณ คอนโซลสำหรับนักพัฒนาจะแสดงคำขอที่ล้มเหลวไปยัง /socket.io/ ให้ตั้งค่า BASE_URL เป็นที่อยู่สาธารณะที่ถูกต้องโดยไม่มีเครื่องหมายทับปิดท้าย เพิ่ม TRUST_PROXY=true เพื่อให้แอปยอมรับ header X-Forwarded-Proto จาก reverse proxy ของคุณ จากนั้นรันคำสั่ง docker compose up -d

ฉันจะสร้างผู้ดูแลระบบคนแรกของ Planka ได้อย่างไร?

ตั้งแต่เวอร์ชัน 1.13 เป็นต้นมา จะไม่มีการสร้างผู้ดูแลระบบโดยอัตโนมัติ คุณสามารถตั้งค่า DEFAULT_ADMIN_EMAIL พร้อมกับตัวแปรรหัสผ่าน ชื่อ และชื่อผู้ใช้ที่ตรงกันแล้วเริ่ม stack หรือรันคำสั่ง docker compose run --rm planka npm run db:create-admin-user แล้วตอบคำถามตามที่ระบบแจ้ง คำสั่งแบบโต้ตอบมีความปลอดภัยมากกว่าบนเซิร์ฟเวอร์ที่ใช้งานร่วมกัน เนื่องจากรหัสผ่านจะไม่ถูกส่งเข้าไปในสภาพแวดล้อมของ container ที่ซึ่ง docker inspect สามารถอ่านได้ การคงค่า DEFAULT_ADMIN_EMAIL ไว้หลังจากนั้นจะล็อกบัญชีดังกล่าวไม่ให้แก้ไขหรือลบผ่านหน้าจออินเทอร์เฟซ

Planka จัดเก็บไฟล์แนบและรูปโปรไฟล์ไว้ที่ไหน?

ไฟล์ที่อัปโหลดทั้งหมดจะอยู่ใน /app/data ภายใน container ใน Planka 2 รวมถึงไฟล์แนบ รูปโปรไฟล์ผู้ใช้ และพื้นหลังของบอร์ด ให้ mount path ดังกล่าวเข้ากับ named volume หากไม่ได้ mount ไฟล์จะถูกเก็บไว้ใน writable layer ของ container และจะถูกลบเมื่อมีการสร้าง container ใหม่ ซึ่งเกิดขึ้นทุกครั้งที่มีการอัปเกรด image การใช้ bind mount ก็ทำได้เช่นกัน แต่เนื่องจากกระบวนการ Node ทำงานด้วย UID 1000 คุณจึงต้องรันคำสั่ง sudo chown -R 1000:1000 บนไดเรกทอรีของ host ไม่เช่นนั้นการอัปโหลดจะล้มเหลวด้วยข้อผิดพลาดด้านสิทธิ์

การ self-host Planka ต้องใช้ RAM เท่าไหร่?

โครงการไม่ได้กำหนดความต้องการฮาร์ดแวร์ขั้นต่ำไว้ ตัวเลข 2 vCPU และ 4 GB ที่ปรากฏบนหน้าเว็บของผู้ให้บริการเป็นค่าเริ่มต้นของผู้ให้บริการมากกว่าจะเป็นการวัดผลจริง และถือว่าเพียงพอสำหรับบอร์ดขนาดเล็ก ภาระงานทั้งหมดประกอบด้วยกระบวนการ Node หนึ่งรายการและกระบวนการ Postgres หนึ่งรายการ ดังนั้นแผน 1 vCPU และ 2 GB จึงรองรับทีมขนาด 2 ถึง 5 คนได้ ให้รันคำสั่ง docker stats --no-stream หลังจากใช้งานตามปกติครบหนึ่งสัปดาห์แล้วจึงปรับขนาดตามตัวเลขการใช้งานจริงของคุณ ให้เฝ้าระวังพื้นที่ดิสก์มากกว่าหน่วยความจำ เนื่องจากไฟล์แนบคือส่วนที่จะเพิ่มขนาดขึ้นเรื่อยๆ

ฉันจะอัปเกรด Planka โดยไม่ให้ข้อมูลสูญหายได้อย่างไร?

ให้สำรองข้อมูลฐานข้อมูลและเก็บถาวร volume ของไฟล์ที่อัปโหลดทันทีก่อนทำการอัปเกรด ไม่ใช่ใช้ตารางเวลาของเมื่อคืนนี้ ให้ใช้คำสั่ง docker compose exec -T postgres pg_dump -U planka -d planka > planka-db.sql โดยคงค่า -T ไว้เพื่อไม่ให้ pseudo-terminal ทำให้ผลลัพธ์ที่ redirect ออกมาเสียหาย ให้อ่านบันทึกการเปลี่ยนแปลง (release notes) สำหรับทุกเวอร์ชันที่คุณข้ามไป เปลี่ยน image tag ให้เป็นเวอร์ชันที่ระบุแทนการใช้ latest จากนั้นรันคำสั่ง docker compose pull และ docker compose up -d พร้อมกับเฝ้าดู log เพื่อตรวจสอบการ migration ให้คงค่า tag ของ Postgres ไว้ที่เวอร์ชันหลักเดิม เพราะเซิร์ฟเวอร์จะปฏิเสธการเปิดไดเรกทอรีข้อมูลที่ถูกเขียนโดยเวอร์ชันหลักที่ต่างกัน