Step-by-step guide to CI/CD with GitHub Actions for PHP/Laravel
By Kevin Marcondes · Published on · Updated on · 8 min read
This step-by-step guide shows how to implement CI/CD with GitHub Actions for PHP/Laravel projects. You’ll learn about triggers, jobs, caching, tests, secrets security, and deploy strategies for staging and production. By the end you’ll have a clear roadmap to set up a reliable pipeline with practical tips for maintenance and rollback.
Pipeline overview and prerequisites
CI/CD integrates automated validations on every change and delivers releases predictably. In Laravel, a pipeline typically includes dependency installation, asset builds, tests and deploys using artisan commands.
Define the pipeline goal before writing any workflow. Decide what runs on each branch, when manual approval is required, and how deploys to staging and production will occur.
Standardize PHP versions and extensions. Document system dependencies and confirm the database required for tests. This avoids surprises on the runner.
Create predictable environments. Use variables, secrets and consistent versioning. That reduces differences between your machine and the CI.
- Access to the GitHub repository and defined main branches.
- composer.json and composer.lock committed to the repo.
- phpunit.xml configured and tests that run in seconds to a few minutes.
- Server or PaaS for deploy via SSH, API or container registry.
- Provisioned test database (MySQL, PostgreSQL or SQLite).
- Versioning and release strategy defined.
Prepare the project: Composer, scripts and environments
Ensure composer.lock is committed. This makes builds reproducible. Install the minimum required extensions and disable Xdebug in CI to speed up the pipeline.
Create scripts in composer.json to standardize commands. Centralize tests, linters and static analysis so the CI only needs to call scripts already used locally.
Keep a complete .env.example. It serves as the base to generate test environments. Never commit real credentials. APP_KEY must be injected via secrets.
Enable Laravel caches in production for better performance. Use config:cache, route:cache and view:cache. See Laravel docs for configuration details.
- scripts.test: vendor/bin/phpunit
- scripts.analyse: vendor/bin/phpstan analyse
- scripts.lint: vendor/bin/php-cs-fixer fix --dry-run --diff
- scripts.post-deploy: php artisan optimize && php artisan migrate --force
Create the workflow in GitHub Actions
Create the file .github/workflows/ci.yml. Use on: push and on: pull_request to validate new changes. Configure a PHP version matrix if needed.
Choose ubuntu-latest as the runner and install PHP with required extensions. Use Composer cache keyed to composer.lock to reduce install time.
If your tests require a database, use runner services. Start MySQL or PostgreSQL as a service and run migrations before tests.
Separate jobs by purpose. Example: build and tests in one job; static analysis in another; deploy in a job conditioned on branch and environment approval.
| Step | Purpose | Example commands |
|---|---|---|
| Setup PHP | Define version and extensions | Install PHP 8.2/8.3, intl, mbstring, pdo, pdo_mysql/pdo_pgsql |
| Cache Composer | Speed up dependencies | composer install --prefer-dist --no-interaction --no-progress |
| Provision DB | Prepare tests | Create database, php artisan key:generate, migrations |
| Tests | Validate the code | vendor/bin/phpunit --colors=never |
| Static analysis | Catch errors early | vendor/bin/phpstan analyse |
| Lint | Enforce style | vendor/bin/php-cs-fixer fix --dry-run |
| Deploy | Deliver with control | php artisan down/up, migrate --force, optimize |
Testing, quality and coverage
Run fast, reliable tests. Favor unit and integration tests with in-memory SQLite when possible. That lowers cost and reduces flaky failures.
Use coverage reports when appropriate. Coverage doesn’t guarantee quality but helps identify untested areas.
Adopt static analysis with PHPStan or Psalm to catch type and contract issues before production.
Standardize style with PHP-CS-Fixer or PHPCS. Consistent code simplifies reviews and reduces merge conflicts.
- Test domain logic without accessing the network unnecessarily.
- Avoid fragile tests that depend on dates and timeouts.
- Mark slow tests and run them only on pushes to main.
- Failures should block merges on pull requests.
Security: secrets, permissions and isolation
Never commit credentials. Use GitHub Actions secrets for APP_KEY, database credentials, SSH keys and deploy tokens. Restrict access per environment.
Apply the principle of least privilege. Create deploy users with limited permissions and avoid reusing keys.
Mask sensitive output in logs. Don’t echo critical variables. Revoke and rotate credentials periodically.
Consider OpenID Connect to authenticate to cloud providers without long-lived keys. This lowers leakage risk.
- Segregate staging and production with separate secrets and environments.
- Require review or approval to promote to production.
- Audit who can view and edit repository secrets.
- Avoid echoing sensitive variables in workflow steps.
Continuous deploy: secure staging and production
Create GitHub environments (staging and production) with protections and required approvals. Deploys start only after quality jobs pass.
Define a packaging strategy. You can produce an artifact with vendor and compiled assets, or build on the server. Artifacts reduce variability.
During deploy, use predictable windows. In Laravel, run config:cache, route:cache and view:cache, then run migrations with --force.
For zero-downtime, use versioned releases with a symlink to current. Publish a new release folder, run migrations, then point the symlink to it.
If you use queues, restart workers after deploy. For Laravel Horizon, use horizon:terminate to reload workers without stopping the queue.
- Suggested order: enable maintenance, publish artifact, run migrations, clear specific caches, disable maintenance.
- Keep 3–5 previous releases for easy rollback.
- Automate a health check after deploy using a status route.
- Consider Docker to standardize the runtime, especially for larger teams.
Observability, rollback and continuity
Monitor metrics and logs to confirm deploy success. Alerts help act quickly when something degrades.
Implement health checks and an automated smoke test after deploy. If it fails, trigger a rollback to the previous release.
Keep consistent backups of the database and critical files before sensitive changes. That reduces impact during incidents.
- Centralize application and server logs.
- Track commit version and release number in headers.
- Create rollback playbooks and test them regularly.
- Integrate alerts with team chat or email.
Common mistakes and final checklist
Some issues repeat in new pipelines. Mapping them ahead of time speeds stabilization and prevents unnecessary incidents.
Use this checklist to validate essential points before enabling automatic production deploys.
- Common mistakes: not committing composer.lock; missing APP_KEY in the environment; running migrations without backups; missing OS dependencies; slow pipelines due to no caching.
- Checklist: triggers defined; PHP matrix tested; Composer cache enabled; test database provisioned; secrets configured; environments require approval; idempotent deploy script; documented rollback.
Frequently asked questions
How should I handle the database in automated deploys?
Run migrations with --force only after your tests and backups pass. Prefer backward-compatible migrations for risky schema changes and keep rollback scripts or playbooks ready. For zero-downtime approaches, separate locking or long-running migration steps into controlled windows, and verify migrations in staging first so production changes are predictable and reversible.
Do I need to use Docker for CI/CD on GitHub Actions?
Docker is not mandatory. It helps standardize the runtime and reproduce issues across environments, which is useful for larger teams or varied server setups. For smaller projects, GitHub hosted runners with runner services for databases are usually sufficient. Consider Docker when local, CI and production environments differ significantly or when container images simplify deploys.
How can I optimize pipeline time?
Use Composer cache keyed to composer.lock to avoid repeated installs, disable Xdebug in CI, and install dependencies with --prefer-dist. Isolate slow tests so they run only on main pushes, run static analysis in parallel jobs, and avoid rebuilding assets for pull requests that don't touch frontend files. These measures reduce runtime and reduce flaky failures.
Is it safe to store APP_KEY in GitHub secrets?
Storing APP_KEY in GitHub Actions secrets is appropriate when access to secrets is restricted and repository permissions are audited. Never echo secrets in logs, and rotate or revoke keys periodically. For some PaaS providers, prefer injecting APP_KEY directly in the provider environment rather than exposing it through repository files or outputs.
How do I separate staging and production in the workflow?
Create distinct GitHub environments for staging and production with separate secrets and protections, and require approvals for production deploys. Condition deploy jobs by branch (for example, develop for staging and main for production) and, if needed, publish different artifacts per environment to minimize variability between staging and production.
Can I use self-hosted runners for deploys?
Yes. Self-hosted runners provide full control over dependencies, network access and private resources, but they require maintenance and security management. Isolate projects, restrict runner permissions, keep systems updated, and use them when hosted runners cannot access required resources or when you need specific OS-level dependencies not available on GitHub hosted runners.
Ready to set up your CI/CD pipeline?
Want to speed up CI/CD implementation with GitHub Actions for PHP/Laravel or review your current pipeline? Let’s discuss your context and next steps.
Talk about my pipelineSee also
- Pre-deploy checklist and quick rollback plan for PHP applications in production
- Security checklist for CI/CD pipelines with self‑hosted runners and secrets management
- Guide: Centralize logs with Fluentd and Elasticsearch for PHP apps on a VPS
- Guide: Organizing PostgreSQL Migrations and Rollbacks for Small Teams