CI/CD — Continuous Integration and Continuous Deployment defined precisely
Concept
CI/CD (Continuous Integration / Continuous Delivery or Deployment) — a set of practices that automate the testing, building, and deployment of code changes.
Continuous Integration (CI): Developers merge code changes frequently (multiple times per day). Each merge triggers automated tests and build checks. The goal: catch integration failures fast, not days later.
Continuous Delivery (CD): Ensuring code is always in a deployable state. After CI passes, a deployment to production is possible at any time — but a human still clicks the deploy button.
Continuous Deployment: Like Continuous Delivery, but the deploy to production is also automated. Every passing commit goes to production automatically.
A typical CI pipeline:
- Developer pushes code.
- CI triggers: install dependencies, run static analysis (PHPStan, Larastan), run tests (PHPUnit).
- If all pass: artifact is built (Docker image, zip archive).
- CD: deploy to staging, run smoke tests, deploy to production.
Benefits of CI/CD:
- Bugs found at merge time, not deployment time.
- No "integration hell" — small, frequent merges stay small.
- Reproducible builds — same environment every time.
- Fast feedback loop — know if your change breaks something in minutes.
Common CI tools: GitHub Actions, GitLab CI, CircleCI, Jenkins, Bitbucket Pipelines.
Laravel + CI: php artisan test, ./vendor/bin/phpstan analyse, ./vendor/bin/pint --test in the CI pipeline.
Code Example
# .github/workflows/ci.yml — GitHub Actions CI pipeline for Laravel
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_DATABASE: testing
MYSQL_ROOT_PASSWORD: password
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: pdo, pdo_mysql, mbstring
coverage: pcov
- name: Cache Composer packages
uses: actions/cache@v3
with:
path: vendor/
key: ${{ runner.os }}-php-${{ hashFiles('composer.lock') }}
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Copy env
run: cp .env.ci .env
- name: Generate key
run: php artisan key:generate
- name: Run static analysis
run: ./vendor/bin/phpstan analyse --no-progress
- name: Check code style
run: ./vendor/bin/pint --test
- name: Run tests
run: php artisan test --parallel --coverage --min=80
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: testing
DB_USERNAME: root
DB_PASSWORD: password
deploy-staging:
needs: test # only runs if test job passes
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to staging
run: ./scripts/deploy.sh staging// .env.ci — CI-specific environment
# .env.ci
APP_ENV=testing
DB_CONNECTION=mysql
CACHE_DRIVER=array
QUEUE_CONNECTION=sync
MAIL_MAILER=array