#ติดตั้ง Caddy Server ด้วย Docker Compose พร้อม Reverse Proxy และ HTTPS อัตโนมัติ

Caddy เป็น Web Server และ Reverse Proxy ที่ได้รับความนิยมในงาน DevOps เพราะตั้งค่าได้ง่าย รองรับ Automatic HTTPS และสามารถจัดการ TLS Certificate ให้อัตโนมัติ ทำให้เหมาะสำหรับการ Deploy Web Application, REST API และบริการที่ทำงานอยู่ภายใน Docker

บทความนี้จะอธิบายตั้งแต่การสร้าง Docker Compose, การเขียน Caddyfile, การทำ Reverse Proxy, การเปิดใช้งาน HTTPS ไปจนถึงแนวทางสำหรับนำไปใช้ใน Production


#Caddy Server คืออะไร

Caddy คือ Open Source Web Server ที่สามารถใช้เป็นทางเลือกแทน Nginx หรือ Apache ได้ โดยมีความสามารถสำคัญ เช่น

  • Web Server
  • Reverse Proxy
  • Automatic HTTPS
  • TLS Certificate Management
  • HTTP/2 และ HTTP/3
  • Load Balancing
  • Gzip และ Zstandard Compression
  • Access Logging
  • Security Headers
  • Docker และ Docker Compose Integration

จุดเด่นสำคัญคือ เมื่อกำหนดชื่อโดเมนใน Caddyfile และตั้งค่า DNS ให้ชี้มายัง Server แล้ว Caddy สามารถขอ ติดตั้ง และต่ออายุ TLS Certificate ให้โดยอัตโนมัติ


#ภาพรวมสถาปัตยกรรม

ตัวอย่างในบทความนี้ใช้ Caddy เป็น Reverse Proxy อยู่ด้านหน้า Web Application

Internet
   |
   v
+-------------------+
|   Caddy Server    |
|   :80 / :443      |
+-------------------+
          |
          v
+-------------------+
| Web Application   |
|     web:80        |
+-------------------+

เมื่อผู้ใช้เข้าเว็บไซต์

https://example.com

Request จะเข้ามายัง Caddy ก่อน จากนั้น Caddy จะส่งต่อ Request ไปยัง Container ของ Application ภายใน Docker Network


#สิ่งที่ต้องเตรียม

ก่อนเริ่มต้นควรมี

  • Docker
  • Docker Compose Plugin
  • Linux Server หรือ Cloud VM
  • Public IP สำหรับใช้งาน Production
  • Domain Name หากต้องการ Automatic HTTPS
  • Firewall ที่เปิดพอร์ต 80 และ 443

ตรวจสอบ Docker:

docker --version

ตรวจสอบ Docker Compose:

docker compose version

#โครงสร้างโปรเจกต์

สร้างโครงสร้างไฟล์ดังนี้

caddy-server/
├── compose.yaml
└── caddy/
    └── Caddyfile

สร้างโฟลเดอร์:

mkdir -p caddy-server/caddy
cd caddy-server

#1. สร้าง Docker Compose

สร้างไฟล์ชื่อ

compose.yaml

แล้วเพิ่ม Configuration:

services:
  web:
    image: nginx:alpine
    container_name: demo-web
    restart: unless-stopped
    networks:
      - web-network

  caddy:
    image: caddy:2-alpine
    container_name: caddy-server
    restart: unless-stopped

    cap_add:
      - NET_ADMIN

    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"

    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

    depends_on:
      - web

    networks:
      - web-network

volumes:
  caddy_data:
  caddy_config:

networks:
  web-network:
    driver: bridge

#อธิบาย Service web

web:
  image: nginx:alpine

ตัวอย่างนี้ใช้ Nginx Container เป็น Web Application สำหรับทดสอบ

restart: unless-stopped

หมายถึง Container จะเริ่มใหม่โดยอัตโนมัติ หากระบบ Restart หรือ Container หยุดทำงานโดยไม่ตั้งใจ


#อธิบาย Service caddy

caddy:
  image: caddy:2-alpine

ใช้ Official Caddy Docker Image

พอร์ตที่เปิดใช้งาน:

ports:
  - "80:80"
  - "443:443"
  - "443:443/udp"
Port Protocol หน้าที่
80 TCP HTTP
443 TCP HTTPS
443 UDP HTTP/3

#Caddy Volume

volumes:
  - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
  - caddy_data:/data
  - caddy_config:/config

Volume สำคัญประกอบด้วย

#/etc/caddy/Caddyfile

ใช้เก็บ Configuration หลักของ Caddy

#/data

ใช้เก็บข้อมูล เช่น

  • TLS Certificate
  • Private Key
  • ACME Account
  • Certificate Metadata

ดังนั้น caddy_data ไม่ควรถูกลบโดยไม่จำเป็นใน Production

#/config

ใช้เก็บ Configuration ภายในของ Caddy


#2. สร้าง Caddyfile สำหรับ Local Development

สร้างไฟล์

caddy/Caddyfile

และเพิ่ม

:80 {
    reverse_proxy web:80
}

ความหมายคือ

Client
  |
  v
Caddy :80
  |
  v
web:80

ภายใน Docker Network สามารถเรียก Container อื่นผ่านชื่อ Service ได้

ดังนั้นให้ใช้

web:80

ไม่ควรใช้

localhost:80

เพราะ localhost ภายใน Caddy Container หมายถึงตัว Caddy Container เอง


#3. เริ่มต้นระบบ

ใช้คำสั่ง

docker compose up -d

ตรวจสอบ Container:

docker compose ps

ตัวอย่างผลลัพธ์:

NAME           IMAGE            STATUS
caddy-server   caddy:2-alpine   Up
demo-web       nginx:alpine     Up

เปิด Browser:

http://localhost

หาก Configuration ถูกต้อง จะเห็นหน้า Welcome ของ Nginx


#4. ดู Log

ดู Log ของ Caddy:

docker compose logs -f caddy

ดู Log ทุก Container:

docker compose logs -f

ดูเฉพาะ 100 บรรทัดล่าสุด:

docker compose logs --tail=100 caddy

#5. ใช้งาน Caddy กับ Domain

สำหรับ Production สามารถกำหนด Domain โดยตรงใน Caddyfile

ตัวอย่าง:

example.com {
    encode zstd gzip

    reverse_proxy web:80

    log
}

หรือใช้ Subdomain:

app.example.com {
    encode zstd gzip

    reverse_proxy web:80

    log
}

เมื่อ Caddy ตรวจพบชื่อ Domain ระบบจะพยายามเปิดใช้งาน HTTPS โดยอัตโนมัติ


#6. Automatic HTTPS ทำงานอย่างไร

เมื่อกำหนด

example.com {
    reverse_proxy web:80
}

Caddy จะจัดการงานสำคัญ เช่น

  1. ตรวจสอบ Domain
  2. ขอ TLS Certificate
  3. ติดตั้ง Certificate
  4. เปิด HTTPS
  5. Redirect HTTP ไป HTTPS
  6. ต่ออายุ Certificate อัตโนมัติ

ดังนั้นไม่จำเป็นต้องติดตั้ง Certbot สำหรับกรณีทั่วไป


#7. ตั้งค่า DNS

สมมติ Server มี IP

203.0.113.10

กำหนด A Record:

Type: A
Name: @
Value: 203.0.113.10

สำหรับ Subdomain:

Type: A
Name: app
Value: 203.0.113.10

หลังจากนั้นสามารถเข้า

https://app.example.com

ตรวจสอบ DNS:

nslookup app.example.com

หรือ

dig app.example.com

#8. เปิด Firewall

สำหรับ Ubuntu ที่ใช้ UFW:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw reload

ตรวจสอบ:

sudo ufw status

หากใช้ Cloud Provider เช่น

  • AWS
  • DigitalOcean
  • Azure
  • Google Cloud

ต้องเปิด Firewall หรือ Security Group ของ Cloud Platform ด้วย


#9. ตรวจสอบ Caddyfile

หลังแก้ไข Configuration ควรตรวจสอบ Syntax ก่อนใช้งาน

docker compose exec caddy   caddy validate   --config /etc/caddy/Caddyfile   --adapter caddyfile

หาก Configuration ถูกต้อง จะไม่มี Error ที่ทำให้ Caddy ไม่สามารถโหลด Configuration ได้


#10. Reload Caddy

หลังแก้ไข Caddyfile สามารถ Reload ได้โดยไม่ต้อง Restart Container

docker compose exec -w /etc/caddy caddy caddy reload

หรือ

docker compose exec caddy   caddy reload   --config /etc/caddy/Caddyfile   --adapter caddyfile

#11. Reverse Proxy หลาย Application

Caddy หนึ่งตัวสามารถให้บริการหลายเว็บไซต์หรือหลาย API ได้

ตัวอย่าง Architecture:

                    +--> frontend:80
                    |
Internet --> Caddy -+
                    |
                    +--> backend:8000

ตัวอย่าง compose.yaml:

services:

  frontend:
    image: nginx:alpine
    restart: unless-stopped
    networks:
      - web-network

  backend:
    image: your-backend-api:latest
    restart: unless-stopped
    expose:
      - "8000"
    networks:
      - web-network

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped

    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"

    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

    networks:
      - web-network

volumes:
  caddy_data:
  caddy_config:

networks:
  web-network:

#Caddyfile

example.com {
    encode zstd gzip
    reverse_proxy frontend:80
}

api.example.com {
    encode zstd gzip
    reverse_proxy backend:8000
}

ผลลัพธ์คือ

https://example.com
        |
        v
frontend:80

และ

https://api.example.com
        |
        v
backend:8000

#12. Reverse Proxy ตาม URL Path

Caddy สามารถ Route Request ตาม Path ได้

example.com {

    encode zstd gzip

    handle /api/* {
        reverse_proxy backend:8000
    }

    handle {
        reverse_proxy frontend:80
    }
}

เช่น

https://example.com

จะส่งไป

frontend:80

ส่วน

https://example.com/api/users

จะส่งไป

backend:8000

#13. ใช้ Caddy กับ Laravel

หาก Laravel ทำงานผ่าน Nginx Container:

example.com {
    reverse_proxy nginx:80
}

Architecture:

Internet
   |
 Caddy
   |
 Nginx
   |
 PHP-FPM
   |
 Laravel

กรณี Caddy ทำหน้าที่แทน Nginx และเชื่อมต่อ PHP-FPM โดยตรง สามารถใช้

example.com {

    root * /var/www/html/public

    encode zstd gzip

    php_fastcgi php:9000

    file_server
}

#14. ใช้ Caddy กับ Laravel Octane

หาก Laravel Octane ทำงานที่พอร์ต 8000

example.com {

    encode zstd gzip

    reverse_proxy laravel:8000
}

Docker Compose:

services:

  laravel:
    build: .
    expose:
      - "8000"

  caddy:
    image: caddy:2-alpine

    ports:
      - "80:80"
      - "443:443"

    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data

#15. Reverse Proxy ไปยัง Application บน Host

ในบางกรณี Application ไม่ได้อยู่ใน Docker แต่ทำงานบน Host เช่น

localhost:8080

บน Linux สามารถเพิ่ม

extra_hosts:
  - "host.docker.internal:host-gateway"

ตัวอย่าง:

caddy:
  image: caddy:2-alpine

  extra_hosts:
    - "host.docker.internal:host-gateway"

จากนั้นกำหนด

example.com {
    reverse_proxy host.docker.internal:8080
}

#16. เพิ่ม Security Headers

สามารถเพิ่ม HTTP Security Headers ได้

example.com {

    encode zstd gzip

    header {
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"

        Strict-Transport-Security         "max-age=31536000; includeSubDomains"

        -Server
    }

    reverse_proxy web:80
}

คำสั่ง

-Server

ใช้ลบ Server Header


#17. เปิด Access Log

ตัวอย่าง Console Log:

example.com {

    reverse_proxy web:80

    log {
        output stdout
        format console
    }
}

ดู Log:

docker compose logs -f caddy

สำหรับระบบ Logging เช่น Loki หรือ Elasticsearch สามารถใช้ JSON

example.com {

    reverse_proxy web:80

    log {
        output stdout
        format json
    }
}

#18. Health Check

สามารถเพิ่ม Health Check ให้ Caddy

caddy:
  image: caddy:2-alpine

  healthcheck:
    test:
      - CMD
      - caddy
      - version

    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 10s

ตรวจสอบ:

docker compose ps

#19. อัปเดต Caddy

ดึง Image ใหม่:

docker compose pull caddy

Deploy:

docker compose up -d

ตรวจสอบ Version:

docker compose exec caddy caddy version

ใน Production แนะนำให้ Pin Version ของ Image ให้ชัดเจน เช่น

image: caddy:2.11-alpine

แทนการพึ่ง Tag ที่อาจเปลี่ยนแปลงตามเวลา


#20. Backup Caddy Volume

ตรวจสอบ Volume:

docker volume ls

Backup:

docker run --rm   -v caddy-server_caddy_data:/data   -v "$(pwd)":/backup   alpine   tar czf /backup/caddy-data-backup.tar.gz -C /data .

จะได้ไฟล์:

caddy-data-backup.tar.gz

#คำสั่ง Docker Compose ที่ใช้บ่อย

#Start

docker compose up -d

#Status

docker compose ps

#Log

docker compose logs -f caddy

#Validate

docker compose exec caddy   caddy validate   --config /etc/caddy/Caddyfile   --adapter caddyfile

#Reload

docker compose exec -w /etc/caddy caddy caddy reload

#Restart

docker compose restart caddy

#Stop

docker compose down

#Update

docker compose pull
docker compose up -d

#Resource Usage

docker stats

#ข้อควรระวัง

#1. อย่าใช้ localhost ระหว่าง Container

ควรใช้

reverse_proxy web:80

แทน

reverse_proxy localhost:80

#2. อย่าลบ Caddy Volume โดยไม่จำเป็น

คำสั่ง

docker compose down -v

จะลบ Named Volume ด้วย ซึ่งอาจทำให้ Certificate และข้อมูล TLS ถูกลบ

สำหรับ Production โดยทั่วไปควรใช้

docker compose down

#3. ตรวจสอบ DNS

Domain ต้องชี้มายัง Server ที่ถูกต้อง

nslookup example.com

#4. ตรวจสอบ Port 80 และ 443

sudo ss -tulpn | grep -E ':80|:443'

ถ้ามี Nginx หรือ Apache บน Host ใช้พอร์ตนี้อยู่ Caddy จะไม่สามารถ Bind Port ได้

ตัวอย่างหยุด Nginx:

sudo systemctl stop nginx
sudo systemctl disable nginx

#Production Architecture ที่แนะนำ

                    Internet
                       |
                       v
                +--------------+
                |    Caddy     |
                | Reverse Proxy|
                | TLS / HTTPS  |
                +------+-------+
                       |
        +--------------+--------------+
        |                             |
        v                             v
+---------------+             +---------------+
|   Frontend    |             |     API       |
| React / Vue   |             | Laravel / Go  |
+---------------+             +---------------+
        |                             |
        +--------------+--------------+
                       |
                  Docker Network

Caddy เป็น Entry Point เพียงตัวเดียวจาก Internet ส่วน Application Container ไม่จำเป็นต้องเปิด Port ออกสู่ Public โดยตรง


#ตัวอย่าง Configuration สำหรับ Production

#compose.yaml

services:

  web:
    image: nginx:alpine
    restart: unless-stopped
    networks:
      - internal

  api:
    image: my-api:latest
    restart: unless-stopped
    expose:
      - "8000"
    networks:
      - internal

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped

    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"

    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

    networks:
      - internal

volumes:
  caddy_data:
  caddy_config:

networks:
  internal:
    driver: bridge

#Caddyfile

example.com {

    encode zstd gzip

    header {
        X-Content-Type-Options nosniff
        X-Frame-Options SAMEORIGIN
        Referrer-Policy strict-origin-when-cross-origin
        -Server
    }

    reverse_proxy web:80

    log {
        output stdout
        format json
    }
}

api.example.com {

    encode zstd gzip

    reverse_proxy api:8000

    log {
        output stdout
        format json
    }
}

#สรุป

Caddy Server เป็นทางเลือกที่เหมาะสำหรับการทำ Reverse Proxy ในระบบ Docker เนื่องจาก Configuration มีความกระชับและสามารถจัดการ HTTPS ได้อัตโนมัติ

องค์ประกอบสำคัญประกอบด้วย

  • Docker Compose สำหรับจัดการ Container
  • Caddy สำหรับเป็น Reverse Proxy
  • Caddyfile สำหรับกำหนด Routing
  • Docker Network สำหรับเชื่อมต่อ Service
  • Named Volume สำหรับเก็บ TLS Certificate
  • DNS สำหรับชี้ Domain ไปยัง Server
  • Firewall สำหรับเปิดพอร์ต HTTP และ HTTPS

Configuration ขั้นต่ำสำหรับ Local Development คือ

:80 {
    reverse_proxy web:80
}

ส่วน Production สามารถใช้

example.com {
    encode zstd gzip
    reverse_proxy web:80
}

เมื่อ DNS ชี้มายัง Server และเปิดพอร์ต 80 กับ 443 ถูกต้อง Caddy จะช่วยจัดการ HTTPS, TLS Certificate และ Redirect จาก HTTP ไป HTTPS ให้อัตโนมัติ

ด้วยเหตุนี้ Caddy + Docker Compose จึงเป็น Stack ที่เหมาะสำหรับการ Deploy Web Application, REST API, Laravel, Node.js, Go, React, Next.js และบริการแบบ Containerized บน VPS หรือ Cloud Server ได้อย่างมีประสิทธิภาพ