Skip to content
CodeSook
CodeSook

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

  1. Uninstall all conflicting packages.
Terminal window
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
  1. Set up Docker’s apt repository.
Terminal window
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo 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/null
sudo apt-get update
  1. Install the Docker packages.
Terminal window
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

หลังจากที่เราติดตั้ง Docker เสร็จแล้ว ลองทดสอบรันคำสั่ง

Terminal window
docker ps

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

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

Terminal window
sudo docker ps

แต่การที่ต้องใส่ sudo ทุกครั้งค่อนข้างที่จะไม่สะดวกใช่ไหมครับ ดังนั้นเราจึงใช้คำสั่งนี้

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

เมื่อเรากลับมาลองรัน

Terminal window
docker ps

คราวนี้จะสามารถใช้ได้เลยโดยไม่ต้องใส่ sudo แล้ว


Create Docker Network

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

Terminal window
docker network create caddy

Set up Nginx

เราจะสร้าง Nginx container ขึ้นมาก่อนเพื่อให้มี service ที่คอยรับ request จริงๆอยู่เบื้องหลัง โดยเราจะสร้างไฟล์ compose.nginx.yaml ด้วยคำสั่งนี้

Terminal window
nano compose.nginx.yaml

จากนั้นให้เราใส่ configuration ข้างในไฟล์ดังนี้

services:
nginx:
image: nginx
container_name: nginx
networks:
- caddy
networks:
caddy:
external: true

เมื่อก็อปวางเสร็จแล้ว ให้กดปุ่ม

  • Ctrl + X → เพื่อออกจาก nano
  • กด Y → เพื่อบันทึกไฟล์
  • แล้วกด Enter → เพื่อบันทึกชื่อไฟล์ตามที่เราใส่ไว้ (compose.nginx.yaml) สังเกตว่าในไฟล์นี้เราได้เพิ่ม networks: - caddy เพื่อเชื่อมต่อ Nginx container ของเราเข้ากับ network caddy ที่เราสร้างไว้ก่อนหน้านี้ ซึ่งจะทำให้ Nginx สามารถสื่อสารกับ Caddy และ Watchtower ได้ในภายหลัง

เราสามารถลองเช็คไฟล์ที่เราสร้างขี้นมาได้ด้วยคำสั่ง cat และตามด้วยชื่อไฟล์

Terminal window
cat compose.nginx.yaml

จากนั้นเรามา run nginx container ที่เราพึ่งสร้างขึ้นมาได้เลย ด้วยคำสั่งนี้

Terminal window
docker compose -f compose.nginx.yaml up -d

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

Terminal window
docker ps

Set up Caddy

ต่อมาเรามา set up caddy กัน เพื่อทำหน้าที่เป็น reverse proxy คอยรับ traffic จาก domain ของเรา (DuckDNS) และส่งไปยัง Nginx

เริ่มจากสร้างไฟล์ Caddyfile

Terminal window
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

Terminal window
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:
- caddy
networks:
caddy:
external: true
volumes:
caddy_data:
caddy_config:
  • ./Caddyfile:/etc/caddy/Caddyfile → ตรงนี้คือ ส่วนที่สำคัญ มันบอก Docker ว่าให้เอาไฟล์ Caddyfile ที่เราเขียนไว้ในโฟลเดอร์ปัจจุบัน (ด้านซ้าย ./Caddyfile) ไปแมปเข้าไปใน container ที่ path /etc/caddy/Caddyfile ซึ่งเป็นตำแหน่งที่ Caddy ใช้อ่าน config โดยอัตโนมัติ

เมื่อเสร็จแล้วกด Ctrl + X, Y และ Enter เพื่อ save และออก

จากนั้นรัน Caddy ด้วยคำสั่ง

Terminal window
docker compose -f compose.caddy.yaml up -d

และตรวจสอบว่า container ทำงานแล้วหรือยัง

Terminal window
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

Terminal window
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 ด้วยคำสั่ง

Terminal window
docker compose -f compose.wt.yaml up -d

และตรวจสอบว่า container ของ Watchtower ทำงานอยู่หรือยัง

Terminal window
docker ps

จะเห็นได้ว่าตอนนี้เรามี 3 container ที่กำลังทำงานอยู่แล้วคือ Nginx, Caddy และ Watchtower

และถ้าอยากให้ watchtower เข้าไป watch ต้องเพิ่ม labels เข้าไปใน compose file ด้วยนะครับ

compose.yaml
services:
nginx:
image: nginx:latest
container_name: nginx
networks:
- caddy
labels:
# ปักธงไว้ให้ Watchtower รู้จัก
- "com.centurylinklabs.watchtower.enable=true"
networks:
caddy:
external: true

นี่คือตัวอย่างไฟล์ compose.wt.yaml ที่ตั้งค่าที่ทีมใช้งานบ่อยๆ ครับ:

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