ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

Home Deploy เว็บไซต์ด้วย Git บน Linux Server

Deploy เว็บไซต์ด้วย Git บน Linux Server

การอัปโหลดไฟล์เว็บผ่าน 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

ตรวจสอบผลลัพธ์

  • วิธี A: รัน git -C /var/www/example.com/app log -1 --oneline บนเซิร์ฟเวอร์ ต้องตรงกับ Commit ล่าสุดบน GitHub
  • วิธี B: รัน git --git-dir=/home/deploy/repos/example.git log -1 --oneline main ต้องตรงกับที่ Push ไป
  • ตรวจว่าโฟลเดอร์ .git เข้าจากเว็บไม่ได้: curl -I https://example.com/.git/config ต้องได้ 403 หรือ 404 ถ้าได้ 200 แปลว่าประวัติโค้ดทั้งหมดรั่วได้ ให้แก้ Document Root หรือเพิ่มกฎบล็อก เช่นใน Nginx ใช้ location ~ /\.git { deny all; }

ย้อนกลับเมื่อ Deploy แล้วมีปัญหา

วิธีที่ปลอดภัยที่สุดคือสร้าง Revert Commit บนเครื่องของคุณ แล้ว Deploy ใหม่ตามปกติ:

git revert HEAD
git push origin main      # วิธี A แล้วรันสคริปต์ Deploy บนเซิร์ฟเวอร์
git push production main  # วิธี B

กรณีฉุกเฉินกับวิธี A สามารถให้เซิร์ฟเวอร์กลับไปที่ Commit ก่อนหน้าทันทีด้วย git -C /var/www/example.com/app reset --hard HEAD~1 คำสั่งนี้จะทิ้งการแก้ไขใด ๆ ที่ทำบนเซิร์ฟเวอร์โดยตรง และการ Deploy ครั้งถัดไปด้วย --ff-only จะยังทำงานได้ตามปกติ ถ้ามีการเปลี่ยนโครงสร้างฐานข้อมูล (Migration) ร่วมด้วย การย้อนโค้ดอย่างเดียวอาจไม่พอ ควรสำรองฐานข้อมูลก่อน Deploy ทุกครั้ง (ดู สำรองและกู้คืนฐานข้อมูล MySQL/MariaDB ด้วย mysqldump)

ปัญหาที่พบบ่อย

fatal: detected dubious ownership in repository

Git ปฏิเสธเมื่อเจ้าของโฟลเดอร์ Repository ไม่ใช่ผู้ใช้ที่รันคำสั่ง มักเกิดเมื่อรัน Git ด้วย root หรือผู้ใช้อื่น ทางแก้ที่ถูกคือรันด้วยผู้ใช้ deploy ผู้เป็นเจ้าของ ถ้าจำเป็นต้องรันด้วยผู้ใช้อื่นจริง ให้เพิ่มข้อยกเว้นเฉพาะ Path นั้น: git config --global --add safe.directory /var/www/example.com/app

Permission denied (publickey) ตอน git fetch

ยังไม่ได้เพิ่ม Deploy Key ใน Repository, ~/.ssh/config ไม่ได้ชี้ไปที่กุญแจ หรือรันด้วยผู้ใช้คนละคนกับที่สร้างกุญแจ ตรวจด้วย ssh -vT [email protected] แล้วดูว่าได้ลองใช้ไฟล์กุญแจใด

Not possible to fast-forward, aborting

มีคนแก้ไฟล์หรือ Commit บนเซิร์ฟเวอร์โดยตรง ดูด้วย git status และ git log origin/main..HEAD เก็บการแก้ไขที่ต้องการกลับเข้า Repository จากเครื่องพัฒนา แล้วค่อยรีเซ็ตเซิร์ฟเวอร์ให้ตรงกับ origin/main หลังสำรองไฟล์แล้ว

Hook ไม่ทำงานหลัง Push

ไฟล์ post-receive ไม่มีสิทธิ์ Execute หรือบันทึกด้วย Line Ending แบบ Windows (CRLF) ตรวจด้วย ls -l และ file ~/repos/example.git/hooks/post-receive ถ้าพบ CRLF ให้แปลงด้วย sed -i 's/\r$//' ไฟล์

หากต้องการวางระบบ Deploy ที่ซับซ้อนขึ้น เช่น หลายเซิร์ฟเวอร์หรือ Zero-downtime ติดต่อทีมซัพพอร์ต THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security