ปัญหาที่นักพัฒนา PHP ทุกคนเจอ
ทุกครั้งที่เริ่มโปรเจกต์ CodeIgniter 4 ใหม่ ก่อนที่จะเขียน business logic สักบรรทัดเดียว เราต้องเสียเวลาหลายชั่วโมงกับการ setup: ทำให้ Docker ทำงานได้, ตั้งค่า Nginx, เชื่อม PHP-FPM, ติดตั้ง MySQL กับ Redis, ทำให้ PHPUnit รันได้ใน container และสุดท้ายก็ต้องนั่งหาว่าทำไม CI pipeline ถึง fail ตั้งแต่ push ครั้งแรก
เวลาที่ใช้ setup คือ "เวลาสูญเปล่า" ไม่ได้ช่วย ship feature และก็ต้องทำซ้ำในทุกโปรเจกต์
Stack ที่ใช้ในโปรเจกต์นี้
stack ทั้งหมดถูกออกแบบมาให้ครอบคลุมตั้งแต่ development ไปจนถึง production โดยไม่ต้องเปลี่ยน config อะไรมาก
| Component | Version | หน้าที่ |
|---|---|---|
| PHP-FPM | 8.2 | FastCGI runtime สำหรับรัน PHP |
| CodeIgniter | 4.7 | MVC framework + Shield authentication |
| Nginx | 1.28-alpine | Reverse proxy และ serve static files |
| MySQL | 8.4 | Primary database |
| Redis | 7-alpine | Cache, sessions และ queue backend |
| Supervisor | system | PID-1 process manager ใน production |
| PHPStan | level 6 | Static analysis ตรวจ code ก่อน deploy |
| PHPUnit | 10.5 | Test suite พร้อม pcov driver |
โครงสร้างไฟล์และ Docker Compose
โปรเจกต์แบ่งเป็นสองโหมดชัดเจน: Dockerfile.dev สำหรับ development (มี pcov + git support) และ Dockerfile สำหรับ production (ใช้ Supervisor เป็น PID-1)
รัน Stack ทั้งหมดด้วยคำสั่งเดียว
ทุกอย่างถูก wrap ไว้ใน Makefile คำสั่ง make setup จะ build images, start containers, รอ MySQL + Redis พร้อม, รัน migrations, seed database และ hit healthcheck endpoint อัตโนมัติ
ถ้า stack ทำงานถูกต้อง จะได้ response JSON ดังนี้:
3 การตัดสินใจสำคัญที่ไม่ค่อยมีคนพูดถึง
1. ใช้ Supervisor เป็น PID-1 แทน php-fpm โดยตรง
ถ้ารัน php-fpm เป็น PID-1 ตรงๆ จะเกิดปัญหา zombie processes สะสมใน container ที่รันนานๆ Supervisor จัดการ signal forwarding ได้ถูกต้อง, reap child processes และ restart service ที่ crash อัตโนมัติ
2. SSH Key ใน Docker Container — ทำให้ถูกต้องและปลอดภัย
ถ้า app ต้องใช้ git clone จาก private repo หรือ deploy ผ่าน SSH ภายใน container การจัดการ SSH key ต้องทำอย่างระมัดระวัง ห้าม hardcode key ใน Dockerfile เด็ดขาด ให้ใช้ Docker build argument แทน
COPY ~/.ssh/id_rsa /root/.ssh/id_rsa ตรงๆ ใน Dockerfile เพราะ key จะถูก bake เข้าไปใน image layer และถ้าใครดึง image ไปก็จะได้ private key ของคุณไปด้วย3. สองสภาพแวดล้อม ไฟล์เดียว .env
ใช้ .env แยก config ระหว่าง dev และ production อย่าเขียน credential ลงใน docker-compose.yml โดยตรง ใช้ variable substitution แทน
GitHub Actions CI/CD Pipeline
ทุก push และ pull request จะ trigger workflow อัตโนมัติ รัน PHPUnit, PHPStan และ Rector เพื่อให้มั่นใจว่า code ที่จะ merge เข้า main ผ่าน quality gate ทุกข้อ
- Push code → GitHub Actions เริ่มทำงาน
make setupbuild Docker stack ทั้งหมด- PHPStan วิเคราะห์ type safety ระดับ 6
- PHPUnit รัน test suite พร้อม code coverage
- Rector ตรวจ code style ตาม PHP 8.2 standards
- ผ่านทุก step → merge ได้, fail → block merge ทันที
Docker Best Practices ที่ต้องรู้
ใช้ Alpine image เสมอ
เลือก php:8.2-fpm-alpine และ nginx:alpine แทน full Debian image ขนาดเล็กกว่า 5–10 เท่า attack surface น้อยกว่า และ pull เร็วกว่ามาก
ห้ามใส่ secret ใน image layer
ใช้ Docker build argument หรือ Docker secrets สำหรับ sensitive data ทุกชนิด ไม่ว่าจะเป็น API keys, SSH keys หรือ database passwords ถ้า key เข้าไปใน layer แล้วจะดึงออกยากมาก
ใช้ Healthcheck ทุก service
Docker รู้ว่า container "start" แต่ไม่รู้ว่า service พร้อมรับ request แล้ว Healthcheck บอก Docker Compose ให้รอ service จริงๆ พร้อมก่อนจึงเริ่ม service ถัดไป
แยก Dockerfile ระหว่าง dev และ production
Dockerfile.dev มี pcov, xdebug, git, tools ต่างๆ ส่วน Dockerfile production ต้องเบาที่สุด ไม่ต้องการ dev dependency ใดๆ ทั้งนั้น
สรุป: ทำไมต้อง Setup แบบนี้?
การ setup CI4 + Docker stack แบบนี้ให้ประโยชน์ชัดเจนสามด้าน:
- ความเร็ว — ไม่ต้องเสียเวลา setup ซ้ำทุกโปรเจกต์
make setupคำสั่งเดียวพร้อมพัฒนา - ความสม่ำเสมอ — ทุก developer ใน team รันบน environment เดียวกันทุกครั้ง ไม่มีปัญหา "works on my machine"
- คุณภาพ — CI/CD pipeline บังคับให้ code ผ่าน test, static analysis และ code quality check ก่อน merge ทุกครั้ง
สำหรับมือใหม่ที่เริ่มต้นกับ CodeIgniter 4 การมี stack แบบนี้ตั้งแต่ต้นจะช่วยให้โฟกัสกับการเขียน business logic ได้เลย แทนที่จะมานั่ง debug environment ปัญหา