
September 09, 2026
14 min read
By Kokil Thapa | Last reviewed: September 2026
CI/CD best practices for small teams and solo developers are not the same ones enterprise platform teams publish. You do not have a dedicated DevOps squad, a Kubernetes cluster, or a staging environment that mirrors production down to the load balancer. You have one or two people who write code, configure the server, answer client email, and deploy on Friday afternoon. The goal is not a perfect pipeline diagram. The goal is repeatable deploys, fast feedback when something breaks, and a rollback path you trust at 11 PM. This guide covers what actually works on real Laravel and PHP projects deployed to a VPS, based on patterns I use on production sites maintained by one developer or a tiny team.
What CI/CD best practices work best for small teams and solo developers?
The highest-leverage practices are boring on purpose. Automate what repeats. Keep what is risky under human control until you have seen it work ten times. A solo developer running GitLab CI on a Rs 2,500/month VPS (~USD 19) gets more value from a five-stage pipeline that always passes than from a twenty-stage pipeline that fails on cache keys.
Start with this baseline checklist before you add complexity:
- Every push to the main branch runs lint, unit tests, and a production asset build.
- Deploy jobs run only from protected branches or release tags.
- Secrets live in CI variables or a vault, never in the repository.
- Production uses symlinked releases so rollback is one command.
- Post-deploy smoke checks confirm the app responds and critical routes load.
If you maintain multiple client sites, copy the same skeleton across repos. Sister legal-tech portals I deploy share one GitLab CI pipeline pattern for Laravel. The app code differs. The deploy mechanics stay identical. That consistency saves hours when a PHP version bump needs testing everywhere.
How do you set up a CI/CD pipeline without a dedicated DevOps engineer?
You do not need Kubernetes or Terraform on day one. You need a runner, a YAML file, and SSH access to your server. GitLab CI and GitHub Actions both work well for PHP and Laravel in 2026. Pick whichever already hosts your code. Moving repos just to change CI tools wastes a week you do not have.
Step 1: Define stages that match your actual deploy path
A practical Laravel pipeline on PHP 8.3 or 8.5 with Laravel 12 or 13.x uses four stages: validate, test, build, deploy. Validate runs Pint or PHP_CodeSniffer. Test runs PHPUnit. Build runs Composer 2.10 and npm 12 to compile Vite 8.x assets. Deploy calls Deployer 7 over SSH.
# .gitlab-ci.yml — minimal Laravel pipeline
stages:
- validate
- test
- build
- deploy
variables:
COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache"
validate:
stage: validate
image: php:8.4-cli
script:
- composer install --no-interaction --prefer-dist
- vendor/bin/pint --test
test:
stage: test
image: php:8.4-cli
script:
- composer install --no-interaction
- cp .env.testing .env
- php artisan key:generate
- vendor/bin/phpunit
build:
stage: build
image: node:26
script:
- npm ci
- npm run build
artifacts:
paths:
- public/build/
deploy:production:
stage: deploy
image: php:8.4-cli
when: manual
only:
- main
script:
- composer global require deployer/deployer:^7.0
- dep deploy production -vvv Commit compiled assets as deploy artefacts when your VPS has no Node.js installed. Many small-team servers run Apache and PHP-FPM only. Building on the server is fragile and slow. Build once in CI and ship the result.
Step 2: Wire SSH and environment secrets correctly
Store DEPLOY_SSH_KEY, database credentials, and API keys in CI masked variables. Never echo them in job logs. Read the full guide on CI/CD secrets management before you paste production .env values anywhere. On Deployer, keep .env in a shared directory outside release folders so it survives every deploy.
Step 3: Add a post-deploy smoke check
After symlink swap, hit the homepage and one authenticated route. A five-line curl script in the deploy job catches the classic "works locally, white screen in production" failure. I have seen this on client projects where storage/ permissions broke after the first automated deploy.
Which CI/CD tools should small teams choose in 2026?
Tool choice matters less than consistency. Switching from GitHub Actions to GitLab CI mid-project because a blog post said so burns time you needed for features. Compare on criteria that affect a two-person team: free minutes, SSH deploy support, secret handling, and PHP cache support.
| Criteria | GitLab CI | GitHub Actions | Bitbucket Pipelines |
|---|---|---|---|
| Best fit | Self-hosted or GitLab repos | GitHub-hosted OSS and SaaS | Atlassian stack teams |
| Free tier | 400 CI minutes/month on free plan | 2,000 minutes/month private repos | 50 minutes/month free |
| SSH deploy | Native with SSH keys in variables | Via appleboy/ssh-action or raw ssh | Native SSH pipe support |
| PHP caching | Built-in cache: paths | actions/cache for Composer | Custom cache definitions |
| Manual prod gate | when: manual | workflow_dispatch or environment protection | Custom manual steps |
| Learning curve | Moderate YAML | Large marketplace, YAML + actions | Simple but fewer examples |
For most solo PHP developers I work with, the repo host wins. If your code is already on GitLab, use GitLab CI. The detailed comparison lives in GitHub Actions vs GitLab CI for 2026. Either tool integrates with Deployer, supports protected branches, and runs PHPUnit in Docker images you control.
Skip Jenkins unless a client already pays for a maintained instance. Jenkins excels at complex multi-team workflows. For one developer maintaining a WooCommerce shop and two Laravel apps, Jenkins admin overhead eats the savings it promises.
How do you deploy safely when you are the only developer?
Solo developers face a unique risk: there is no second pair of eyes on the merge. You pushed at 4 PM. You are dinner at 6 PM. The payment webhook broke silently. Safe solo deploys combine automation with deliberate friction at the production boundary.
Use manual or tag-gated production deploys
Auto-deploy to production on every green build feels fast until a bad migration runs at midnight. Keep staging auto-deploy if you have staging. Gate production behind a manual job button or a semver tag. On projects like Adventure Third Pole Trek, a Livewire booking app, I auto-deploy to a staging subdomain and manually promote after a quick click-through test.
Run database migrations as a separate explicit step
Do not hide php artisan migrate --force inside a generic deploy script without thinking. Destructive migrations need a backup first. Schedule a nightly mysqldump via cron on the VPS. Document restore steps in the repo README. For PostgreSQL 18 or MySQL 9.7, test migrations against a anonymised dump monthly.
Keep rollbacks boring and tested
Deployer 7 symlink swap means rollback does not require git revert and rebuild. Run dep rollback production and PHP-FPM serves the previous release in seconds. Test rollback on staging quarterly. A rollback you have never run is not a rollback. It is hope.
- Tag releases with
git tag v1.4.2 && git push origin v1.4.2. - Deploy from tags in CI using
only: [tags]rules. - Keep the last five releases on disk; prune older ones weekly.
- Reload PHP-FPM after symlink swap to clear opcache stale bytecode.
- Verify queue workers restart if you run Laravel Horizon or
queue:workvia Supervisor.
Blue-green deploys and feature flags are valid but often overkill for a single VPS. Read blue-green deployment explained when traffic justifies two production nodes. Until then, atomic releases plus feature flags for small teams cover most safe-release needs without doubling hosting cost.
What should a minimal CI/CD pipeline include for speed and reliability?
Speed keeps developers running the pipeline instead of skipping it. Reliability means the pipeline fails for real problems, not flaky cache or missing extensions. These optimisations matter on free-tier minute budgets.
Cache Composer and npm dependencies aggressively
A cold composer install on every job wastes three to six minutes. Cache vendor directories keyed on lock file hashes. The dedicated guide on CI/CD caching for Composer and npm shows GitLab and GitHub cache syntax side by side. On sister sites sharing one pipeline template, identical cache keys across repos simplify debugging.
# GitLab CI cache example
cache:
key:
files:
- composer.lock
paths:
- vendor/
- .composer-cache/ Set sensible test scope, not 100% coverage day one
PHPUnit on critical paths beats aspirational coverage gates that block every deploy. Add code coverage gates in CI once you have stable tests around checkout, auth, and payment flows. A legal-tech portal with document upload needs tests on file validation and access policies. A brochure site may need only smoke tests.
Validate YAML and pipeline config in CI
Before pushing pipeline changes, use a JSON formatter and validator mindset on any generated config. Lint your .gitlab-ci.yml with CI lint API or local tools. Broken YAML that fails silently wastes an entire afternoon. I validate pipeline edits on a throwaway branch before merging to main.
Log enough to debug, not enough to drown
Ship application logs to a single searchable place. You do not need ELK on day one. A practical setup from log aggregation for small teams might be daily log rotation plus a Sentry free tier for exceptions. When a deploy fails, you need the last twenty lines of the deploy job and the first PHP error from production.
WordPress and WooCommerce 11.1 projects follow the same principle with different scripts. Run PHPCS, build theme assets if needed, rsync or Deployer to the server, and flush object cache after deploy. The WordPress development workflow differs in file layout but not in CI philosophy: test, build, deploy atomically, verify.
What mistakes break CI/CD for small teams most often?
Most failures are operational, not architectural. The pipeline YAML is fine. The server ran out of disk because old releases were never pruned. Or cron still pointed at last month's release path after Deployer moved the symlink.
Common traps I fix on production systems:
- Drift between local PHP and CI PHP versions. Pin the same 8.3 or 8.4 image in CI that production runs. Mismatch causes "passes in pipeline, fails on server" errors.
- Running Node builds on the VPS. Install Node 26 LTS locally and in CI only. Ship compiled assets. Servers stay simpler and cheaper.
- Secrets in deploy.php or shell history. Use CI variables and restricted Deployer config. Rotate keys when a contractor leaves.
- No health check after deploy. A green deploy job means SSH succeeded, not that Laravel booted. Curl the app before you close the laptop.
- Over-engineering before the first successful deploy. Get one boring path working end to end. Then add parallel tests, coverage gates, and preview environments.
Microservices tempt small teams because big companies use them. On a two-person team, a monolith with a clean pipeline beats three repos and a service mesh. The monolith vs microservices reality check article covers when splitting actually helps. For most client sites I maintain, including Notary Kathmandu on shared Deployer infrastructure, one repo and one pipeline per site is the right unit of deployment.
When CI minutes run low, optimise job parallelism carefully. Running lint and test in parallel saves time but doubles concurrent runner usage. On free tiers, sequential stages often cost less total minutes than four parallel jobs that each install Composer from scratch.
External references worth bookmarking: the official GitLab CI/CD documentation, the GitHub Actions documentation, and the Deployer 7 getting started guide. These stay current. Random Medium posts from 2022 often reference deprecated syntax.
If you outgrow a single server, incremental steps beat a full platform rewrite. Add a read replica before sharding. Add a queue worker before Kubernetes. Add build pipeline automation for release notes before you add a developer portal. Small teams win by compounding simple habits, not by copying Netflix's toolchain.
For ongoing server hardening, PHP-FPM tuning, and backup verification, pair your pipeline with solid Linux system administration practices. CI/CD deploys the code. The server still needs UFW, fail2ban, SSL renewal, and monitored disk space. A pipeline cannot fix a full /var partition.
Agencies billing in NPR should document deploy runbooks for clients. A Rs 50,000/month retainer (~USD 375) for maintenance should include "how we roll back" in writing. Clients panic less when rollback is a named step, not improvisation. The support and maintenance service model works when deploy ownership is explicit.
Finally, treat pipeline changes like application changes. Open a merge request. Describe what stage you added. Let CI validate CI. On GitLab CI/CD for PHP projects, I keep a ci-test branch workflow for experimenting with new stages without blocking client deliverables.
Key Takeaways
- Keep one pipeline file with four stages: validate, test, build, deploy — add complexity only after ten successful production releases.
- Gate production behind manual jobs or release tags; auto-deploy staging if you have it.
- Use Deployer 7 atomic releases on a VPS so rollback is one SSH command, not a rebuild.
- Cache Composer and npm by lock file hash to stay within free CI minute tiers.
- Store secrets in CI variables, keep
.envin Deployer shared paths, and never commit credentials. - Run a post-deploy curl smoke test and monitor disk space plus cron paths after every symlink swap.
People Also Ask
Do solo developers really need CI/CD?
Yes, if you deploy more than once a month or sleep while production runs. CI/CD catches broken tests before deploy, standardises the release steps you would otherwise run from memory, and gives you rollback when a migration goes wrong. A solo developer benefits most from the safety net, not from enterprise orchestration features.
How much does CI/CD cost for a small team?
Many solo and two-person teams stay on free tiers: GitHub Actions offers 2,000 minutes per month on private repos, GitLab offers 400 minutes on its free plan. A cached Laravel pipeline run often takes six to eight minutes. That supports dozens of deploys monthly at zero CI cost. Paid runners start around USD 10–20/month when you exceed limits or need faster hardware.
Should small teams auto-deploy to production?
Auto-deploy to production works after the pipeline has been stable for weeks and you have automated smoke tests plus database backups. Until then, use manual production jobs or tag-only deploy rules. Staging auto-deploy is lower risk and catches environment-specific bugs before clients see them.
What is the simplest CI/CD setup for a Laravel app on a VPS?
GitLab CI or GitHub Actions runs Pint, PHPUnit, Composer install, and npm build on push. A manual deploy job SSHs to Ubuntu, runs Deployer 7, swaps the symlink, reloads PHP-FPM, and curls the homepage. Shared .env and storage/ persist across releases. Total setup time for an experienced developer is one to two days including staging verification.
Ship boring pipelines that survive real Friday deploys
CI/CD best practices for small teams and solo developers boil down to disciplined simplicity: one pipeline, cached dependencies, guarded production deploys, atomic releases, and a rollback you have actually tested. You do not need a platform team. You need a repeatable path from git push to verified production that still works when you are tired and the client is waiting. If you want help wiring GitLab CI, Deployer, or a first production pipeline on a Nepal or remote VPS, contact us or explore custom software development and the Court Marriage In Nepal portfolio case for Laravel sites deployed through the same workflow.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

