DevOps
How to Set Up a Good CI/CD Pipeline
September 22, 2026
A CI/CD pipeline is one of those things you only truly miss when it is not there. Once you have one running, you wonder how you ever worked without it. But a bad pipeline is worse than none: slow builds, false alarms, and a team that starts ignoring the checks because they do not mean anything.
This is a practical guide to setting up a pipeline that actually works, based on what holds up in Laravel and Vue.js projects at Fantomu.dev.
What CI/CD actually means
Continuous Integration (CI) means that every push to the repository automatically runs a set of checks: tests, linting, type checks, and optionally a trial build. The goal is to catch problems before they reach the main branch.
Continuous Deployment (CD) goes one step further: passing builds are automatically distributed to an environment. That might be a staging environment on every merge to the development branch, or production on every merge to main.
Both are valuable. You do not need to start with full automated deployment to production. Begin with CI, and add CD once you have confidence in your test coverage.
The core components of a good pipeline
1. Speed over completeness
A pipeline that takes ten minutes gets bypassed. Developers push, do not wait for the result, and start something else. Aim for a pipeline that finishes in under three minutes for most pushes. Slow steps, such as end-to-end tests or full integration tests, belong in a separate pipeline that only runs on specific branches or pull requests.
2. Linting and static analysis first
Linting fails fast and gives immediately actionable feedback. Make it the first step in the pipeline so you are not waiting ten minutes for tests to discover a badly formatted file.
For Laravel: use PHP CS Fixer or Pint. For Vue.js and TypeScript: ESLint with strict rules. Both can be run locally, which means developers can fix issues before pushing.
3. Layer your tests
Not all tests need to run on every push. A sensible structure:
- On every push: unit tests and feature tests that require no external services
- On pull requests to main: full test suite including integration tests
- Nightly or manually triggered: end-to-end tests with Playwright or Dusk
This keeps the feedback loop short for daily work, while the full suite still runs before code reaches production.
4. Environment variables and secrets
Never store secrets in your repository, even in encrypted form if you can avoid it. Use your CI platform's secrets feature (GitHub Actions, GitLab CI, or Bitbucket Pipelines) and inject them as environment variables during the build. Set up a separate test database and test configuration for the CI environment. Never connect to production from a pipeline.
5. Cache your dependencies
Composer dependencies and npm packages are reinstalled from scratch on every build unless you configure caching. Use cache steps keyed on the lockfile (composer.lock or package-lock.json) to avoid this. This can bring a four-minute pipeline down to under one minute.
Deployment: from staging to production
For most projects, a two-environment model is sufficient: staging and production.
- Staging is updated automatically on every merge to the development branch. This is the environment you show clients for sign-off.
- Production is updated on every merge to main, or triggered manually via an approval step in the pipeline.
For Laravel projects on a VPS, a simple SSH deployment step works well: pull the latest changes, run composer install, run migrations, and restart the queue workers. Tools like Deployer formalise this pattern.
For container-based deployments (Docker, Kubernetes), build an image as an artefact in CI, store it in a registry, and pull it in the deployment step.
Common mistakes
- Too much in one job. Split jobs into parallel steps so linting and tests run at the same time.
- Ignoring failures. If your team starts ignoring pipeline notifications, the notifications are not set up well. Make them meaningful or turn them off.
- No rollback strategy. Automated deployment to production requires a plan for when things go wrong. A blue-green deployment or a simple revert step in the pipeline is enough.
- Production credentials in the pipeline. Always use separate credentials per environment, with the minimum permissions required.
A simple GitHub Actions workflow as a starting point
For a Laravel project with MySQL, a basic workflow looks like this: a CI job that installs Composer dependencies, creates the test database, runs migrations, and runs the test suite, followed by a deployment job that only runs on a push to main and executes an SSH deployment to the production server.
Start simple. A pipeline that is always green and runs fast is worth more than an elaborate pipeline nobody trusts. Add steps as your confidence in the automation grows.