Production-Ready CodeIgniter 4 + Docker พร้อม CI/CD ใน 10 นาที

โดย CyberMAN



🐳 Docker + CI/CD + CodeIgniter 4

Production-Ready CodeIgniter 4 + Docker
พร้อม CI/CD ใน 10 นาที

ตั้งค่าสภาพแวดล้อม PHP ระดับ Production ด้วย Docker, Nginx, MySQL, Redis และ GitHub Actions ในขั้นตอนเดียว

🕐 อ่าน 8 นาที⚡ PHP-FPM 8.2🐳 Docker Compose🔁 GitHub Actions

ปัญหาที่นักพัฒนา PHP ทุกคนเจอ

ทุกครั้งที่เริ่มโปรเจกต์ CodeIgniter 4 ใหม่ ก่อนที่จะเขียน business logic สักบรรทัดเดียว เราต้องเสียเวลาหลายชั่วโมงกับการ setup: ทำให้ Docker ทำงานได้, ตั้งค่า Nginx, เชื่อม PHP-FPM, ติดตั้ง MySQL กับ Redis, ทำให้ PHPUnit รันได้ใน container และสุดท้ายก็ต้องนั่งหาว่าทำไม CI pipeline ถึง fail ตั้งแต่ push ครั้งแรก

เวลาที่ใช้ setup คือ "เวลาสูญเปล่า" ไม่ได้ช่วย ship feature และก็ต้องทำซ้ำในทุกโปรเจกต์

💡 บทความนี้จะพาคุณตั้งแต่ศูนย์ไปถึง environment ที่ tested และพร้อม production จริงๆ ด้วย CodeIgniter 4 + Docker stack ภายใน 10 นาที พร้อม GitHub Actions CI/CD ที่รัน PHPUnit, PHPStan และ Rector อัตโนมัติ

Stack ที่ใช้ในโปรเจกต์นี้

stack ทั้งหมดถูกออกแบบมาให้ครอบคลุมตั้งแต่ development ไปจนถึง production โดยไม่ต้องเปลี่ยน config อะไรมาก

ComponentVersionหน้าที่
PHP-FPM8.2FastCGI runtime สำหรับรัน PHP
CodeIgniter4.7MVC framework + Shield authentication
Nginx1.28-alpineReverse proxy และ serve static files
MySQL8.4Primary database
Redis7-alpineCache, sessions และ queue backend
SupervisorsystemPID-1 process manager ใน production
PHPStanlevel 6Static analysis ตรวจ code ก่อน deploy
PHPUnit10.5Test suite พร้อม pcov driver

โครงสร้างไฟล์และ Docker Compose

โปรเจกต์แบ่งเป็นสองโหมดชัดเจน: Dockerfile.dev สำหรับ development (มี pcov + git support) และ Dockerfile สำหรับ production (ใช้ Supervisor เป็น PID-1)

docker-compose.yml
version: '3.9'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/var/www/html
    depends_on:
      - db
      - redis
    environment:
      CI_ENVIRONMENT: development
      DB_HOST:     db
      REDIS_HOST:  redis

  nginx:
    image: nginx:1.28-alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app

  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE:      ci4app
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:7-alpine

volumes:
  db_data:

รัน Stack ทั้งหมดด้วยคำสั่งเดียว

ทุกอย่างถูก wrap ไว้ใน Makefile คำสั่ง make setup จะ build images, start containers, รอ MySQL + Redis พร้อม, รัน migrations, seed database และ hit healthcheck endpoint อัตโนมัติ

terminal
# Build, start, migrate, seed — ทุกอย่างในคำสั่งเดียว
make setup

# ตรวจสอบว่า stack ทำงานปกติ
curl http://localhost:8080/health

ถ้า stack ทำงานถูกต้อง จะได้ response JSON ดังนี้:

healthcheck response
{
  "status":    "ok",
  "database":  "ok",
  "redis":     "ok",
  "timestamp": "2026-06-26T15:00:00+00:00"
}
✅ เห็น JSON นี้ = สำเร็จ!หมายความว่า PHP-FPM, Nginx, MySQL, Redis ทำงานครบ, migrations รันเรียบร้อย และ healthcheck ผ่านทั้งหมด

3 การตัดสินใจสำคัญที่ไม่ค่อยมีคนพูดถึง

1. ใช้ Supervisor เป็น PID-1 แทน php-fpm โดยตรง

ถ้ารัน php-fpm เป็น PID-1 ตรงๆ จะเกิดปัญหา zombie processes สะสมใน container ที่รันนานๆ Supervisor จัดการ signal forwarding ได้ถูกต้อง, reap child processes และ restart service ที่ crash อัตโนมัติ

Dockerfile (production)
FROM php:8.2-fpm-alpine

RUN apk add --no-cache supervisor

# คัดลอก config ของ supervisor
COPY supervisord.conf /etc/supervisor/conf.d/app.conf

# Healthcheck via FastCGI
HEALTHCHECK --interval=10s --timeout=3s \
  CMD cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1

CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

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 ของคุณไปด้วย
Dockerfile (SSH key ปลอดภัย)
# รับ SSH key ผ่าน build argument
ARG ssh_prv_key
ARG ssh_pub_key

RUN mkdir -p /root/.ssh && \
    echo "$ssh_prv_key" > /root/.ssh/id_rsa && \
    echo "$ssh_pub_key" > /root/.ssh/id_rsa.pub && \
    chmod 600 /root/.ssh/id_rsa && \
    chmod 600 /root/.ssh/id_rsa.pub

# ลบ key หลัง build เสร็จ (ใช้ --squash)
terminal — build command
docker build \
  --build-arg ssh_prv_key="$(cat ~/.ssh/id_rsa)" \
  --build-arg ssh_pub_key="$(cat ~/.ssh/id_rsa.pub)" \
  --squash \
  -t myapp .

3. สองสภาพแวดล้อม ไฟล์เดียว .env

ใช้ .env แยก config ระหว่าง dev และ production อย่าเขียน credential ลงใน docker-compose.yml โดยตรง ใช้ variable substitution แทน

.env
CI_ENVIRONMENT=development
DB_HOST=db
DB_PORT=3306
DB_DATABASE=ci4app
DB_USERNAME=ci4user
DB_PASSWORD=secret
REDIS_HOST=redis
REDIS_PORT=6379

GitHub Actions CI/CD Pipeline

ทุก push และ pull request จะ trigger workflow อัตโนมัติ รัน PHPUnit, PHPStan และ Rector เพื่อให้มั่นใจว่า code ที่จะ merge เข้า main ผ่าน quality gate ทุกข้อ

.github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Build & Start Stack
        run: make setup

      - name: PHPStan — Static Analysis
        run: docker compose exec app vendor/bin/phpstan analyse --level=6

      - name: PHPUnit — Run Tests
        run: docker compose exec app vendor/bin/phpunit --coverage-text

      - name: Rector — Code Quality Check
        run: docker compose exec app vendor/bin/rector process --dry-run
🔁 Flow ทำงานอย่างไร
  1. Push code → GitHub Actions เริ่มทำงาน
  2. make setup build Docker stack ทั้งหมด
  3. PHPStan วิเคราะห์ type safety ระดับ 6
  4. PHPUnit รัน test suite พร้อม code coverage
  5. Rector ตรวจ code style ตาม PHP 8.2 standards
  6. ผ่านทุก step → merge ได้, fail → block merge ทันที

Docker Best Practices ที่ต้องรู้

1

ใช้ Alpine image เสมอ

เลือก php:8.2-fpm-alpine และ nginx:alpine แทน full Debian image ขนาดเล็กกว่า 5–10 เท่า attack surface น้อยกว่า และ pull เร็วกว่ามาก

2

ห้ามใส่ secret ใน image layer

ใช้ Docker build argument หรือ Docker secrets สำหรับ sensitive data ทุกชนิด ไม่ว่าจะเป็น API keys, SSH keys หรือ database passwords ถ้า key เข้าไปใน layer แล้วจะดึงออกยากมาก

3

ใช้ Healthcheck ทุก service

Docker รู้ว่า container "start" แต่ไม่รู้ว่า service พร้อมรับ request แล้ว Healthcheck บอก Docker Compose ให้รอ service จริงๆ พร้อมก่อนจึงเริ่ม service ถัดไป

4

แยก 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 ปัญหา

🚀 ลองทำตามเลยวันนี้!