GitLab CI + aaPanel: how I deploy Laravel without drama on a single VPS

Short answer: I deploy aviwebsquad.in with GitLab CI into timestamped release folders on aaPanel, symlink current, and keep .env + storage in a shared tree—no Kubernetes, no blue-green cluster, just disciplined bash.

If you read my CI/CD pipeline overview, this is the specific implementation on a ₹-friendly VPS—not theory.

Why not “just git pull” on the server?

I tried that on early projects. It fails quietly when:

Aviwebsquad uses scripts/deploy_production.sh with:

/releases/{sha}-{pipeline}/
/shared/.env
/shared/storage
/current -> latest release

Each pipeline run is immutable. Rollback = repoint symlink.

GitLab shell runner on aaPanel

aaPanel ships PHP at /www/server/php/84/bin/phpnot on PATH for the gitlab-runner user. CI failed with php: command not found until I added scripts/ci-env.sh:

export PATH="/www/server/php/84/bin:$PATH"

That one line is now sourced in .gitlab-ci.yml test/build/deploy jobs. See also Pest tests before deploying CMS changes—tests run in the same PATH context.

Deploy steps that actually matter

  1. Build assets on CI (npm ci && npm run build) so the server never compiles Vite under traffic.
  2. Rsync release excluding node_modules, including public/build.
  3. php artisan migrate --force only after symlink swap (or with maintenance mode— I prefer a 2-second window).
  4. php artisan optimize + Horizon restart if queues changed.

What I deliberately skip

Relationship to preview environments

Preview environments with GitHub Actions is the pattern for PRs. Production stays GitLab → aaPanel because that is where the domain already lives.

FAQ

Is this zero-downtime?

Near-zero. Requests during symlink swap might hit old or new release—both must boot. I run smoke Pest tests post-deploy.

aaPanel vs Laravel Forge?

Forge is smoother; aaPanel is what this VPS already had. The release-folder pattern works on both.

Where do MCP content writes fit?

Content agents hit Sanctum APIs—deploy does not touch posts. See securing the MCP content API.