
August 20, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
A Laravel app that builds locally but ships through manual FTP uploads will eventually break production. Wiring Laravel, Git, Bitbucket, and npm into one pipeline removes that risk. You push to Bitbucket, the runner installs PHP dependencies with Composer, compiles front-end assets with npm and Vite, runs tests, then deploys through SSH. I use this pattern on legal-tech portals and eCommerce sites where downtime costs real money. This guide covers the YAML, the Bitbucket Pipelines stages concept, caching, manual approval gates, and Deployer-based releases that actually survive Monday traffic.
bitbucket-pipelines.yml at the repo root with PHP and Node steps, Composer and npm caches keyed to lock files, service containers for MySQL, parallel test and build stages, and a manual production deploy step after artefacts pass.Before you write YAML, align pipeline stages with how your app is structured. If you run a monolith with queues and scheduled tasks, read modern Laravel architecture best practices first. Pipeline design follows application boundaries, not the other way around.
How do you connect Laravel, Git, Bitbucket, and npm in one pipeline?
Laravel 13.x needs PHP 8.3 or higher. Laravel 12.x runs on PHP 8.2+. Most teams I work with target PHP 8.4 or 8.5 on the runner image. Your bitbucket-pipelines.yml must install extensions Laravel expects: mbstring, xml, zip, bcmath, pdo_mysql, and redis. Node.js 26 LTS handles npm 12 and Vite 8.x asset builds. Place the file at the repository root next to composer.json and package.json.
Base configuration for Laravel 12/13
The example below runs on pushes to main and develop. MySQL and Redis run as service containers. MySQL uses a tmpfs mount so migrations stay fast. This matches patterns from the official Bitbucket Pipelines documentation.
# bitbucket-pipelines.yml
image: php:8.4-cli
definitions:
services:
mysql:
image: mysql:8.4
variables:
MYSQL_DATABASE: 'testing'
MYSQL_ROOT_PASSWORD: 'secret'
MYSQL_USER: 'laravel'
MYSQL_PASSWORD: 'secret'
redis:
image: redis:8-alpine
caches:
composer: vendor
node: node_modules
pipelines:
branches:
main:
- step:
name: Test and Build
size: 2x
caches:
- composer
- node
services:
- mysql
- redis
script:
- apt-get update && apt-get install -y libzip-dev unzip git curl
- curl -fsSL https://deb.nodesource.com/setup_26.x | bash -
- apt-get install -y nodejs
- docker-php-ext-install zip pdo_mysql bcmath opcache
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- until mysqladmin ping -h127.0.0.1 -ularavel -psecret --silent; do sleep 1; done
- composer install --no-interaction --prefer-dist --optimize-autoloader
- cp .env.ci .env
- php artisan key:generate
- php artisan migrate:fresh --force
- ./vendor/bin/pest --coverage-clover=coverage.xml
- npm ci && npm run build
artifacts:
- vendor/**
- public/build/**
- bootstrap/cache/** A common mistake is skipping .env.ci before Artisan runs. Laravel throws opaque errors when APP_KEY or database credentials are missing. Copy the CI env file explicitly every time. For deeper test setup, see Laravel testing with Pest in CI/CD.
Secrets and environment variables
Never commit production secrets to Git. Store APP_KEY, SSH keys, and API tokens as secured repository or workspace variables in Bitbucket. Mark sensitive values as secured so logs mask them. Merge CI templates at runtime:
script:
- echo "$APP_KEY_BASE64" | base64 -d >> .env.ci
- cp .env.ci .env For broader secret-handling patterns, read how to handle secrets in CI/CD pipelines safely. The same rules apply whether you use Bitbucket, GitLab, or GitHub.
What is the Bitbucket Pipelines stages concept?
Bitbucket Pipelines organises work into steps grouped inside pipelines. A pipeline runs when a trigger fires — usually a Git push or pull request. Each step executes inside a Docker container with its own script block, caches, services, and artefacts. Steps in the same list run sequentially unless you use parallel. Later steps can consume artefacts from earlier ones.
Think of it as a assembly line. Stage one validates PHP. Stage two compiles front-end assets. Stage three deploys only if both pass. This maps cleanly to how Laravel teams split backend and front-end work. Understanding stages prevents you from cramming install, test, build, and deploy into one bloated step that fails opaquely.
Multi-stage pipeline example
Splitting test and asset compilation into parallel steps cuts wall-clock time. Both restore the same Composer cache independently:
pipelines:
branches:
main:
- step:
name: Install Dependencies
caches: [composer, node]
script:
- composer install --no-interaction
- npm ci
artifacts:
- vendor/**
- node_modules/**
- parallel:
- step:
name: Run Tests
script:
- ./vendor/bin/pest
- step:
name: Build Assets
script:
- npm run build
artifacts:
- public/build/**
- step:
name: Deploy Staging
deployment: staging
script:
- php deployer.phar deploy staging Artefacts are the glue between stages. Without them, each step starts from a clean checkout. Pass vendor/, public/build/, and compiled config cache forward so deploy steps do not reinstall everything. For Vite-specific tuning, see the Vite config guide for Laravel projects.
How do you set up Bitbucket Pipelines manual step approval?
Production deploys should not fire automatically on every green build. Bitbucket supports manual steps with the trigger: manual keyword. The pipeline pauses until a authorised team member clicks Deploy in the Bitbucket UI. This is your Bitbucket Pipelines manual step approval gate.
pipelines:
branches:
main:
- step:
name: Test and Build
script:
- composer install && npm ci && npm run build
- ./vendor/bin/pest
artifacts:
- vendor/**
- public/build/**
- step:
name: Deploy to Production
deployment: production
trigger: manual
script:
- php deployer.phar deploy production Combine deployment: production with trigger: manual for environment-scoped variables and an audit trail. Only users with deployment permissions see the Run button. Staging can stay automatic on every merge to develop. Production waits for human confirmation after tests pass.
On sister legal-tech sites I maintain with Deployer 7 and GitLab CI, the same pattern applies. Bitbucket's native manual trigger is simpler than webhook-based approval systems. Document who may approve production deploys in your runbook. Pair this with a post-deploy health check so approval does not mean blind trust.
How do you optimise Composer and npm cache in Bitbucket Pipelines?
Uncached Laravel pipelines often exceed ten minutes. Most of that is redundant composer install and npm ci. Bitbucket provides named caches, but default branch-based keys cause misses across feature branches. Tie cache invalidation to lock files instead.
Cache definitions that actually hit
definitions:
caches:
composer: vendor
node: node_modules Run composer validate --strict before install to catch lock file drift early. For more tactics, read CI/CD caching for fast Composer and npm installs. Use the JSON formatter tool to inspect pipeline log payloads when cache restore fails silently.
What is the best zero-downtime deploy strategy for Laravel on Bitbucket?
PHP apps deploy best with symlink swaps, not in-place overwrites. Deployer 7 creates timestamped release directories, links shared .env and storage/, runs migrations, then atomically points current at the new release. I've used this on production Laravel applications for years without user-visible downtime during deploys.
Deployer step in Bitbucket
- step:
name: Deploy Production
deployment: production
trigger: manual
script:
- apt-get update && apt-get install -y openssh-client rsync
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-keyscan -H your-server.com >> ~/.ssh/known_hosts
- curl -LO https://deployer.org/deployer.phar
- php deployer.phar deploy production --no-interaction See the full Deployer 7 getting started guide for recipe configuration. For Laravel-specific recipes, read zero-downtime deployment for Laravel with Deployer and the Laravel production deployment checklist.
Post-deploy health check
- step:
name: Verify Deployment
script:
- curl -sf https://your-app.com/api/health | grep -q '"status":"ok"' This catches broken migrations, missing env vars, and opcache staleness. A green deploy step means nothing if HTTP returns 500. On a legal-tech portal I built, this gate prevented a bad config cache from reaching clients.
How do Bitbucket Pipelines compare to GitHub Actions and GitLab CI?
Platform choice depends on where your Git repos live, free-minute budgets, and team tooling. All three run Laravel, Git, and npm workflows well. Differences show up in pricing, marketplace actions, and self-hosted runner flexibility.
| Criteria | Bitbucket Pipelines | GitHub Actions | GitLab CI/CD |
|---|---|---|---|
| Free tier minutes | 50 min/month per workspace | 2,000 min/month on public repos | 400 min/month on shared runners |
| Manual deploy gates | Native trigger: manual | Environment protection rules | Manual jobs in rules |
| PHP ecosystem | Docker images, custom scripts | setup-php action, large marketplace | Strong PHP templates |
| Jira integration | Native Atlassian stack | Third-party apps | Third-party apps |
| Self-hosted runners | Linux, macOS, Windows | All major platforms | Most flexible options |
| Best fit | Atlassian/Jira teams | Open source, GitHub-centric shops | Self-managed DevOps teams |
Teams on Jira often pick Bitbucket because branch and deployment status sync into tickets automatically. If you already run GitLab elsewhere, compare against the GitLab CI/CD for Laravel step-by-step guide and GitHub Actions vs GitLab CI comparison. Exceeding free tiers costs Rs 1,500–5,000/month (~USD 11–37) at current rates. Track usage in the dashboard. See the Nepal income tax guide for freelancers for deducting infrastructure spend.
How do you troubleshoot common Bitbucket Pipelines failures?
Most failures fall into four buckets: memory limits, flaky services, permission errors, and cache corruption. Knowing which bucket saves hours.
Memory and step size
Laravel test suites and Vite builds can exceed the default 1 GB allocation. OOM kills show as exit code 137 or plain "Killed". Use size: 2x for 4 GB on Laravel projects:
- step:
size: 2x
script:
- ./vendor/bin/pest Available sizes in 2026: 1x (1 GB), 2x (4 GB), 4x (8 GB), 8x (16 GB). Larger sizes burn minutes faster. Profile before jumping to 8x.
Service container timing
MySQL needs a few seconds after the container starts. Without a wait loop, tests fail intermittently with connection refused:
script:
- until mysqladmin ping -h127.0.0.1 -ularavel -psecret --silent; do sleep 1; done
- php artisan migrate:fresh --force Deterministic waits beat retry decorators for database readiness. Flaky pipelines erode team trust faster than slow pipelines.
File permissions on deploy
Pipeline containers run as root. Production PHP-FPM often runs as www-data. Fix ownership after upload:
script:
- chown -R www-data:www-data storage/ bootstrap/cache/
- chmod -R 775 storage/ bootstrap/cache/ Server-side issues like this overlap with Linux system administration for production PHP hosts. Wrong permissions are a top reason Laravel works in CI but fails after deploy.
When to call for help
Multi-app setups, custom runners, or compliance requirements add complexity fast. A CI/CD pipeline setup expert in Nepal can short-circuit weeks of trial and error. For application delivery beyond pipelines, see web development services and examples like Court Marriage In Nepal — a Laravel legal portal shipped with automated deploys.
Key Takeaways
- Place
bitbucket-pipelines.ymlat the repo root and wire Laravel (Composer), Git triggers, Bitbucket runners, and npm (Vite) in separate logical steps. - Use the Bitbucket Pipelines stages concept: sequential steps for install, parallel blocks for test vs build, artefacts to pass
vendor/andpublic/build/forward. - Add
trigger: manualon production deploy steps for Bitbucket Pipelines manual step approval before traffic hits new code. - Cache
vendorandnode_moduleswith keys tied to lock files — expect roughly 60% faster builds on cache hits. - Deploy with Deployer symlink swaps, reload PHP-FPM after the atomic
currentswitch, and curl a health endpoint before marking success. - Store secrets as secured Bitbucket variables; never commit
.envproduction values to Git.
People Also Ask
Does Bitbucket Pipelines support Laravel and npm in the same pipeline?
Yes. One pipeline can run PHP steps for Composer and Artisan, then Node steps for npm ci and npm run build. Install Node inside the PHP image or split work across parallel steps that share cached dependencies through artefacts.
What are Bitbucket Pipelines stages?
Stages are steps grouped under a pipeline trigger. Each step runs in its own container with a script, optional caches, services, and artefacts. Sequential steps wait for the prior step. Parallel blocks run simultaneously. Manual steps pause until a human approves.
How do I require manual approval before production deploy?
Add trigger: manual to the production deploy step and set deployment: production. Bitbucket shows a Run button only to users with deployment permissions. Pair this with branch restrictions so only main can reach that step.
How many free build minutes does Bitbucket Pipelines include?
Free workspaces get 50 build minutes per month on the standard tier as of 2026. Paid plans add more minutes and larger step sizes. Monitor usage under Repository settings → Pipelines → Usage. Self-hosted runners consume no cloud minutes.
Ship Laravel with confidence through Bitbucket Pipelines
Connecting Laravel, Git, Bitbucket, and npm in one pipeline is straightforward once you respect stages, caching, and manual production gates. Start with test and build on every push. Add Deployer deploys with symlink swaps. Verify health after each release. Treat your YAML like application code — review changes in pull requests and document non-obvious decisions.
Need help wiring pipelines for an existing Laravel project or migrating from manual FTP deploys? Contact us to discuss CI/CD setup, or reach out directly with your repository details and current hosting stack.
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.

