02. Setup VPS and Docker
สร้าง VM บน Google Cloud
Project Creation
ขั้นตอนแรกเรามาสร้าง VM กันก่อน โดยที่เราจะสร้างมันขึ้นมาที่ Google Cloud

หากพึ่งเคยสมัครครั้งแรกเราจะได้รับ credit ฟรี $300 ด้วยนะ เมื่อเรา login หรือ signup สำเร็จแล้ว เราจะมาสร้างโปรเจกต์ใหม่กัน โดยกดไปที่ Select a project และ New project และให้เราเติม Project name เข้าไปโดยผมจะขอตั้งชื่อโปรเจกต์นี้ว่า demo และจึงกดปุ่ม Create เพื่อสร้างโปรเจกต์ หลังจากนั้นจึงกดที่ปุ่ม Select a project เพื่อเลือกโปรเจกต์ที่เราพึ่งสร้างไป

VM Creation
เมื่อเลือกโปรเจกต์แล้วให้เรากดปุ่มเมนูที่ฝั่งซ้ายด้านบน แล้วให้เราเลือกไปที่ Compute Engine และ VM instances

หากพึ่งเคยใช้ VM instances เป็นครั้งแรกเราต้องมา enable ก่อน ซึ่งมันจะให้เราผูกกับบัตรเครดิด เพราะฉะนั้นหากไม่ได้ใช้แล้วอย่าลืมมาลบเครื่อง VM กันด้วยนะ หาก enable เรียบร้อยแล้ว เราจะมาเจอกับหน้านี้ ให้เรากดไปที่ Create instance ทางด้านบน

Machine configuration
เราจะมาเริ่ม set up เครื่อง VM ของเรากัน โดยที่ผมจะเลือกแบบที่ราคาต่ำสุด เพราะนี่เป็นเพียงการ demo เท่านั้น อย่างแรกเลยเราจะตั้งชื่อเครื่องให้มัน ซึ่งผมจะตั้งชื่อเดียวกับชื่อโปรเจกต์ ซึ่งก็คือ demo เพื่อผมจะได้จำได้ง่าย ในส่วนของ region ผมจะเลือกเป็น asia-southeast1 (Singapore) ส่วน Zone ผมจะเลือกเป็น asia-southeast1-b และ Series ผมจะเลือกเป็น E2 เพราะราคามันถูกสุด

ต่อมาในด้านล่างในส่วนของ Machine type ผมจะเลือกเป็น Shared-core และ e2-medium

OS and Storage
ในแท็ปต่อมา OS and storage ให้เรากดไปที่ปุ่ม Change และเลือก Operating System เป็น Ubuntu, Version เป็น Ubuntu 24.04 LTS x86/64, amd64, Boot disk type เป็น Balanced persistent disk, Size เป็น 10 GB และจึงกด Select

Data protection
แท็ปถัดไป Data protection ผมจะเลือกเป็น No backups เผื่อประหยัดเครดิต และเราไม่จำเป็นต้องมี backups เพราะนี่เป็นเพียง demo project

Networking
ในแท็ป Networking ผมจะเพิ่ม Network tags เป็น demo ให้เหมือนชื่อโปรเจกต์

Observability
สุดท้าย ในแท็ป observability ผมจะ disable Ops Agent ด้วยเหตุผลเดิม เผื่อที่จะประหยัดงบประมาณ เพราะนี่เป็นเพียง demo project. อีกสองแท็ปข้างล่าง(Security และ Advanced) เราจะยังไม่ได้ไปยุ่งกับมันใน Tutorial นี้ ให้เรากดปุ่ม Create ด้านล่างเผื่อสร้าง VM เครื่องแรกของเราได้เลย!

Set up Firewall
หลังจากที่เราได้สร้างเครื่อง VM ของเราแล้ว เราจะต้องมา set up firewall rules กัน เราจะกดเข้าไปที่การ์ด Set up firewall rules ด้านล่าง (หากใครไม่เจอสามารถค้นหาได้ใน search bar) หลังจากนั้นให้เรากดไปที่ปุ่ม Create firewall rules ด้านบน

ผมจะตั้ง Target tags เหมือนเดิมคือ demo ให้เหมือนตอนตั้งชื่อ project และ network tags ส่วน SourceIPv4 ranges ผมจะตั้งเป็น 0.0.0.0/0 และผมจะ check TCP พร้อมเพิ่ม 2 port ก็คือ port 80 และ 443 หลังจากทำตามนี้ครบแล้วก็กดปุ่ม Create ด้านล่าง
Set up domain (DuckDNS)
เราจะมา set up domain ให้กับเครื่อง VM ที่เราพึ่งสร้างกัน ใน tutorial นี้ ผมจะใช้เป็น Duck DNS เพราะมันฟรีและใช้งานง่าย หลังจากเข้าไปในเว็บไซต์ของ Duck DNS เราต้อง login ก่อนจึงจะใช้งานได้

เมื่อ login แล้วให้เราคิดชื่อ domain มาอันนึง ผมจะใช้เป็น codesook และให้เรา กดปุ่ม add domain

หลังจากนั้นให้เรากลับมาที่ Google Cloud ในหน้า VM instances อีกครั้งเพื่อที่จะ copy External IP ไปใส่ใน domain บน Duck DNS

ให้เราเอา External IP ที่เรา copy มาใส่ใน current ip ของ domain ที่เราพึ่งสร้างไป และกดปุ่ม update ip

Set up Docker
ให้เรามาที่หน้า VM instances และกดไปที่่ drop down menu ของเครื่อง VM ที่เราพึ่งสร้างไป และกด Open in browser window

Set up Docker
หลังจากที่เราเปิด SSH ให้ run 3 คำสั่งนี้เพื่อติดตั้ง Docker
- Uninstall all conflicting packages.
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done

- Set up Docker’s
apt
repository.
# Add Docker's official GPG key:sudo apt-get updatesudo apt-get install ca-certificates curlsudo install -m 0755 -d /etc/apt/keyringssudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullsudo apt-get update

- Install the Docker packages.
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

หลังจากที่เราติดตั้ง Docker เสร็จแล้ว ลองทดสอบรันคำสั่ง
docker ps
คุณจะเห็นว่ามันจะขึ้น error ว่า permission denied ซึ่งหมายความว่าผู้ใช้ปัจจุบันของเราไม่มีสิทธิ์เข้าถึง Docker daemon โดยตรง

ดังนั้นหากต้องการให้รันได้เราต้องใส่ sudo
ข้างหน้าคำสั่งทุกครั้ง เช่น
sudo docker ps

แต่การที่ต้องใส่ sudo
ทุกครั้งค่อนข้างที่จะไม่สะดวกใช่ไหมครับ
ดังนั้นเราจึงใช้คำสั่งนี้
sudo usermod -aG docker $USERnewgrp docker
- บรรทัดแรก: ใช้สิทธิ์แอดมิน (sudo) เพื่อ เพิ่ม user ของเราเข้าไปในกลุ่ม docker
- บรรทัดที่สอง: อัปเดต shell ปัจจุบันให้รับรู้การเปลี่ยนแปลงนั้นทันที เพื่อให้เราพิมพ์คำสั่ง docker ได้เลยโดยไม่ต้องใส่ sudo และไม่ต้อง logout/login ใหม่
เมื่อเรากลับมาลองรัน
docker ps
คราวนี้จะสามารถใช้ได้เลยโดยไม่ต้องใส่ sudo
แล้ว

Create Docker Network
ก่อนที่เราจะ run service ต่าง ๆ (เช่น Caddy, Nginx, Watchtower) ผ่าน docker compose
เราต้องมี เครือข่ายกลาง (network) ที่ใช้สำหรับให้ container เหล่านี้สื่อสารกันได้
docker network create caddy

Set up Nginx
เราจะสร้าง Nginx container ขึ้นมาก่อนเพื่อให้มี service ที่คอยรับ request จริงๆอยู่เบื้องหลัง
โดยเราจะสร้างไฟล์ compose.nginx.yaml
ด้วยคำสั่งนี้
nano compose.nginx.yaml
จากนั้นให้เราใส่ configuration ข้างในไฟล์ดังนี้
services: nginx: image: nginx container_name: nginx networks: - caddynetworks: caddy: external: true
เมื่อก็อปวางเสร็จแล้ว ให้กดปุ่ม
Ctrl + X
→ เพื่อออกจาก nano- กด
Y
→ เพื่อบันทึกไฟล์ - แล้วกด
Enter
→ เพื่อบันทึกชื่อไฟล์ตามที่เราใส่ไว้ (compose.nginx.yaml
) สังเกตว่าในไฟล์นี้เราได้เพิ่มnetworks: - caddy
เพื่อเชื่อมต่อ Nginx container ของเราเข้ากับ networkcaddy
ที่เราสร้างไว้ก่อนหน้านี้ ซึ่งจะทำให้ Nginx สามารถสื่อสารกับ Caddy และ Watchtower ได้ในภายหลัง
เราสามารถลองเช็คไฟล์ที่เราสร้างขี้นมาได้ด้วยคำสั่ง cat
และตามด้วยชื่อไฟล์
cat compose.nginx.yaml

จากนั้นเรามา run nginx container ที่เราพึ่งสร้างขึ้นมาได้เลย ด้วยคำสั่งนี้
docker compose -f compose.nginx.yaml up -d

เราสามารถตรวจสอบว่า container ของ Nginx ทำงานอยู่หรือยัง ด้วยคำสั่งนี้
docker ps

Set up Caddy
ต่อมาเรามา set up caddy กัน เพื่อทำหน้าที่เป็น reverse proxy คอยรับ traffic จาก domain ของเรา (DuckDNS) และส่งไปยัง Nginx
เริ่มจากสร้างไฟล์ Caddyfile
nano Caddyfile
ใส่ configuration ข้างในดังนี้
codesook.duckdns.org { reverse_proxy nginx:80}
codesook.duckdns.org
→ คือ domain ที่เราได้มาจาก DuckDNS และเราผูกไว้กับ External IP ของ VM แล้ว ดังนั้นเมื่อมีคนเข้าเว็บผ่าน domain นี้ คำสั่งใน Caddyfile จะถูกเรียกใช้reverse_proxy nginx:80
→ ให้ Caddy ทำหน้าที่เป็น reverse proxy ส่งต่อ traffic ไปยัง service ที่ชื่อว่าnginx
(ซึ่งเราสร้างไว้ในcompose.nginx.yaml
) โดยใช้ port 80 ที่เป็น default HTTP port ของ container Nginx
พูดง่าย ๆ คือ เวลาเราพิมพ์ https://codesook.duckdns.org
→ Caddy จะรับ request แล้วส่งต่อไปที่ nginx:80
(container nginx ของเรา) นั่นเอง

เมื่อเสร็จแล้วกด Ctrl + X
, Y
และ Enter
เพื่อ save และออก
จากนั้นสร้างไฟล์ compose.caddy.yaml
nano compose.caddy.yaml
ใส่ configuration ข้างในดังนี้
version: "3"
services: caddy: container_name: caddy image: caddy:2 ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config # extra_hosts: # - "host.docker.internal:host-gateway" networks: - caddynetworks: caddy: external: truevolumes: caddy_data: caddy_config:
./Caddyfile:/etc/caddy/Caddyfile
→ ตรงนี้คือ ส่วนที่สำคัญ มันบอก Docker ว่าให้เอาไฟล์Caddyfile
ที่เราเขียนไว้ในโฟลเดอร์ปัจจุบัน (ด้านซ้าย./Caddyfile
) ไปแมปเข้าไปใน container ที่ path/etc/caddy/Caddyfile
ซึ่งเป็นตำแหน่งที่ Caddy ใช้อ่าน config โดยอัตโนมัติ
เมื่อเสร็จแล้วกด Ctrl + X
, Y
และ Enter
เพื่อ save และออก
จากนั้นรัน Caddy ด้วยคำสั่ง
docker compose -f compose.caddy.yaml up -d

และตรวจสอบว่า container ทำงานแล้วหรือยัง
docker ps
จะสังเกตุได้ว่าตอนนี้เราได้มี container สองอันที่กำลังรันอยู่แล้ว ก็คือ nginx และ caddy

หากตอนนี้เราเข้าไปที่ https://codesook.duckdns.org
(หรือ domain ที่เราสร้างไว้ใน DuckDNS ของตัวเอง) เราจะเห็นหน้า Default Page ของ Nginx
แต่เราจะมาติดตั้ง Watchtower กันก่อน เพื่อช่วยให้ container ของเราอัปเดตเป็นเวอร์ชันใหม่ล่าสุดแบบอัตโนมัติ

Set up Watchtower
Watchtower มีหน้าที่คอยช่วยให้เรา อัปเดต container อัตโนมัติ เวลามี image เวอร์ชั่นใหม่ออกมา ซึ่งช่วยลดภาระที่เราต้องมาคอย pull และ restart container ใหม่เองทุกครั้ง
เริ่มจากสร้างไฟล์ compose.wt.yaml
nano compose.wt.yaml

จากนั้นใส่ configuration ข้างในดังนี้
services: watchtower: image: containrrr/watchtower environment: - WATCHTOWER_LABEL_ENABLE=true # which service need to allow watchtower to watch new image that service should put the label - WATCHTOWER_POLL_INTERVAL=30 # check new image every 30 seconds - WATCHTOWER_ROLLING_RESTART=true # restart the one was changed image detected and got zero downtime deployment as well volumes: - /var/run/docker.sock:/var/run/docker.sock
เมื่อก็อปวางเสร็จแล้ว กด Ctrl + X
, Y
, Enter
เพื่อ save และออก
จากนั้นเราจะรัน Watchtower ด้วยคำสั่ง
docker compose -f compose.wt.yaml up -d
และตรวจสอบว่า container ของ Watchtower ทำงานอยู่หรือยัง
docker ps
จะเห็นได้ว่าตอนนี้เรามี 3 container ที่กำลังทำงานอยู่แล้วคือ Nginx, Caddy และ Watchtower

และถ้าอยากให้ watchtower เข้าไป watch ต้องเพิ่ม labels
เข้าไปใน compose file ด้วยนะครับ
services: nginx: image: nginx:latest container_name: nginx networks: - caddy labels: # ปักธงไว้ให้ Watchtower รู้จัก - "com.centurylinklabs.watchtower.enable=true"
networks: caddy: external: true
นี่คือตัวอย่างไฟล์ compose.wt.yaml
ที่ตั้งค่าที่ทีมใช้งานบ่อยๆ ครับ:
services: watchtower: image: containrrr/watchtower container_name: watchtower restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock environment: # --- การตั้งค่าพื้นฐาน --- - WATCHTOWER_LABEL_ENABLE=true - WATCHTOWER_POLL_INTERVAL=30 - WATCHTOWER_ROLLING_RESTART=true
# --- การตั้งค่าเพิ่มเติม --- - WATCHTOWER_CLEANUP=true - WATCHTOWER_NOTIFICATIONS=shoutrrr - WATCHTOWER_NOTIFICATIONS_LEVEL=info - WATCHTOWER_NOTIFICATION_URL=discord://token@channel