GitLab CI/CD dùng một file YAML để mô tả pipeline gồm stage, job, runner, biến môi trường, cache, artifact và điều kiện deploy. Với ứng dụng PHP và Node.js, cấu hình tốt không chỉ là chạy composer install hoặc npm ci; nó phải phân biệt dependency cache với artifact, chạy test trong môi trường tái lập và không để secret xuất hiện trong log.
Bài viết này xây dựng một pipeline cơ bản có test PHP, test Node.js, lưu artifact build và deploy có điều kiện. Bạn có thể bắt đầu bằng một file .gitlab-ci.yml nhỏ, sau đó mở rộng sang matrix, environment, manual approval và chiến lược rollback.
1. GitLab CI/CD gồm pipeline, stage và job
Pipeline là toàn bộ lần chạy được kích hoạt bởi push, merge request, tag hoặc lịch. Stage mô tả thứ tự logic như validate, test, build và deploy. Job là đơn vị thực thi chạy trên runner; nhiều job trong cùng stage có thể chạy song song nếu không phụ thuộc nhau. Tài liệu GitLab phân biệt cache dùng để tái sử dụng file giữa các job với artifact dùng để truyền output của job hoặc tải về sau.
Stage diễn tả thứ tự, job diễn tả công việc, runner cung cấp nơi chạy. Hiểu ba lớp này giúp YAML dễ đọc và dễ debug hơn.Diễn giải thực hành dựa trên GitLab CI/CD YAML syntax documentation.
stages:
- validate
- test
- build
- deploy
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
lint:
stage: validate
script:
- ./ci/lint.sh
test_php:
stage: test
script:
- composer install --no-interaction --prefer-dist
- vendor/bin/phpunit2. Runner và image thực thi
Runner là máy thực thi job. Với ứng dụng PHP, có thể dùng image PHP CLI có Composer hoặc cài Composer trong before_script. Với Node.js, chọn image Node phù hợp và dùng npm ci hoặc pnpm install --frozen-lockfile. Pin major runtime trong pipeline giúp giảm khác biệt giữa laptop và CI, nhưng vẫn cần cập nhật image có kiểm soát để nhận security update.
| Thành phần | PHP | Node.js |
|---|---|---|
| Runtime | php:8.3-cli hoặc image tương thích | node:22 hoặc phiên bản đã pin |
| Lockfile | composer.lock | package-lock.json hoặc pnpm-lock.yaml |
| Install | composer install --no-interaction | npm ci hoặc pnpm install --frozen-lockfile |
| Test | PHPUnit, Pest, PHPStan | npm test, pnpm test, ESLint, TypeScript |
| Artifact | build output, coverage, vendor khi cần | dist, build, coverage hoặc package bundle |
Không nên dùng image “latest” cho production pipeline nếu không có lý do và kiểm soát thay đổi. Tách image build khỏi image deploy nếu runtime production không cần toolchain.
3. Variables và secrets
GitLab variables có thể khai báo ở project, group hoặc environment. Biến cấu hình thông thường có thể nằm trong YAML nếu không nhạy cảm, còn token, private key, database password và deploy credential phải lưu trong secret store của GitLab với masking và protection phù hợp. Không echo toàn bộ biến môi trường để debug.
variables:
PHP_VERSION: "8.3"
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.cache/composer"
cache:
key:
files:
- composer.lock
paths:
- .cache/composer/
security_scan:
stage: validate
script:
- test -n "$CI_COMMIT_SHA"
- ./ci/check-secrets.shBiến protected chỉ nên xuất hiện trong pipeline branch/tag được bảo vệ. Nếu job chạy trên merge request từ fork mà có thể truy cập secret production, đó là lỗi thiết kế. Hãy tách job deploy production và đặt điều kiện branch, environment hoặc manual approval.
4. Cache và artifact không giống nhau
Cache giúp job sau hoặc pipeline sau dùng lại dependency nhằm giảm thời gian tải. Artifact là output được GitLab lưu giữ theo job, thường dùng để truyền thư mục dist từ build sang deploy hoặc tải coverage report. Cache có thể bị miss; artifact của một pipeline thành công phải là nguồn xác định cho bước deploy trong pipeline đó.
cache_dependencies:
stage: test
cache:
key:
files:
- package-lock.json
paths:
- .npm/
script:
- npm ci --cache .npm --prefer-offline
- npm test
build_frontend:
stage: build
needs: [cache_dependencies]
script:
- npm run build
artifacts:
name: "frontend-$CI_COMMIT_SHORT_SHA"
paths:
- dist/
expire_in: 7 daysCache key nên thay đổi khi lockfile thay đổi. Không cache .env hoặc credential. Artifact nên có tên chứa commit SHA, có retention phù hợp và chỉ chứa file cần cho deploy hoặc audit. Nếu artifact lớn, xem xét object storage hoặc registry thay vì đẩy toàn bộ thư mục lên GitLab mỗi lần.
5. Pipeline PHP có test, static analysis và artifact
Một pipeline PHP thực dụng có thể chạy lint, static analysis, unit test và đóng gói source. Nếu deploy bằng Docker, artifact có thể là Dockerfile và metadata; nếu deploy bằng release directory, artifact là source đã loại test data cùng vendor đã cài. Không nên tạo vendor bằng PHP version khác với runtime production nếu có extension hoặc native dependency.
php_test:
image: php:8.3-cli
stage: test
cache:
key:
files:
- composer.lock
paths:
- .composer-cache/
before_script:
- apt-get update -qq && apt-get install -y -qq git unzip
- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
- php composer-setup.php --install-dir=/usr/local/bin --filename=composer
script:
- COMPOSER_CACHE_DIR=.composer-cache composer install --no-interaction --prefer-dist
- vendor/bin/phpunit
- vendor/bin/phpstan analyse --no-progress6. Pipeline Node.js cho npm hoặc pnpm
Node.js job nên dùng lockfile bắt buộc. Với npm, npm ci sẽ fail nếu package-lock không đồng bộ; đó là tín hiệu tốt vì CI không tự sửa dependency. Với pnpm, pin phiên bản pnpm và dùng corepack hoặc image đã chuẩn bị sẵn. Nếu build frontend, artifact thường là dist; nếu build server, cần kèm production dependency và start command rõ ràng.
node_test:
image: node:22-bookworm-slim
stage: test
cache:
key:
files:
- pnpm-lock.yaml
paths:
- .pnpm-store/
before_script:
- corepack enable
- corepack prepare pnpm@10.12.4 --activate
- pnpm config set store-dir .pnpm-store
script:
- pnpm install --frozen-lockfile
- pnpm lint
- pnpm test
- pnpm build
artifacts:
paths:
- dist/
expire_in: 7 daysKhông truyền npm token hoặc registry credential bằng command line nếu command có thể hiện trong log. Dùng masked variable, .npmrc tạm thời có quyền hạn tối thiểu và xóa file sau job. Kiểm tra package script trước khi chạy trên runner có secret.
7. needs, dependencies và chạy song song
Nếu test PHP và Node.js độc lập, cho chúng chạy song song trong cùng stage. Job build chỉ bắt đầu khi các test cần thiết pass. Từ khóa needs giúp pipeline không phải chờ toàn bộ stage khi chỉ phụ thuộc một số job, nhưng hãy đặt quan hệ rõ để không vô tình deploy khi thiếu test.
stages: [test, build, deploy]
test_php:
stage: test
script: ["./ci/test-php.sh"]
test_node:
stage: test
script: ["./ci/test-node.sh"]
build:
stage: build
needs: [test_php, test_node]
script: ["./ci/build.sh"]
artifacts:
paths: [dist/]
deploy_production:
stage: deploy
needs:
- job: build
artifacts: true
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
script: ["./ci/deploy.sh"]8. Rules, branch và manual deployment
Deploy production không nên chạy từ mọi branch. rules có thể giới hạn theo default branch, tag hoặc biến môi trường. Job manual tạo một điểm kiểm duyệt; environment production có thể yêu cầu approval và ghi lại lịch sử deployment. Với merge request, chỉ chạy test và build, không cấp deploy credential nếu không cần.
Pipeline cũng nên có workflow: rules để tránh chạy hai pipeline không cần thiết cho cùng một thay đổi, chẳng hạn vừa branch pipeline vừa merge request pipeline. Tối ưu này giảm chi phí runner nhưng không nên đánh đổi khả năng kiểm thử bắt buộc.
9. Artifact, release và rollback
Một release tốt gắn với commit SHA, artifact checksum và log test. Khi deploy, máy chủ nhận artifact bất biến rồi chuyển symlink hoặc cập nhật container tag. Nếu health check thất bại, rollback về artifact trước đó. Không build lại trên server bằng source hiện tại vì kết quả có thể khác runner và khó audit.
deploy:
stage: deploy
needs:
- job: build
artifacts: true
script:
- sha256sum dist.tar.gz
- ./ci/upload-release.sh "$CI_COMMIT_SHA" dist.tar.gz
- ./ci/activate-release.sh "$CI_COMMIT_SHA"
- ./ci/smoke-test.sh || ./ci/rollback.sh
environment:
name: production
url: https://example.comNếu ứng dụng có database migration, rollback code và rollback schema phải được thiết kế cùng nhau. Migration một chiều có thể khiến release cũ không chạy được. Với website PHP hoặc Node.js nhỏ, chiến lược expand-and-contract và backup trước deploy thường an toàn hơn một lệnh rollback vội.
10. Lỗi YAML và lỗi vận hành thường gặp
- Indent YAML sai khiến job hoặc variables nằm ngoài scope dự kiến.
- Dùng cache thay cho artifact và deploy từ dependency cache không xác định.
- Không khóa runtime hoặc package manager, làm kết quả thay đổi theo ngày.
- Job deploy đọc secret từ pipeline merge request không được bảo vệ.
- Artifact hết hạn trước khi cần rollback hoặc chứa quá nhiều file không cần.
- Dùng latest image và cài package không theo lockfile.
- Không có needs nên deploy chạy dù một job kiểm thử bắt buộc thất bại.
- Build trên runner nhưng deploy lại build lại trên VPS.
Bài CI/CD và best practices cung cấp bức tranh tổng quát. Nếu pipeline đóng gói container, hãy kết hợp với bài Docker Compose production và bài multi-stage Dockerfile.
11. Checklist .gitlab-ci.yml production
- Xác định stage và job theo thứ tự validate, test, build, deploy.
- Pin image/runtime và dùng lockfile cho Composer, npm hoặc pnpm.
- Tách cache dependency khỏi artifact release.
- Dùng masked/protected variables cho secret và không in chúng ra log.
- Đặt needs để build/deploy phụ thuộc đúng job test.
- Giới hạn deploy bằng branch, tag, environment hoặc manual approval.
- Đặt artifact retention đủ cho điều tra và rollback.
- Đưa checksum, commit SHA và health check vào quy trình release.
- Chạy cold pipeline định kỳ để phát hiện phụ thuộc cache ẩn.
12. Kết luận
GitLab CI/CD cơ bản không cần bắt đầu bằng một YAML khổng lồ. Một pipeline rõ ràng với job test cho PHP và Node.js, cache đúng mục đích, artifact có version và deploy được bảo vệ đã đủ tạo nền tảng tốt. Sau khi pipeline ổn định, bạn mới nên thêm matrix, parallel, security scan, canary hoặc environment phức tạp hơn.