- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
CI/CD Explained With a Simple Laravel Pipeline on GitHub Actions
About Post
Here's a deployment process I'd bet many teams still run: someone finishes a feature, merges it, connects to the server, runs git pull, remembers composer install, half-remembers the migrations, and forgets to restart the queue workers. Everything is fine until the day the person who knows the steps is on holiday.
CI/CD sounds like enterprise jargon, but the core idea is small: let a machine run the same checks and the same deployment steps, in the same order, every time. You don't need Kubernetes for that. You need one YAML file and a deploy script.
Let's build a simple, real pipeline for a Laravel 12 app with GitHub Actions, one step at a time.
CI and CD in one sentence each
- Continuous Integration (CI): every change is automatically built and tested when it's pushed, so problems are found while they're still small and fresh.
- Continuous Delivery/Deployment (CD): code that passes the checks can be released, either automatically or with one click, through a scripted process.
CI answers "is this change safe?". CD answers "how does it get to production without anyone typing commands from memory?".
Step 1: a branching flow that the pipeline can guard
A pipeline needs a clear gate to stand at. A simple flow works for most teams:
- Work happens on short-lived feature branches.
- Every change goes into
mainthrough a pull request. - The pull request runs the CI checks, and a teammate reviews the code.
- Merging to
maindeploys.
Then turn on branch protection for main in GitHub: require the CI checks to pass and require at least one approving review. Now "I'll just push a quick fix to main" is impossible by design, not by willpower. This is the way we work at Kate as well: Git branching, pull-request reviews and an automated pipeline in front of production.
Step 2: the CI workflow
Create .github/workflows/ci.yml:
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none
- run: composer install --no-interaction --prefer-dist
- run: cp .env.example .env && php artisan key:generate
- name: Code style
run: vendor/bin/pint --test
- name: Static analysis
run: vendor/bin/phpstan analyse
- name: Tests
run: php artisan test
That's a complete, working CI pipeline. On every pull request and every push to main, GitHub starts a fresh Linux machine, installs the PHP version you choose, installs your dependencies and runs three kinds of checks. Use the same PHP version as production, so the pipeline catches version problems before your server does.
Step 3: understand the three checks
Each check catches a different kind of problem, and they run from cheapest to most expensive:
- Code style (Pint). Laravel ships with Pint.
--testfails the build instead of fixing files, so style discussions disappear from code review. Runvendor/bin/pintlocally to fix. - Static analysis (PHPStan with Larastan). Finds bugs without running the code: calling a method that doesn't exist, passing the wrong type, a possibly-null value used as an object. Install with
composer require --dev larastan/larastan, add aphpstan.neon, and start at a low level. Raise it over time. - Tests (PHPUnit or Pest). The behaviour checks. Laravel's default
phpunit.xmlruns tests against an in-memory SQLite database, which is fast and fine for many apps.
Two gotchas worth knowing. If your app relies on MySQL-specific behaviour (JSON columns, full-text search, strict modes), add a MySQL service container to the job so tests run on the real engine. And if your feature tests render Blade views that use @vite, either add a npm ci and npm run build step, or call $this->withoutVite() in those tests, because there's no built manifest on a fresh runner.
Step 4: deploy only what passed
Now add a second job to the same file. It only runs on pushes to main, and only after the test job succeeds:
deploy:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Trigger deployment
run: curl -fsS -X POST "${{ secrets.DEPLOY_HOOK_URL }}"
needs: test is the important line: no green tests, no deploy. The environment: production setting lets you add protection rules in GitHub, such as a required manual approval, if you want delivery instead of fully automatic deployment.
The deploy itself can be triggered in several ways: a deploy hook from a service like Laravel Forge, an SSH step that runs a script on the server, or a container build pushed to a registry. The hook keeps this example simple and keeps server credentials out of the workflow. Whichever you choose, keep secrets in GitHub's encrypted secrets, never in the YAML.
Step 5: a deploy script, not a memory
Whatever triggers it, the server should run the same steps every time. A simplified version:
set -e
cd /var/www/app
git pull origin main
composer install --no-dev --optimize-autoloader --no-interaction
npm ci && npm run build
php artisan migrate --force
php artisan optimize
php artisan queue:restart
set -e stops the script at the first failing command, so a failed install never continues into migrations. php artisan optimize caches config, routes, views and events. queue:restart tells workers to finish their current job and reload the new code; forgetting it is the classic reason a fixed bug "keeps happening" in background jobs.
This script updates the app in place, so there's a short window while files change. Tools like Envoyer, Deployer or a symlink-based release folder give you zero-downtime deploys when you need them.
The principle: anything you have to remember to do during a deployment, you'll eventually forget. Put it in the pipeline or the script, and let the machine be the one with the good memory.
What you get
- Every pull request shows a green tick or a red cross before anyone reviews it.
- Style and simple type bugs never reach code review.
- Nothing reaches production without passing the checks.
- Deployments are boring, repeatable, and possible for anyone on the team.
Start with the CI file today; it takes minutes. Add the deploy job once you trust your tests. Then improve one piece at a time: caching Composer dependencies, running tests in parallel, adding a staging environment.
What does your deploy process look like right now? And what's the one step you'd most like to stop doing by hand?

Be first to comment it...