What is CI/CD?
- CI (Continuous Integration) — automatic tests and code analysis on every push or pull request
- CD (Continuous Deployment) — automatic deployment to the server after all tests pass
The goal: to avoid deploying "manually over SSH on Friday at 5pm".
Basic workflow
Create the file .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: secret
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: pdo_pgsql, intl
coverage: xdebug
- name: Cache Composer
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
- name: Install dependencies
run: composer install --no-progress
- name: Setup test database
run: |
php bin/console doctrine:database:create --env=test
php bin/console doctrine:migrations:migrate --no-interaction --env=test
- name: Run PHPUnit
run: php bin/phpunit --coverage-text
- name: Run PHPStan
run: vendor/bin/phpstan analyse
Deployment after green tests
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/myapp
git pull origin main
composer install --no-dev --optimize-autoloader
php bin/console cache:clear --env=prod
php bin/console doctrine:migrations:migrate --no-interaction
Add the SSH_HOST, SSH_USER, SSH_PRIVATE_KEY variables in GitHub → Settings → Secrets.
Useful actions
| Action | Use |
|---|---|
actions/checkout@v4 |
Fetching the code |
shivammathur/setup-php@v2 |
PHP setup |
actions/cache@v4 |
Cache vendor/, node_modules/ |
appleboy/ssh-action |
Deployment over SSH |
Build statuses in the README
Add a badge to README.md:

→