คำตอบสั้น ๆ: Deploy เว็บไซต์ขึ้น Linux Server ด้วย Git ทั้งแบบดึงจาก GitHub ด้วย Deploy Key และแบบ git push เข้า Bare Repository พร้อมวิธีย้อนกลับเมื่อมีปัญหา
คู่มือนี้อธิบาย Deploy ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย
การอัปโหลดไฟล์เว็บผ่าน FTP หรือ SFTP ทีละไฟล์ทำให้เกิดปัญหาบ่อย เช่น ลืมอัปโหลดบางไฟล์ ไม่รู้ว่าบนเซิร์ฟเวอร์เป็นเวอร์ชันไหน และย้อนกลับไม่ได้เมื่อมีปัญหา การใช้ Git ทำให้เซิร์ฟเวอร์มีโค้ดตรงกับ Commit ที่ระบุได้แน่นอน อัปเดตได้ด้วยคำสั่งเดียว และย้อนกลับไปเวอร์ชันก่อนหน้าได้ภายในไม่กี่วินาที
บทความนี้อธิบายสองวิธีที่ใช้บ่อยบน Linux Server ที่ไม่มี Control Panel คือ วิธี A ดึงโค้ดจาก GitHub หรือ GitLab ด้วย Deploy Key และ วิธี B สร้าง Bare Repository บนเซิร์ฟเวอร์ แล้ว git push จากเครื่องของคุณโดยตรงพร้อม Hook ที่ Deploy ให้อัตโนมัติ หากคุณใช้ Plesk ให้ใช้ฟีเจอร์ Git ในตัวตาม Deploy เว็บไซต์จาก Git Repository บน Plesk
สิ่งที่ต้องเตรียม
- เซิร์ฟเวอร์ Ubuntu 22.04/24.04 หรือ AlmaLinux/Rocky Linux 8-9 ที่ตั้งเว็บเซิร์ฟเวอร์ไว้แล้ว (เช่น Nginx Server Block หรือ Apache)
- ผู้ใช้สำหรับ Deploy โดยเฉพาะ ในตัวอย่างชื่อ
deployสร้างได้ตาม สร้างผู้ใช้ใหม่และให้สิทธิ์ sudo บน Linux (ผู้ใช้นี้ไม่จำเป็นต้องมีสิทธิ์ sudo) - โปรเจกต์เว็บที่อยู่ใน Git แล้ว และไม่มีไฟล์ความลับ เช่น
.envหรือรหัสผ่านฐานข้อมูลอยู่ใน Repository
ติดตั้ง Git บนเซิร์ฟเวอร์:
# Ubuntu
sudo apt update && sudo apt install -y git
# AlmaLinux / Rocky Linux
sudo dnf install -y git
เตรียมโฟลเดอร์เว็บและสิทธิ์
ให้ผู้ใช้ deploy เป็นเจ้าของไฟล์ และให้กลุ่มของเว็บเซิร์ฟเวอร์อ่านได้ (Ubuntu ใช้กลุ่ม www-data ส่วน AlmaLinux/Rocky ใช้ nginx หรือ apache ตามเว็บเซิร์ฟเวอร์):
sudo mkdir -p /var/www/example.com
sudo chown deploy:www-data /var/www/example.com
sudo chmod 2750 /var/www/example.com
เลข 2 หน้าสุดคือ setgid ทำให้ไฟล์ที่สร้างใหม่ในโฟลเดอร์นี้อยู่ในกลุ่ม www-data อัตโนมัติ โฟลเดอร์ที่เว็บต้องเขียนได้ เช่น uploads ให้กำหนดสิทธิ์แยกเฉพาะโฟลเดอร์นั้น (ดู ทำความเข้าใจสิทธิ์ไฟล์บน Linux ด้วย chmod และ chown)
คำสั่งต่อจากนี้ให้รันในฐานะผู้ใช้ deploy:
sudo -iu deploy
วิธี A: ดึงโค้ดจาก GitHub หรือ GitLab ด้วย Deploy Key
ขั้นตอนที่ A1: สร้าง Deploy Key
ssh-keygen -t ed25519 -f ~/.ssh/deploy_example -N "" -C "[email protected]"
cat ~/.ssh/deploy_example.pub
คัดลอก Public Key ที่แสดงไปเพิ่มใน Repository บน GitHub ที่ Settings > Deploy keys > Add deploy key โดยไม่ต้องติ๊ก Allow write access เพื่อให้กุญแจนี้อ่านได้อย่างเดียว บน GitLab อยู่ที่ Settings > Repository > Deploy keys Deploy Key ผูกกับ Repository เดียว ถ้าเซิร์ฟเวอร์ถูกเจาะ ผู้โจมตีจะเข้าถึงได้เฉพาะ Repository นั้นแบบอ่านอย่างเดียว ปลอดภัยกว่าการใช้ SSH Key ส่วนตัวของคุณ
ขั้นตอนที่ A2: ให้ SSH ใช้กุญแจนี้กับ GitHub
cat >> ~/.ssh/config <<'EOF'
Host github.com
IdentityFile ~/.ssh/deploy_example
IdentitiesOnly yes
EOF
chmod 600 ~/.ssh/config
ssh -T [email protected]
ครั้งแรกจะถามยืนยัน Host Key ให้ตรวจ Fingerprint กับที่ GitHub เผยแพร่ไว้ในเอกสารทางการก่อนตอบ yes ผลที่ถูกต้องคือข้อความ Hi ชื่อ/repo! You've successfully authenticated, but GitHub does not provide shell access. รายละเอียดเรื่องไฟล์ ~/.ssh/config ดูได้ที่ ใช้ไฟล์ ~/.ssh/config ให้ SSH เข้าเซิร์ฟเวอร์ได้สะดวกขึ้น
ขั้นตอนที่ A3: Clone และ Deploy ครั้งแรก
git clone [email protected]:your-org/example-site.git /var/www/example.com/app
แนะนำให้ตั้ง Document Root ของเว็บเซิร์ฟเวอร์ไปที่โฟลเดอร์ย่อยที่เป็นหน้าเว็บจริง เช่น /var/www/example.com/app/public ไม่ใช่รากของ Repository เพื่อไม่ให้โฟลเดอร์ .git และไฟล์อื่นเข้าถึงได้จากเว็บ
ขั้นตอนที่ A4: อัปเดตครั้งต่อไป
สร้างสคริปต์ Deploy เพื่อให้ทุกครั้งทำเหมือนกัน:
cat > ~/deploy-example.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
cd /var/www/example.com/app
git fetch origin
git merge --ff-only origin/main
git log -1 --oneline
EOF
chmod +x ~/deploy-example.sh
~/deploy-example.sh
--ff-only ทำให้การอัปเดตล้มเหลวอย่างปลอดภัยถ้ามีคนแก้ไฟล์บนเซิร์ฟเวอร์โดยตรง แทนที่จะสร้าง Merge Commit แปลก ๆ บนเครื่อง Production บรรทัดสุดท้ายแสดง Commit ที่ Deploy อยู่ ถ้าโปรเจกต์ต้องติดตั้ง Dependency (เช่น composer install --no-dev หรือ npm ci) ให้เพิ่มคำสั่งเหล่านั้นต่อท้ายสคริปต์
วิธี B: Push ตรงเข้าเซิร์ฟเวอร์ด้วย Bare Repository และ Hook
เหมาะเมื่อไม่ต้องการพึ่ง GitHub หรือต้องการ Deploy ด้วยคำสั่ง git push จากเครื่องของคุณ
ขั้นตอนที่ B1: สร้าง Bare Repository
mkdir -p ~/repos
git init --bare ~/repos/example.git
Bare Repository เก็บเฉพาะประวัติของ Git ไม่มีไฟล์ทำงาน (Working Tree) ไฟล์เว็บจะถูก Checkout ไปที่โฟลเดอร์เว็บโดย Hook
ขั้นตอนที่ B2: สร้าง Hook post-receive
cat > ~/repos/example.git/hooks/post-receive <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TARGET=/var/www/example.com/app
GIT_DIR=/home/deploy/repos/example.git
BRANCH=main
while read -r oldrev newrev ref; do
if [ "$ref" = "refs/heads/$BRANCH" ]; then
mkdir -p "$TARGET"
git --work-tree="$TARGET" --git-dir="$GIT_DIR" checkout -f "$BRANCH"
echo "Deployed $BRANCH at $(git --git-dir="$GIT_DIR" rev-parse --short "$newrev")"
else
echo "Ignored $ref (only $BRANCH is deployed)"
fi
done
EOF
chmod +x ~/repos/example.git/hooks/post-receive
Hook นี้ทำงานทุกครั้งที่มีการ Push เข้ามา และ Checkout เฉพาะ Branch main ไปที่โฟลเดอร์เว็บ วิธีนี้ไม่มีโฟลเดอร์ .git อยู่ในโฟลเดอร์เว็บเลย แต่ไฟล์ที่ถูกลบออกจาก Repository จะยังค้างอยู่ในโฟลเดอร์เว็บ ถ้าต้องการลบด้วย ให้เพิ่มคำสั่ง git --work-tree="$TARGET" --git-dir="$GIT_DIR" clean -fd หลังบรรทัด checkout โดยต้องแน่ใจก่อนว่าโฟลเดอร์ uploads และไฟล์ .env ถูกระบุใน .gitignore แล้ว เพราะ clean -fd ไม่ลบไฟล์ที่อยู่ใน .gitignore แต่จะลบไฟล์อื่นที่ Git ไม่รู้จักทั้งหมด
ขั้นตอนที่ B3: Push จากเครื่องของคุณ
บนเครื่องคอมพิวเตอร์ของคุณ (ต้องใส่ SSH Key ของคุณใน /home/deploy/.ssh/authorized_keys บนเซิร์ฟเวอร์ก่อน ดู ตั้งค่า SSH Key และปิดการเข้าสู่ระบบด้วยรหัสผ่านบน Linux):
git remote add production [email protected]:repos/example.git
git push production main
ผลลัพธ์จะมีบรรทัดที่ขึ้นต้นด้วย remote: ซึ่งเป็นข้อความจาก Hook เช่น remote: Deployed main at 3f2a9c1 ถ้า SSH ใช้พอร์ตอื่น ให้เขียน URL แบบ ssh://[email protected]:2222/home/deploy/repos/example.git