Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

CI/CD Best Practices for Small Teams and Solo Developers

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.
Small Team CI/CD FlowGit Pushmain or tagLint + TestPHP, PHPUnitBuild AssetsVite, npm 12DeploySSH, DeployerSolo Developer RulesOne pipeline file in version controlManual or tag-gated production deployAtomic releases with instant rollbackSecrets only in CI variables
CI/CD best practices for small teams: a short linear pipeline with guarded production deploys and symlink rollback.

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.

Deployer Release Layout/var/www/example.com/releases/202609081200current →symlink swapshared/.env, storageRollback = one commanddep rollback productionPrevious release becomes current
Atomic Deployer releases let solo developers roll back production in seconds without rebuilding.

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.

CriteriaGitLab CIGitHub ActionsBitbucket Pipelines
Best fitSelf-hosted or GitLab reposGitHub-hosted OSS and SaaSAtlassian stack teams
Free tier400 CI minutes/month on free plan2,000 minutes/month private repos50 minutes/month free
SSH deployNative with SSH keys in variablesVia appleboy/ssh-action or raw sshNative SSH pipe support
PHP cachingBuilt-in cache: pathsactions/cache for ComposerCustom cache definitions
Manual prod gatewhen: manualworkflow_dispatch or environment protectionCustom manual steps
Learning curveModerate YAMLLarge marketplace, YAML + actionsSimple 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.

CI Tool Decision TreeWhere is your code?GitLabUse GitLab CIGitHubUse ActionsCache depsManual prod deployCache depsManual prod deploySame rules either path
Small team CI/CD tool choice: follow your Git host, then apply identical cache and deploy-gate rules.

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.

  1. Tag releases with git tag v1.4.2 && git push origin v1.4.2.
  2. Deploy from tags in CI using only: [tags] rules.
  3. Keep the last five releases on disk; prune older ones weekly.
  4. Reload PHP-FPM after symlink swap to clear opcache stale bytecode.
  5. Verify queue workers restart if you run Laravel Horizon or queue:work via 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.

Pipeline Speed: Before vs AfterBeforeCold Composer: 5 minCold npm: 4 minTests: 2 minDeploy: 3 minTotal: ~14 minAfterCached Composer: 45 secCached npm: 40 secTests: 2 minDeploy: 3 minTotal: ~6 mincache
Caching dependencies cuts solo developer CI/CD pipeline time roughly in half on typical PHP and Laravel projects.

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 .env in 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

One pipeline file with four stages: validate, test, build, deploy. Lint and test on every push, build Vite assets in CI, deploy over SSH with Deployer 7 atomic releases, store secrets in the CI vault, and gate production behind manual jobs or release tags until you trust the pipeline after ten successful deploys.

A Rs 2,500/month VPS (~USD 19) plus free-tier GitLab CI (400 minutes/month) or GitHub Actions (2,000 minutes on private repos) covers most solo Laravel projects. No Kubernetes or dedicated DevOps hire required.

You need a runner, one YAML file, and SSH access to your server — not Kubernetes or Terraform on day one. Define four stages matching your deploy path: validate runs Pint or PHP_CodeSniffer, test runs PHPUnit, build runs Composer 2.10 and npm 12 for Vite 8.x assets, deploy calls Deployer 7 over SSH. Store DEPLOY_SSH_KEY and credentials in CI masked variables. Keep .env in a Deployer shared directory outside release folders. Add a five-line curl smoke check after symlink swap to catch white-screen failures from broken storage permissions.

Pick whichever tool your Git host already provides — GitLab CI for GitLab repos, GitHub Actions for GitHub. Both support SSH deploy, PHP caching, protected branches, and Deployer 7 integration. Bitbucket Pipelines works for Atlassian stacks but offers only 50 free minutes monthly. Skip Jenkins unless a client already pays for a maintained instance; its admin overhead eats savings for a developer maintaining one WooCommerce shop and two Laravel apps. Tool choice matters less than applying identical cache keys and deploy-gate rules across repos.

Neither wins on features alone — follow your repo host to avoid wasting a week migrating repos. GitLab CI offers 400 free minutes, built-in cache paths, and native SSH key variables. GitHub Actions gives 2,000 minutes on private repos and actions/cache for Composer. Both run PHPUnit in Docker images you control and support manual production gates via when: manual or workflow_dispatch. For sister legal-tech portals I deploy, the app code differs but the pipeline skeleton stays identical regardless of host.

Solo developers have no second pair of eyes on merges, so combine automation with deliberate friction at production. Gate production behind a manual job button or semver tag — auto-deploy staging if available, then promote after click-through testing. Run php artisan migrate --force as an explicit step, not hidden inside deploy scripts, with nightly mysqldump backups via cron first. Tag releases with git tag v1.4.2, deploy from tags only, keep five releases on disk, reload PHP-FPM after symlink swap, and restart queue workers. Test dep rollback production on staging quarterly.

Auto-deploy to production on every green build feels fast until a bad migration runs at midnight. Keep production manual or tag-gated until confidence is high after ten successful releases. Staging can auto-deploy — on Adventure Third Pole Trek, a Livewire booking app, I auto-deploy staging and manually promote after a quick test. The goal is repeatable deploys with a rollback path you trust at 11 PM, not maximum deployment frequency.

Four linear stages on PHP 8.3 or 8.4 with Laravel 12 or 13.x: validate (Pint or PHPCS), test (PHPUnit with .env.testing), build (Composer 2.10 plus npm 12 compiling Vite 8.x assets), and deploy (Deployer 7 over SSH, when: manual on main). Commit compiled public/build/ artefacts when your VPS runs Apache and PHP-FPM only with no Node.js installed. Add post-deploy curl checks on the homepage and one authenticated route before closing your laptop.

Many small-team VPS servers run Apache and PHP-FPM only — no Node.js. Building on the server is fragile, slow, and adds operational complexity. Build once in CI with Node 26 LTS, ship compiled assets as deploy artefacts, and keep the server simpler and cheaper. I have seen production white screens on client projects where server-side builds failed silently; CI build artefacts eliminate that variable entirely.

Deployer 7 symlink swap makes rollback one command: dep rollback production, and PHP-FPM serves the previous release in seconds — no git revert or rebuild required. Prune releases older than the last five weekly. Reload PHP-FPM after swap to clear opcache stale bytecode. Verify queue workers restart if running Horizon or queue:work via Supervisor. A rollback you have never tested on staging is hope, not a plan — test quarterly and document restore steps in the repo README.

Store DEPLOY_SSH_KEY, database credentials, and API keys in CI masked variables — never in the repository, deploy.php, or shell history. Never echo secrets in job logs. On Deployer, keep .env in a shared directory outside release folders so it survives every deploy. Rotate keys when a contractor leaves. Agencies billing Rs 50,000/month retainers (~USD 375) should document rollback and secret-handling steps in writing so clients panic less during incidents.

Cache Composer vendor directories and npm dependencies keyed on lock file hashes — cold composer install wastes three to six minutes per job. Caching cuts pipeline time roughly in half on typical PHP projects. On free tiers, sequential stages often cost less total minutes than four parallel jobs each installing Composer from scratch. Set sensible PHPUnit scope on critical paths rather than aspirational 100% coverage gates that block every deploy. Lint .gitlab-ci.yml on a throwaway branch before merging pipeline changes to main.

A green deploy job only means SSH succeeded — not that Laravel booted. After symlink swap, curl the homepage and one authenticated route with a five-line script in the deploy job. I have seen storage/ permission breaks cause white screens on first automated deploys. Reload PHP-FPM for opcache, restart queue workers, and confirm critical payment webhooks respond. Ship logs to Sentry free tier or daily rotation so the last twenty deploy job lines and first PHP error are searchable when something breaks at dinner time.

Operational failures, not bad YAML: PHP version drift between CI (8.4 image) and production (8.3), Node builds on VPS instead of CI, secrets in deploy scripts, no health check after deploy, old Deployer releases filling disk, cron still pointing at last month's release path, and over-engineering before the first successful end-to-end deploy. Get one boring path working, then add parallel tests and coverage gates. For most client sites including Notary Kathmandu on shared Deployer infrastructure, one repo and one pipeline per site is the right unit.

No — atomic Deployer 7 releases on a single VPS cover most safe-release needs without doubling hosting cost. Blue-green deploys and feature flags are valid when traffic justifies two production nodes, but overkill for one developer on a Rs 2,500/month server. If you outgrow one server, add a read replica before sharding, a queue worker before Kubernetes, and build pipeline automation before a developer portal. Small teams win by compounding simple habits, not copying Netflix's toolchain.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: