GitLabDockerCI/CD

คู่มือ: สร้าง head-project — หน้ารวม Nav Link Deploy ของห้องเรียน

เอกสารนี้เป็น tutorial แนะนำขั้นตอนการสร้าง head-project จัดทำเพื่อให้อาจารย์นำไปปรับใช้สอนต่อได้

1head-project คืออะไร

head-project คือโปรเจกต์กลางที่รวบรวม nav link ของเว็บที่เพื่อนแต่ละคนในห้อง deploy ไว้บน server เดียวกัน คนละ port เพื่อให้เข้าถึงผลงานของทุกคนได้จากหน้าเดียว ไม่ต้องจำ URL หรือ port ของแต่ละคนแยกกัน ผู้จัดทำเอกสารนี้รับผิดชอบส่วนของกลุ่มสายตรง (ทท) port 9901-9936

2สิ่งที่ต้องมีก่อนเริ่ม

  • บัญชี GitLab (สมัครฟรีที่ gitlab.com)
  • Docker และ Docker Compose ติดตั้งบนเครื่อง
  • Text editor เช่น VS Code
  • Git ติดตั้งบนเครื่อง
  • รายชื่อ port ของเพื่อนในห้องที่ deploy ไว้แล้ว สำหรับใส่ใน nav link

3สร้าง GitLab Project

1) เข้า gitlab.com แล้ว login

หน้า GitLab dashboard หลัง login
หน้า Home ของ GitLab หลัง login สำเร็จ

2) กด New project แล้วเลือก Create blank project

หน้า Create new project
เลือก "Create blank project"

3) ตั้งชื่อ project ว่า head-project
4) เลือก Visibility Level ตามที่อาจารย์กำหนด (Public หากต้องการให้ทุกคนเข้าถึงได้)

ฟอร์มสร้าง blank project ชื่อ head-project ตั้ง Visibility เป็น Public
ตั้งชื่อ project + เลือก Visibility = Public

5) กด Create project

Project head-project สร้างสำเร็จ repo ว่าง
Project สร้างเสร็จแล้ว — repo ว่าง พร้อม clone

ต่อมาให้เรา cd เข้า folder ที่เราจะสร้าง project เพื่อ clone มาก่อน (เราจะใช้ SSH ในการ access)

Terminal cd เข้าโฟลเดอร์ VSCode
info
ก่อนจะ clone คุณต้องทำการ gen sshkey ก่อน โดยใช้คำสั่ง
bash
ssh-keygen -t ed25519 -C "your_email@example.com"
ต้องแก้ example gmail เป็น gmail ที่เราได้สมัครกับ GitLab นะครับ มันต้องตรงกันถึงจะ access ได้

เราต้องการ id_ed25519.pub ซึ่งเป็น public key ที่ต้องนำไปเพิ่มไว้ที่ GitLab (Edit profile → SSH Keys → Add new key) ก่อน ถึงจะ clone/push ผ่าน SSH ได้โดยไม่ต้องกรอก password หรือ token ทุกครั้งที่ใช้งาน

เพิ่ม key เข้า GitLab
ไปที่ GitLab → คลิกรูปโปรไฟล์มุมขวาบน → Edit profile → SSH Keys → Add new key
วาง key นี้ทั้งบรรทัด แล้วกด Add key

คำสั่ง git clone แบบ SSH สำหรับ head-project
คัดลอกคำสั่ง SSH clone จากหน้า repo
หน้า SSH Keys settings วาง public key
วาง public key ที่หน้า User Settings → SSH Keys

ใช้คำสั่งนี้ตรวจสอบว่าคุณเชื่อมกับ GitLab หรือยัง

bash
ssh -T git@gitlab.com

ถ้าขึ้น Welcome to GitLab = ใช้ได้แล้ว

Terminal ทดสอบ ssh -T git@gitlab.com สำเร็จ
เชื่อม SSH สำเร็จ — ขึ้น "Welcome to GitLab"

ใช้ URL แบบ SSH ตอน clone — จากหน้าจอที่ส่งมา ใช้บรรทัดใต้ SSH tab (ไม่ใช่ HTTPS)

Terminal git clone SSH head-project
git clone ผ่าน SSH

แล้ว cd เข้าไปยัง head-project ของเรา จากนั้นเปิด IDE

Terminal cd head-project
Terminal เปิด VS Code ด้วยคำสั่ง code .
เปิดโปรเจกต์ด้วย code .

4วางโครงสร้างไฟล์

โครงสร้างไฟล์พื้นฐานของ head-project มีดังนี้

structure
head-project/
├── index.html          หน้ารวม nav link ของทุกคน
├── Dockerfile          สร้าง image สำหรับ serve หน้าเว็บ
├── docker-compose.yml  สั่งรัน container
├── .dockerignore       กัน .git และไฟล์เอกสารหลุดเข้า image
└── .gitlab-ci.yml      สำหรับ deploy อัตโนมัติ (ถ้าใช้ CI/CD)
VS Code เปิด head-project เห็นไฟล์ทั้งหมดใน explorer
หน้าตาโปรเจกต์เปิดใน VS Code — ไฟล์ครบตามโครงสร้าง

ดูไฟล์จริงทั้งหมดได้ที่ repo GitLab (public — เปิดดูได้เลยไม่ต้องขอสิทธิ์): gitlab.com/VacTuzX-dot/head-project

5เขียน Dockerfile

Dockerfile ใช้ nginx:alpine เป็น base image แล้ว copy ไฟล์ index.html เข้าไป serve เป็นหน้าเว็บ static

Dockerfile
FROM nginx:alpine
COPY . /usr/share/nginx/html
เนื้อหา Dockerfile ใน VS Code
Dockerfile จริงที่เขียนไว้ — ดูโค้ดจริงใน repo
info
อย่าลืมสร้าง .dockerignore คู่กัน ระบุ .git, README.md และไฟล์เอกสารอื่นที่ไม่จำเป็นต้องอยู่ใน image ป้องกันไฟล์หลุดขึ้น production

6ทดสอบ Local ก่อน deploy

รันคำสั่งต่อไปนี้บนเครื่องตัวเองก่อน push ขึ้น server จริง เพื่อเช็คว่า Dockerfile ถูกต้องตั้งแต่ต้นทาง

bash
docker build -t head-project .
docker run --name head-project -p 8080:80 head-project
info
ต้องใส่ --name head-project ด้วย — ไม่งั้น container จะได้ชื่อสุ่มจาก Docker (เช่น modest_newton) ซึ่งเป็นคนละชื่อกับ image ทำให้คำสั่ง docker stop head-project ด้านล่างหาไม่เจอ (image กับ container เป็นคนละ object กัน คนละชื่อกันได้ ถ้าไม่ระบุ --name ไว้)

เปิดเบราว์เซอร์ไปที่ http://localhost:8080 ต้องเห็นหน้ารวม nav link แสดงผลถูกต้องก่อนค่อย deploy จริง

docker build head-project สำเร็จ FINISHED 8/8
docker build ผ่าน — FINISHED 8/8
docker run head-project สำเร็จ ได้ HTTP 200 ที่ localhost:8080
docker run แล้วเข้า localhost:8080 ได้จริง (HTTP 200)
หน้าเว็บ head-project แสดงผลจริงที่ localhost:8080 เห็น nav link ครบ 36 คน
หน้าเว็บขึ้นจริงที่ localhost:8080 — nav link ครบ 36 คน
info
คำสั่ง docker run -p 8080:80 head-project ด้านบนไม่ได้ใส่ -d จึงรันแบบ foreground — terminal ค้างรอ log อยู่ตลอด กด Ctrl + C เพื่อยกเลิก/หยุด container ได้ทันที (เท่ากับ interrupt process ที่กำลังรันอยู่)
Terminal กด Ctrl+C หยุด docker run ที่รันแบบ foreground
กด Ctrl+C แล้ว container หยุดทำงาน (worker process exit ทั้งหมด)

เก็บกวาดหลัง Ctrl+C (ไม่ให้ container/image ค้างเปลืองเครื่อง) — เปิด Docker Desktop → แท็บ Containers ก่อน เพื่อดูว่า container ID / name จริงคืออะไร

Docker Desktop แท็บ Containers แสดง container ที่หยุดแล้ว พร้อม name และ container ID
เช็ค name / container ID จากแท็บ Containers

เอา name หรือ ID ที่เห็นมาลบ container ทาง terminal

bash
docker rm head-project       # ใช้ name หรือ container ID ที่เห็นใน Docker Desktop ก็ได้
info
ถ้า docker rm head-project ขึ้น No such container — แปลว่าตอน docker run ไม่ได้ใส่ --name head-project ไว้ container เลยชื่อสุ่ม (เช่น modest_newton) เอาชื่อที่เห็นในรูปด้านบนมาใช้แทนได้เลย เช่น docker rm modest_newton

ลบ container เสร็จแล้ว ไปที่แท็บ Images ต่อ — image head-project จะยังค้างอยู่ (ลบ container ไม่ได้ลบ image ให้อัตโนมัติ)

Docker Desktop แท็บ Images แสดง image head-project ค้างอยู่
image head-project ยังอยู่ในแท็บ Images แม้ลบ container แล้ว

ลบ image ทิ้งด้วยคำสั่ง (หรือกดปุ่มถังขยะตรงแถว head-project ในรูปก็ได้ ไม่ต้องพิมพ์เลย)

bash
docker rmi head-project
info
ลำดับสำคัญ: ลบ container ก่อน แล้วค่อยลบ image — ถ้า container (ตัวที่ยังไม่ลบ) ใช้ image นั้นอยู่ docker rmi จะลบไม่ได้ ขึ้น error ว่า image ถูกใช้งานอยู่
info
ถ้าใช้คำสั่งแล้วเจอ error (เช่น name ไม่ตรง, ลบไปแล้วรอบนึงแล้ว) ไม่ต้องแก้คำสั่งให้ยุ่งยาก — เปิด Docker Desktop (หรือ OrbStack) แล้วกดปุ่มถังขยะตรงแถว container/image นั้นได้เลย ผลลัพธ์เหมือนกัน ง่ายกว่าไล่หา name/id ทาง terminal

7Deploy ขึ้น server จริง

เลือกวิธี deploy ได้ 2 แบบ

  • แบบที่ 1 CI/CD อัตโนมัติ — เขียน .gitlab-ci.yml ให้ SSH เข้า server แล้วรัน docker compose up -d --build ทุกครั้งที่ push เข้า branch main
  • แบบที่ 2 Manual — SSH เข้า server เอง แล้ว git pull ตามด้วย docker compose up -d --build
warning
ก่อน deploy ต้องเช็คว่า port ที่จะใช้ว่างจริง และตั้ง container_name ไม่ให้ชนกับของเพื่อนคนอื่นบน server เดียวกัน

ถ้าใช้แบบที่ 1 (CI/CD อัตโนมัติ) ต้องตั้งค่า secret ให้ pipeline ก่อน push — ไปที่ Settings → CI/CD ในเมนูซ้าย

เมนู Settings เปิด ชี้ไปที่ CI/CD
Settings → CI/CD

เพิ่ม variable 3 ตัว: SERVER_IP, SERVER_USER, SERVER_PASSWORD (ค่าเดียวกับที่ตั้งใน GitHub Secrets ของ repo หลัก)

หน้า CI/CD Settings ตั้งค่า variable SERVER_IP, SERVER_USER, SERVER_PASSWORD
ตั้ง 3 ตัวแล้ว — ทุกตัวติ๊ก Protected
warning
จุดที่พลาดกันบ่อย (เจอจริงตอนทำ): ตั้ง 3 ตัวแล้วติ๊ก Visible (ค่า default ของ GitLab) ทั้งที่ SERVER_PASSWORD ต้องเป็น Masked เท่านั้น — ถ้าเป็น Visible password จะโผล่เป็น plain text ใน job log ให้ใครก็ดูได้ ต้องกลับไปแก้ก่อน push จริง: เปิด variable นั้น → เปลี่ยน Visibility เป็น Masked → Save

commit + push จริง — เมื่อ push เข้า branch main สำเร็จ pipeline (ถ้าใช้แบบที่ 1) จะเริ่มรันเองทันที

bash
git add .
git commit -m "feat: initial head-project setup (nav hub, Dockerfile, CI/CD)"
git push
Terminal git add, commit, push สำเร็จขึ้น GitLab
push สำเร็จ — GitLab สร้าง branch main รับโค้ดแล้ว

8ตรวจผลลัพธ์

  • docker ps เพื่อเช็คว่า container รันอยู่สถานะ Up
  • เปิด http://PUBLIC_IP:PORT ทดสอบว่าหน้าเว็บขึ้นจริง
  • กด nav link แต่ละอันในหน้า head-project เช็คว่าลิงก์ไปหา port ของเพื่อนแต่ละคนถูกต้อง ไม่ error
หน้าเว็บ head-project deploy สำเร็จ โชว์ online 1/36
deploy สำเร็จ ขึ้นจริงที่ http://49.229.108.152:9900 — สถานะ "online 1/36" คือ nav ถูกเขียนให้เช็ค port แต่ละคนสด ๆ ด้วย fetch() แล้วโชว์เฉพาะคนที่ container รันอยู่จริง (คนอื่นยังไม่ deploy ก็ถูกกรองออกอัตโนมัติ ไม่ต้องไปลบ/แก้ list มือ)

9ปัญหาที่มักเจอ และวิธีแก้

อาการวิธีแก้
permission denied ขณะเชื่อมต่อ docker.sock user บน server ไม่ได้อยู่ใน docker group แก้ด้วย sudo usermod -aG docker <user> แล้วต้อง SSH เข้าใหม่จริงๆ เพราะ group ไม่ refresh ใน session เดิม
network ที่ระบุเป็น external แต่หาไม่เจอ ต้องสร้าง network ก่อนด้วยคำสั่ง docker network create <ชื่อ>
เข้าเว็บผ่าน public IP ไม่ได้ docker-compose.yml ไม่ได้ตั้งค่า ports ไว้ ต้องเพิ่มบรรทัด ports เพื่อ map port ออกมาให้เข้าถึงจากภายนอกได้
container ชื่อซ้ำกับเพื่อน ตั้ง container_name ให้ไม่ซ้ำกัน เช่นใช้เลขนักศึกษาต่อท้ายชื่อ container

10ใช้ code ซ้ำสำหรับสาย/กลุ่มอื่น (เช่น ม.6)

repo head-project เป็น public บน GitLab — หัวกลุ่มสายอื่นเปิดดู/copy โค้ดได้เลยไม่ต้องขอสิทธิ์ ไม่ต้อง fork ก็ได้ (สร้าง repo ใหม่ของตัวเองแล้ว copy ไฟล์ 5 ไฟล์นี้ไปวางตรงๆ):

  • Dockerfile — เหมือนกันทุก track ไม่ต้องแก้
  • .dockerignore — เหมือนกันทุก track ไม่ต้องแก้
  • .gitlab-ci.yml — ไม่ต้องแก้ (deploy path derive จาก $CI_PROJECT_PATH_SLUG ของ repo ตัวเองอัตโนมัติอยู่แล้ว ดู ขั้นตอน 7)
  • docker-compose.yml — ต้องแก้ 2 จุด (ดูด้านล่าง)
  • index.html — ต้องแก้ 3 จุด (ดูด้านล่าง)

ตัวอย่างจริง: สายตรง (9900) → ม.6 (hub 9940, นักเรียน 9941-9965)

info
docker-compose.yml — แก้ ports กับ container_name:
docker-compose.yml
    container_name: head_project_m6_web   # เดิม head_project_direct_web — ต้องไม่ซ้ำกัน
    ports:
      - "9940:80"                         # เดิม "9900:80"
info
index.html — แก้ PORT_BASE, ข้อความ sub/footer, และแทนที่ทั้ง array STUDENTS ด้วยรายชื่อ 25 คนของสาย ม.6 (เริ่มนับที่คนที่ 41 ตามชีต port-assignments):
index.html
const PORT_BASE = 9940;   // เดิม 9900 — ทุกคนใน STUDENTS จะได้ port = PORT_BASE + ลำดับ
// sub: "...(port 9941-9965)"   footer: "...กลุ่ม ม.6"
const STUDENTS = [
  ["ชื่อ-สกุล คนที่ 1", "0XX"],   // → 9941
  ["ชื่อ-สกุล คนที่ 2", "0XX"],   // → 9942
  // ... ใส่ให้ครบ 25 คน (9941-9965)
];
warning
ห้ามลืมแก้ PORT_BASE — ถ้าลืม nav ทุกอันจะลิงก์ผิด port ไปชนกับของสายตรง ทั้งที่หน้าเว็บ deploy สำเร็จปกติ (ไม่มี error ให้เห็น เพราะเป็นแค่เลข port ผิด ไม่ใช่ code พัง)

ที่เหลือทำตามขั้นตอน 3-8 ปกติ: สร้าง GitLab project ใหม่ของตัวเอง (public), เขียนไฟล์ตามด้านบน, test local, ตั้ง CI/CD Variables 3 ตัว (SERVER_IP/SERVER_USER/SERVER_PASSWORD แบบ Masked — ค่าเดียวกับของสายตรงถ้าใช้ server เดียวกัน), แล้ว push

11ทิปสำหรับอาจารย์นำไปสอนต่อ

  • กำหนด port ให้แต่ละคนล่วงหน้าเป็นสูตรตายตัว เช่น 9900 บวกเลขนักศึกษา แทนการให้นักศึกษาหา port ว่างเอง ป้องกันการชนกันตอน deploy พร้อมกันทั้งห้อง
  • ใช้ git commit history เป็นหลักฐานลำดับขั้นตอนการทำงานของนักศึกษาแต่ละคนได้ ไม่ต้องให้เขียนรายงานแยกต่างหาก
  • จุดที่ควรเน้นตอนสอน คือการใช้ .dockerignore ป้องกันไฟล์หลุดขึ้น production และการแยก CI/CD job เป็น build/test/deploy เพื่อให้ pipeline fail fast เมื่อมีข้อผิดพลาด