SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-28

วิธีตั้งค่า Traefik v3 รันหลายแอปบน Docker Compose

เรียนรู้วิธีรัน 5 แอปผ่าน IP เดียวด้วย Traefik v3 บน Docker Compose ตั้งแต่การกำหนด Host rule การทำ TLS อัตโนมัติ ไปจนถึงการแก้ไขปัญหา acme.json ที่ทำให้คอนเทนเนอร์สตาร์ทไม่ขึ้น

หนึ่ง IP, ห้าแอป, หนึ่งพอร์ต 443

VPS ของคุณมี public IPv4 address เพียงหนึ่งหมายเลขและ TCP port 443 เพียงหนึ่งพอร์ต คุณต้องการรัน Gitea, staging copy ของแอป, internal dashboard, status page และ webhook receiver บนเซิร์ฟเวอร์เครื่องเดียว โดยใช้ 5 โดเมนเนม Reverse proxy คือกระบวนการที่ทำหน้าที่ถือครองพอร์ต :80 และ :443 คอยอ่าน header Host ในทุกคำขอที่เข้ามา แล้วส่งต่อไปยัง container ที่ถูกต้อง Traefik สามารถทำหน้าที่นี้ได้ รวมถึงการขอและต่ออายุ certificate ให้กับแต่ละโดเมนเนมโดยอัตโนมัติโดยที่คุณไม่ต้องรัน certbot ด้วยตนเอง Nginx และ Caddy ก็สามารถจัดการ 5 โดเมนเนมนี้ได้ดีเช่นกัน ดังนั้นหากคุณยังไม่ได้ตัดสินใจเลือก ควรเปรียบเทียบ proxy ทั้งสามตัวในด้านการจัดการ certificate และภาระในการตั้งค่าต่อแอป ก่อนที่จะเชื่อมต่อทุกอย่างเข้ากับตัวใดตัวหนึ่ง

สิ่งที่ทำให้ Traefik แตกต่างจาก block server {} ของ nginx คือแหล่งที่มาของการตั้งค่า สำหรับ nginx คุณต้องแก้ไขไฟล์แล้ว reload ใหม่ และวงจรชีวิตของ certificate ยังคงเป็นงานแยกต่างหาก ซึ่งเป็นขั้นตอนเดียวกับที่คุณทำเมื่อ ออก certificate ของ Let's Encrypt ด้วย certbot บน nginx โดยที่ตัวตั้งเวลาต่ออายุจะอยู่นอกเหนือการทำงานของ web server โดยสิ้นเชิง แต่ Docker provider ของ Traefik จะคอยเฝ้าดู Docker event stream และอ่าน labels จาก container ของคุณ: เพียงแค่เริ่ม container ที่มี label กฎ Host() ระบบก็จะสามารถเข้าถึงได้ภายในหนึ่งวินาที และเมื่อหยุด container เส้นทางนั้นก็จะหายไป นี่คือกับดักเช่นกัน เพราะการตั้งค่าที่อยู่ใน labels จะกระจายตัวอยู่ใน 5 จุดพร้อมกัน และหากใส่ label ผิด ระบบจะไม่มีการแจ้งเตือนใดๆ container จะเพียงแค่ไม่ถูก route และ Traefik จะไม่แสดงข้อความผิดพลาดใดออกมา

คำนามหลักสี่ประการ

  • Entrypoints คือซ็อกเก็ตที่คอยรับการเชื่อมต่อ คุณจะต้องกำหนดค่าสองรายการ ได้แก่ web บนพอร์ต :80 และ websecure บนพอร์ต :443
  • Routers ทำหน้าที่จับคู่คำขอ (Host(...)) และเชื่อมโยงเข้ากับ service โดยจะมีการร้องขอใบรับรองแยกตาม router ผ่านทาง tls.certresolver
  • Services คือส่วน backend ซึ่งประกอบด้วยคอนเทนเนอร์และพอร์ตที่คอนเทนเนอร์นั้นรับการเชื่อมต่ออยู่ ภายใน เครือข่าย Docker
  • Middlewares คือส่วนที่อยู่ระหว่าง router และ service ทำหน้าที่จัดการงานต่างๆ เช่น basic auth, การกำหนด IP allow-lists, การเขียน header ใหม่ และการทำ redirects

คำนามทั้งสี่นี้เป็นชื่อที่ Traefik ใช้เรียกงานที่คุณต้องเขียนด้วยตนเองในกรณีอื่น: router เปรียบเสมือน server_name, service คือเป้าหมายของ proxy_pass และ middlewares คือคำสั่งจัดการ header และการยืนยันตัวตนที่คุณต้องตั้งค่าเองเมื่อคุณ สร้าง server block ของ nginx reverse proxy ทีละบรรทัด

การตั้งค่าแบบคงที่ (static configuration เช่น entrypoints, providers, ACME) จะถูกส่งผ่านทางบรรทัดคำสั่งของ Traefik หรือในไฟล์ traefik.yml ซึ่งการเปลี่ยนแปลงค่าเหล่านี้จำเป็นต้องรีสตาร์ท Traefik ส่วนการตั้งค่าแบบไดนามิก (dynamic configuration เช่น routers, services, middlewares) จะได้รับมาจาก label ของคอนเทนเนอร์และสามารถโหลดใหม่ได้ทันทีโดยไม่ต้องรีสตาร์ท การสับสนระหว่างการตั้งค่าทั้งสองประเภทนี้เป็นสาเหตุทั่วไปที่ทำให้เกิดปัญหา "ตั้งค่า flag ไปแล้วแต่ไม่มีผล"

ไฟล์ compose

เครือข่าย Docker ร่วมที่ชื่อ proxy คือโครงสร้างหลัก Traefik จะเข้าถึงคอนเทนเนอร์ได้ก็ต่อเมื่อทั้งสองอยู่บนเครือข่ายเดียวกันเท่านั้น

name: edge

networks:
  proxy:
    name: proxy

services:
  traefik:
    image: traefik:v3.5
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=proxy
      - --entryPoints.web.address=:80
      - --entryPoints.websecure.address=:443
      - --entryPoints.web.http.redirections.entryPoint.to=websecure
      - --entryPoints.web.http.redirections.entryPoint.scheme=https
      - --certificatesresolvers.le.acme.email=you@example.com
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
      # while you iterate, point at staging so a mistake costs nothing:
      # - --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --api.dashboard=true
      - --log.level=INFO
      - --accesslog=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
      - traefik.http.routers.dashboard.entrypoints=websecure
      - traefik.http.routers.dashboard.tls.certresolver=le
      - traefik.http.routers.dashboard.service=api@internal
      - traefik.http.routers.dashboard.middlewares=dashboard-auth
      - traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$REPLACE$$THIS

  gitea:
    image: gitea/gitea:1  # major-only pin keeps this demo copy-pasteable; pin an exact release in production
    restart: unless-stopped
    volumes:
      - ./gitea:/data
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.gitea.rule=Host(`git.example.com`)
      - traefik.http.routers.gitea.entrypoints=websecure
      - traefik.http.routers.gitea.tls.certresolver=le
      - traefik.http.services.gitea.loadbalancer.server.port=3000

docker compose up -d จากนั้นจึงเป็น docker compose logs -f traefik แอปพลิเคชันเพิ่มเติมแต่ละตัวคือการคัดลอกบล็อก gitea โดยกำหนดชื่อ router ของตนเอง, Host() ของตนเอง และพอร์ตภายในของตนเอง การติดตั้ง Nextcloud บน Docker พร้อม TLS และการสำรองข้อมูล สามารถทำในลักษณะเดียวกัน โดยให้ลบการเผยแพร่พอร์ต (published ports) ออก, เชื่อมต่อเข้ากับ proxy และปล่อยให้ label ของ router จัดการชื่อโฮสต์และใบรับรอง

มีรายละเอียด 5 ประการที่สำคัญดังนี้

exposedByDefault=false ทำให้คอนเทนเนอร์ไม่ปรากฏแก่ Traefik จนกว่าจะมีการระบุ traefik.enable=true หากละเว้นส่วนนี้ คอนเทนเนอร์ทุกตัวที่คุณเริ่มทำงาน รวมถึงคอนเทนเนอร์ชั่วคราวอย่าง postgres ที่คุณรันเพื่อตรวจสอบบางอย่าง จะถูกสร้างเส้นทาง (route) ให้โดยอัตโนมัติ

providers.docker.network=proxy บอก Traefik ว่าควรใช้เครือข่ายใดเมื่อคอนเทนเนอร์เชื่อมต่อกับเครือข่ายหลายแห่ง หากละเว้นส่วนนี้ Traefik อาจเลือก IP ของคอนเทนเนอร์ผิด ซึ่งจะแสดงผลเป็นข้อผิดพลาด 502 ที่ดูเหมือนเป็นความผิดพลาดของแอปพลิเคชัน

loadbalancer.server.port=3000 คือพอร์ตที่อยู่ ภายใน คอนเทนเนอร์ เช่น Gitea ที่ฟังคำสั่งอยู่ที่พอร์ต 3000 สังเกตว่าไม่มีคอนเทนเนอร์แอปพลิเคชันใดเผยแพร่พอร์ตออกมาเลย มีเพียง Traefik เท่านั้นที่ทำ

การเปลี่ยนเส้นทาง (redirect) บน entrypoint web จะเปลี่ยนคำขอที่เป็นข้อความธรรมดาให้เป็น 308 ไปยัง HTTPS พอร์ต 80 จะยังคงเปิดอยู่เนื่องจาก ACME HTTP challenge จำเป็นต้องใช้พอร์ตนี้ รวมถึงผู้ใช้งานที่พิมพ์ชื่อโฮสต์โดยตรงด้วย

การใช้ $$ สองตัวในค่า hash ของ basic-auth คือการ escape ใน Compose ไม่ใช่การพิมพ์ผิด ให้สร้างค่าดังกล่าวด้วย htpasswd -nbB admin 'your-password' (จากแพ็กเกจ apache2-utils) แล้วจึงเพิ่ม $ เป็นสองเท่าในทุกตำแหน่ง

ใบรับรองและกับดักของ acme.json

tlschallenge=true เลือกใช้ TLS-ALPN-01: Let's Encrypt จะเชื่อมต่อเข้ามายังเซิร์ฟเวอร์ของคุณผ่านพอร์ต 443 และ Traefik จะตอบรับการตรวจสอบ (challenge) ภายใน TLS handshake ทางเลือกอื่นคือ HTTP-01 บนพอร์ต 80 ให้สลับบรรทัด tlschallenge ในรายการ command: ของ Traefik เป็นสองบรรทัดนี้:

      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

ใช้ได้ทั้งสองวิธี ทั้งสองวิธีต้องการให้ DNS สาธารณะสำหรับชื่อโฮสต์ชี้มาที่ VPS ของคุณแล้ว เพื่อให้ผู้ออกใบรับรองสามารถตรวจสอบชื่อและเชื่อมต่อจากภายนอกได้ ให้สร้างระเบียน A (และ AAAA) ก่อน ตรวจสอบด้วย dig +short git.example.com แล้วจึงเริ่มการทำงานของ Traefik

ตอนนี้คือกับดักที่ทำให้หลายคนเสียเวลาไปทั้งคืน Traefik จะเก็บคีย์บัญชี ACME และใบรับรองทั้งหมดไว้ในไฟล์เดียวคือ acme.json หากไฟล์นี้อนุญาตให้กลุ่มหรือบุคคลทั่วไปอ่านได้ Traefik จะแสดงข้อความที่ใกล้เคียงกับบรรทัดนี้แล้วหยุดทำงาน:

error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600

วิธีแก้ไขที่สะอาดที่สุดคือวิธีข้างต้น: ให้ bind-mount ไดเรกทอรี แล้วปล่อยให้ Traefik สร้างไฟล์ขึ้นมาเองด้วยสิทธิ์ที่ถูกต้อง หากคุณสร้าง acme.json ด้วย touch ค่า umask ของคุณจะทำให้ไฟล์เป็น 644 ให้แก้ไขบนโฮสต์ด้วยคำสั่ง:

chmod 600 ./letsencrypt/acme.json
docker compose restart traefik

สำรองข้อมูลไดเรกทอรีนั้นพร้อมกับ volume ของแอปพลิเคชันของคุณ การสูญเสียไฟล์นี้ยังพอแก้ไขได้โดยการออกใบรับรองใหม่ แต่การออกใบรับรองใหม่พร้อมกันห้าชื่อโฮสต์จะทำให้คุณติดข้อจำกัดด้านอัตรา (rate limits)

ให้ใช้ staging CA ในระหว่างการทดสอบ ให้ยกเลิกการคอมเมนต์บรรทัด caserver เพื่อให้เส้นทางทั้งหมดทำงานได้ จากนั้นค่อยคอมเมนต์กลับและลบ acme.json เพื่อให้มีการขอใบรับรองจริงใหม่ทั้งหมด Let's Encrypt สำหรับการใช้งานจริงอนุญาตให้ขอใบรับรองที่ซ้ำกันได้ 5 ฉบับต่อสัปดาห์สำหรับชุดชื่อโฮสต์เดียวกัน และจะจำกัดการตรวจสอบที่ล้มเหลวซ้ำๆ สำหรับชื่อเดิม ส่วน staging จะออกใบรับรองที่ไม่น่าเชื่อถือ ซึ่งเบราว์เซอร์ของคุณจะแจ้งเตือน และคำเตือนนั้นคือสัญญาณว่าการตั้งค่าสำเร็จ โดยมีข้อจำกัดที่ผ่อนปรนกว่ามาก

แดชบอร์ดคือส่วนควบคุม ไม่ใช่ตัวอย่างสาธิต

คู่มือเริ่มต้นใช้งานส่วนใหญ่ตั้งค่า --api.insecure=true ซึ่งเปิดใช้งานแดชบอร์ดบนพอร์ต 8080 โดยไม่มีการยืนยันตัวตน หากรันบนเครื่องที่มี Public IP ข้อมูลโครงสร้างการทำ Routing, ชื่อ Hostname, ชื่อ Middleware และพอร์ตของ Backend จะถูกเปิดเผยแก่ทุกคนที่สแกนเข้ามา

Label บนบริการ traefik ด้านบนคือทางเลือกที่ปลอดภัยกว่า โดยการทำ Routing แดชบอร์ดเหมือนกับแอปพลิเคชันอื่น ผ่านชื่อ Hostname จริง บนโปรโตคอล TLS และอยู่หลัง basicauth ส่วน service=api@internal คือตัวเชื่อมต่อ Router เข้ากับ API ภายในของ Traefik คุณสามารถเพิ่มความปลอดภัยได้อีกขั้นด้วยการต่อพ่วง IP allow-list โดยประมวลผลจากซ้ายไปขวา หาก IP ของออฟฟิศคุณเป็นแบบ Dynamic ให้กำหนดช่วง IP เป็น Subnet ที่ได้รับจาก WireGuard VPN ที่คุณโฮสต์เองบน VPS เดียวกัน แล้วเข้าถึงแดชบอร์ดผ่านอุโมงค์ VPN เท่านั้น:

- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-auth

การใช้รหัสผ่าน basicauth ร่วมกันเพียงชุดเดียวจะเริ่มไม่ปลอดภัยเมื่อคุณมีแอปพลิเคชันที่ต้องการบัญชีแยกกันถึง 5 รายการ ซึ่งช่อง Middleware เดียวกันนี้สามารถใช้ forwardauth เพื่อส่งต่อการตัดสินใจยืนยันตัวตนไปยัง Authentik ซึ่งเป็นเซิร์ฟเวอร์ Single Sign-On ที่คุณโฮสต์เอง ส่งผลให้แดชบอร์ดและทุกเส้นทางที่เกี่ยวข้องอยู่หลังการล็อกอินเพียงจุดเดียวที่คุณสามารถเพิกถอนสิทธิ์ได้จากที่เดียว

Docker socket มีสิทธิ์เป็น root

/var/run/docker.sock เป็น API ที่สามารถสร้างคอนเทนเนอร์ซึ่งเมาท์ / จากโฮสต์ได้ การเข้าถึงส่วนนี้เทียบเท่ากับการมีสิทธิ์ root บนเครื่อง และ Traefik จำเป็นต้องใช้เพื่ออ่านค่า labels

ให้คง :ro ไว้บนจุดเมาท์ แต่ต้องเข้าใจให้ชัดเจนว่าสิ่งนี้ช่วยอะไรได้บ้าง: มันทำให้ ไฟล์ socket เป็นแบบอ่านได้อย่างเดียวเท่านั้น แต่มันไม่ได้หยุดคำขอ POST ที่ส่งไปยัง Docker API ผ่านช่องทางนี้ วิธีป้องกันที่แท้จริงคือการไม่ให้ Traefik เข้าถึง socket โดยตรง แต่ให้ใช้พร็อกซีสำหรับกรองคำขอคั่นกลางไว้:

  dockerproxy:
    image: tecnativa/docker-socket-proxy   # pin the current tag
    restart: unless-stopped
    environment:
      CONTAINERS: 1
      NETWORKS: 1
      POST: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy

ให้ลบ volume ของ socket ออกจาก Traefik และชี้ provider ไปยังพร็อกซีแทน:

--providers.docker.endpoint=tcp://dockerproxy:2375

Traefik จะยังคงมีสิทธิ์อ่านข้อมูลคอนเทนเนอร์และเครือข่ายได้ แต่จะสูญเสียความสามารถในการสร้างสิ่งต่างๆ ไป

ไฟร์วอลล์ พอร์ต และกฎที่ทุกคนมักเข้าใจผิด

เปิดพอร์ตไว้สองพอร์ต พร้อมกับ SSH:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

พอร์ตที่เผยแพร่โดย Docker จะข้ามการทำงานของ ufw Docker จะแทรกกฎ iptables ของตนเองเข้าไป ซึ่งจะถูกประมวลผลก่อน chain ของ ufw ดังนั้นคอนเทนเนอร์ที่เริ่มทำงานด้วย ports: ["3000:3000"] จะสามารถเข้าถึงได้จากอินเทอร์เน็ตแม้ว่าจะมีการตั้งค่า ufw deny ไว้ก็ตาม วิธีป้องกันคือการจัดการเชิงโครงสร้าง ไม่ใช่การตั้งค่าไฟร์วอลล์: ให้เผยแพร่พอร์ตจาก Traefik เท่านั้น และกำหนดให้คอนเทนเนอร์อื่นทั้งหมดใช้ networks: [proxy] โดยไม่ต้องกำหนดค่าอื่นเพิ่มเติม หากมีบริการใดที่จำเป็นต้องเข้าถึงโฮสต์จริงๆ ให้ bind บริการนั้นไว้กับ loopback ด้วย "127.0.0.1:3000:3000"

การแก้ไขปัญหา: ข้อผิดพลาดที่คุณจะพบจริง

404 page not found ซึ่งแสดงโดย Traefik หมายความว่าไม่มี router ใดตรงกับคำขอ โดยเรียงลำดับความเป็นไปได้ดังนี้: container ขาด traefik.enable=true (โดยที่ตั้งค่า exposedByDefault=false ไว้แล้ว); กฎ Host() ไม่ตรงกับชื่อที่คุณพิมพ์; ชื่อ router ใน label หนึ่งไม่ตรงกับอีก label หนึ่ง (routers.gitea.rule และ routers.gitea.entrypoints ต้องเป็นคำเดียวกัน); หรือคุณใส่เครื่องหมายคำพูดครอบ hostname แทนที่จะใช้ backtick โดย Traefik v3 จำเป็นต้องใช้ backtick ภายในตัวกำหนดเงื่อนไข (matcher)

502 Bad Gateway หมายความว่า router ตรงกับคำขอแล้ว แต่ไม่สามารถเข้าถึง backend ได้ ส่วนใหญ่มักเกิดจาก container ไม่ได้อยู่ในเครือข่าย proxy ให้ตรวจสอบที่ docker inspect -f '{{json .NetworkSettings.Networks}}' gitea อีกสาเหตุที่เป็นไปได้คือการระบุ loadbalancer.server.port ผิดพลาด เช่น คุณระบุพอร์ตที่เผยแพร่ออกไป (published port) หรือแอปพลิเคชันฟังการเชื่อมต่อที่พอร์ตอื่น โดย log จะระบุความพยายามในการเชื่อมต่อไว้ที่: dial tcp 172.18.0.5:8080: connect: connection refused

เบราว์เซอร์แจ้งเตือน และใบรับรองถูกออกให้ TRAEFIK DEFAULT CERT หมายความว่าไม่มีใบรับรองสำหรับ hostname นั้น และ Traefik ได้แสดงใบรับรอง self-signed ที่เป็นค่าเริ่มต้นออกมา ให้ตรวจสอบบรรทัด ACME ดังนี้:

unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.com

DNS ยังไม่ได้ชี้มาที่เซิร์ฟเวอร์นี้ ให้แก้ไข record, รอจนหมดเวลา TTL และรีสตาร์ท Traefik

Invalid response from http://git.example.com/.well-known/acme-challenge/... ในขั้นตอน HTTP challenge หมายความว่าพอร์ต 80 ไม่สามารถเข้าถึง Traefik จากภายนอกได้ ซึ่งมักเกิดจาก firewall ระดับผู้ให้บริการที่อยู่หน้า VPS ไม่ใช่ ufw

ใบรับรองไม่ออก และ DNS ของคุณอยู่บน Cloudflare โดยเปิดใช้งาน orange cloud เนื่องจาก Cloudflare ทำ TLS termination ที่ edge ของตนเอง ทำให้ TLS-ALPN-01 ไม่สามารถทำงานผ่านได้ ให้ตั้งค่า record เป็น DNS-only ในระหว่างการออกใบรับรอง หรือเปลี่ยนไปใช้การท้าทายแบบ DNS-01 ด้วย API token ทั้งนี้ DNS-01 เป็นวิธีเดียวที่สามารถออกใบรับรองแบบ wildcard ได้

Redirect loop เกิดจากมีบางอย่างที่อยู่หน้า Traefik ทำ TLS termination ไปก่อนแล้ว และส่งต่อข้อมูลแบบ plaintext มาที่พอร์ต :80 จากนั้น entrypoint redirect จึงส่งคำขอกลับไปที่ HTTPS อีกครั้ง ให้ลบการตั้งค่า redirect ออกหนึ่งรายการ

การดูแลให้ระบบทำงานอย่างต่อเนื่อง

Docker unit ต้องถูกตั้งค่าให้เริ่มทำงานตอนบูต (systemctl is-enabled docker) และ restart: unless-stopped จะช่วยให้ stack กลับมาทำงานได้อีกครั้งหลังรีบูต หากต้องการการควบคุมที่ชัดเจน การใช้ systemd unit ขนาดเล็กที่รัน docker compose -f /srv/edge/compose.yml up -d พร้อมกับ RemainAfterExit=yes จะช่วยให้คุณได้รับ systemctl status edge และการควบคุมลำดับการทำงาน

ให้ระบุเวอร์ชันของ Traefik แบบเจาะจง (traefik:v3.5 ห้ามใช้ latest) การอัปเกรดจาก v2 ไป v3 มีการเปลี่ยนแปลงไวยากรณ์ของกฎและชื่อ provider ซึ่งการรัน latest โดยอัตโนมัติอาจโหลด config ที่ระบบไม่เข้าใจอีกต่อไปโดยไม่ตั้งใจ การอัปเกรดควรทำอย่างรอบคอบ: อ่านบันทึกการย้ายระบบ ปรับเวอร์ชัน tag, docker compose up -d traefik และคอยตรวจสอบ log หากคุณยังใช้ tag v2 อยู่ คู่มือการย้ายระบบ Traefik จาก v2 ไป v3 จะอธิบายขั้นตอนการเปลี่ยนชื่อทั้งหมด โหมดความเข้ากันได้ และวิธีการย้อนกลับที่ยังคงรักษา certificate ของคุณไว้

สำรองข้อมูล ./letsencrypt และ volume ข้อมูลของแต่ละแอปพลิเคชัน Traefik ไม่ได้เก็บสถานะอื่นใดที่คุณไม่สามารถสร้างขึ้นใหม่จากไฟล์ compose ได้

สิ่งที่มักจะติดขัดเมื่อขยายระบบ

ขีดจำกัดแรกไม่ใช่ปริมาณการรับส่งข้อมูล แต่เป็นตัวเซิร์ฟเวอร์เอง การใช้ Traefik หนึ่งตัวบน VPS หนึ่งเครื่องถือเป็นจุดเดียวที่อาจทำให้ระบบล่ม (single point of failure) สำหรับแอปพลิเคชัน 5 ตัว และเนื่องจาก acme.json เป็นการจัดเก็บข้อมูลแบบไฟล์ หากมี Traefik สองอินสแตนซ์เขียนข้อมูลพร้อมกันจะทำให้ไฟล์เสียหายได้ การขยายระบบ (scaling out) หมายถึงการย้ายที่จัดเก็บใบรับรอง (certificate) ออกจากไฟล์ หรือการทำ TLS termination ที่จุดอื่นแทน

ประเด็นที่สองคือการเชื่อมต่อที่คงอยู่เป็นเวลานาน (long-lived connections) เช่น Server-sent events, การอัปโหลดไฟล์ขนาดใหญ่ และไคลเอนต์ที่ตอบสนองช้า มักจะติดปัญหาเรื่อง timeout ของจุดรับคำขอ (entrypoint) โดยมี --entryPoints.websecure.transport.respondingTimeouts.readTimeout รวมถึง writeTimeout และ idleTimeout เป็นตัวปรับตั้งค่า ส่วน WebSockets นั้นสามารถใช้งานได้ทันทีโดยไม่ต้องตั้งค่าเพิ่มเติม

ประเด็นที่สามคือพื้นที่จัดเก็บข้อมูล --accesslog=true จะเขียน log ลง stdout และ Docker driver แบบ json-file จะเก็บข้อมูลนั้นไว้ตลอดไปหากไม่มีการจำกัดขนาด ควรตั้งค่า logging.options.max-size ในบริการ Traefik หรือเขียน access log ลงไฟล์แล้วทำการหมุนเวียนไฟล์ (rotate) แทน

ทั้งหมดนี้ไม่จำเป็นต้องใช้ระบบ orchestrator แต่จำเป็นต้องมีเซิร์ฟเวอร์ที่คุณควบคุมได้ มี IP จริง และเปิดพอร์ต 80 กับ 443 สู่สาธารณะ โดยมี VPS ขนาดเล็กเพียงเครื่องเดียวก็เพียงพอต่อความต้องการทั้งหมดแล้ว

FAQ

ฉันยังจำเป็นต้องใช้ certbot หากฉันรัน Traefik หรือไม่?

ไม่จำเป็น ACME resolver ของ Traefik จะร้องขอและต่ออายุใบรับรองสำหรับทุก hostname ที่กำหนดเส้นทางไว้ และจัดเก็บทั้งหมดไว้ใน acme.json ส่วน certbot ยังคงเป็นเครื่องมือที่เหมาะสมเมื่อ nginx หรือเซิร์ฟเวอร์อื่นทำหน้าที่เป็น TLS termination ด้วยตนเอง การรันทั้งสองอย่างพร้อมกันสำหรับ hostname เดียวกันจะทำให้โควตา rate limits ของ Let's Encrypt หมดลงโดยเปล่าประโยชน์

ทำไม container ของฉันถึงส่งค่า 404 ผ่าน Traefik?

ค่า 404 ที่ส่งมาจาก Traefik หมายความว่าไม่มี router ใดตรงกับคำขอนั้นเลย ให้ตรวจสอบว่า container มีการระบุ traefik.enable=true (จำเป็นต้องมีเมื่อตั้งค่า exposedByDefault=false แล้ว) ตรวจสอบว่าค่า Host() ตรงกับชื่อที่คุณระบุ และชื่อของ router ต้องเหมือนกันในทุก label ของแอปนั้น นอกจากนี้ Traefik v3 ต้องการให้ใช้ backticks ภายในตัวจับคู่ (matcher) แทนการใช้เครื่องหมายคำพูด

ความแตกต่างระหว่าง 404 และ 502 ในที่นี้คืออะไร?

ค่า 404 หมายความว่าการกำหนดเส้นทางไม่เกิดขึ้นเลย ส่วน 502 หมายความว่า router จับคู่ได้แล้วแต่ backend ปฏิเสธการเชื่อมต่อ สาเหตุที่พบบ่อยของ 502 คือ container ไม่ได้เชื่อมต่อกับเครือข่าย proxy และการตั้งค่า loadbalancer.server.port ที่ชี้ไปยังพอร์ตที่เผยแพร่ออกมา แทนที่จะเป็นพอร์ตที่แอปใช้งานจริงภายใน container โดย access log จะระบุที่อยู่ที่ Traefik พยายามเชื่อมต่อไว้อย่างชัดเจน

การ mount Docker socket แบบอ่านอย่างเดียว (read-only) เพียงพอหรือไม่?

flag :ro ทำให้ไฟล์ socket เป็นแบบอ่านอย่างเดียวเท่านั้น แต่ไม่ได้จำกัด API ที่อยู่เบื้องหลัง คำขอ POST ยังคงส่งผ่านช่องทางนี้ได้ และการเข้าถึง Docker API นั้นเทียบเท่ากับการมีสิทธิ์ root บนโฮสต์ วิธีการที่ปลอดภัยกว่าคือการใช้ container docker-socket-proxy ตามที่แสดงไว้ข้างต้น ซึ่งจะเปิดเผยเฉพาะข้อมูลการอ่าน container และเครือข่ายให้กับ Traefik และบล็อกการเขียนทั้งหมดโดยสิ้นเชิง

Traefik สามารถออกใบรับรองแบบ wildcard ได้หรือไม่?

ทำได้ผ่านการตรวจสอบแบบ DNS-01 เท่านั้น โดยต้องใช้ API token สำหรับผู้ให้บริการ DNS ของคุณ ส่วน TLS-ALPN-01 และ HTTP-01 จะตรวจสอบได้ทีละ hostname และไม่สามารถออกใบรับรองแบบ wildcard ได้ นอกจากนี้ DNS-01 ยังเป็นคำตอบในกรณีที่ CDN เช่น Cloudflare ทำหน้าที่เป็น TLS termination อยู่หน้า VPS ของคุณ ซึ่งทำให้การตรวจสอบอีกสองวิธีไม่สามารถทำได้สำเร็จ