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.

Bitbucket Pipelines CI/CD Guide

By Kokil Thapa | Last reviewed: August 2026

Shipping code without automated testing and deployment is a liability for any production web system. This Bitbucket Pipelines CI/CD Guide provides the exact configuration patterns I use to automate Laravel and PHP application delivery, eliminating manual FTP uploads and inconsistent server states. Whether you are maintaining a legal-tech portal or a high-traffic eCommerce store, proper pipeline configuration ensures your production environment matches your local development setup every single time.

For teams evaluating their infrastructure options, understanding the difference between platform-managed pipelines and self-hosted runners is critical before writing your first YAML line. If you are also exploring backend architecture decisions alongside your DevOps setup, reading about modern Laravel architecture best practices will help align your pipeline stages with your application's actual structural needs.

Git Pushmain / developBuild & TestPHP 8.4 + MySQLPackageArtefacts + AssetsDeploySSH + Symlink SwapCache: Composer/npmArtefact Storage
Core Bitbucket Pipelines CI/CD Guide workflow: source control triggers build, test, package, and deploy stages with integrated caching

How do you configure Bitbucket Pipelines for Laravel applications?

Laravel 12.x requires PHP 8.2 minimum, but in 2026 most production systems should target PHP 8.4 for performance and security updates. Your bitbucket-pipelines.yml must specify an image that includes the necessary PHP extensions for Laravel: mbstring, xml, zip, bcmath, pdo_mysql, and redis. Using the official Atlassian PHP images or community-maintained Laravel-specific images prevents extension mismatch errors that commonly break builds.

Base pipeline configuration

The following configuration runs on every push to main and develop branches. It uses services for MySQL and Redis, matching the typical Laravel stack. Note that the MySQL service uses a temporary filesystem (tmpfs) to speed up database operations during testing — this is safe for CI because data does not persist between steps.

<?php
# 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:7.4

pipelines:
  branches:
    main:
      - step:
          name: Test & Build
          size: 2x
          caches:
            - composer
            - node
          services:
            - mysql
            - redis
          script:
            - apt-get update && apt-get install -y libzip-dev unzip git nodejs npm
            - docker-php-ext-install zip pdo_mysql bcmath opcache
            - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
            - composer install --no-interaction --prefer-dist --optimize-autoloader
            - cp .env.ci .env
            - php artisan key:generate
            - php artisan migrate:fresh --force
            - ./vendor/bin/phpunit --coverage-clover=coverage.xml
            - npm ci && npm run build
          artifacts:
            - vendor/
            - public/build/
            - bootstrap/cache/**

A common mistake I see on client projects is forgetting to copy a CI-specific .env.ci file before running Artisan commands. Laravel will fail silently or throw cryptic errors if the environment file is missing. Always include this step explicitly rather than relying on default behaviour.

Environment variable management

Never commit secrets to your repository. Use Bitbucket workspace or repository variables for sensitive values like APP_KEY, database passwords for staging databases, API keys, and SSH private keys. Mark sensitive variables as "secured" in the Bitbucket UI so they are masked in logs and cannot be retrieved via the API. For Laravel projects, store the base64-encoded APP_KEY as a secured variable and decode it in your pipeline script:

script:
  - echo $APP_KEY_BASE64 | base64 -d > .env.app_key
  - cat .env.ci .env.app_key > .env

How do you optimise Bitbucket Pipelines build times for PHP projects?

Unoptimised PHP pipelines frequently exceed 10–15 minutes per build, primarily due to redundant dependency installation. Caching is the single most impactful optimisation for Laravel and Symfony projects. Bitbucket Pipelines supports built-in cache definitions for Composer and npm, but you must configure them correctly to avoid stale dependencies.

Without CacheComposer Install3 min 20 secnpm Install2 min 45 secTests1 min 30 secBuild Assets1 min 10 secTotal: 8m 45sWith Cache HitCache8 secCache6 secTests1 min 30 secBuild Assets1 min 10 secTotal: 3m 02sCache Key Strategy: checksum(composer.lock) + checksum(package-lock.json)Invalidates only when dependency manifests change — not on every commit
Build time reduction using proper cache key strategies in Bitbucket Pipelines CI/CD Guide configurations

Correct cache key configuration

The default cache key in many tutorials uses branch names, which causes unnecessary cache misses. Instead, use file checksums to create deterministic cache keys. Define custom caches in your pipeline definitions:

definitions:
  caches:
    composer: vendor
    node: node_modules

pipelines:
  default:
    - step:
        caches:
          - composer
          - node
        script:
          - composer validate --strict
          - composer install --no-interaction --prefer-dist --optimize-autoloader

Bitbucket automatically generates cache keys based on the cache name and branch by default. For better hit rates across branches, consider using custom cache keys based on lock file hashes. This ensures that feature branches with identical dependencies share the same cache as main, reducing build times from 8+ minutes to under 3 minutes on cache hits.

Parallel step execution

For larger Laravel applications with extensive test suites, split testing and asset compilation into parallel steps. Both steps can restore from the same Composer cache independently:

  • Step A (Test): Runs PHPUnit/Pest with database migrations against MySQL service
  • Step B (Assets): Runs Vite build for production CSS/JS bundles
  • Step C (Deploy): Depends on both A and B completing successfully

This parallelism reduces total pipeline duration significantly for projects where asset compilation takes 60+ seconds. On one legal-tech portal I maintain, parallelising these steps cut the feedback loop from 12 minutes to 7 minutes.

What is the best deployment strategy for zero-downtime Laravel releases?

Zero-downtime deployment means serving requests continuously during releases. The symlink-swap pattern is the industry standard for PHP applications and works reliably with Bitbucket Pipelines. Tools like Deployer 7 automate this process, creating timestamped release directories, sharing persistent storage and environment files, and atomically swapping the current symlink.

/var/www/appcurrent → release-20260820releases/20260820103000releases/20260819091500releases/20260818080000shared/ (.env, storage/)Deployment Sequence1. Create new timestamped release directory2. Upload artefacts + install Composer deps3. Symlink shared/.env and shared/storage/4. Run migrations + cache:clear + config:cache5. Atomic symlink swap (ln -sfn)6. Reload PHP-FPM + prune old releases
Zero-downtime symlink swap architecture used in production Laravel deployments via Bitbucket Pipelines

Integrating Deployer with Bitbucket Pipelines

Add Deployer as a dev dependency and invoke it from your pipeline. Store the SSH private key as a secured Bitbucket variable and write it to a temporary file during deployment:

- step:
    name: Deploy to Production
    deployment: production
    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

Always use the deployment keyword in Bitbucket Pipelines. This enables environment-specific variable scoping and provides deployment tracking in the Bitbucket UI. Production deployments should require manual approval or be restricted to the main branch only.

Post-deployment verification

After the symlink swap, verify the deployment succeeded before marking the pipeline as complete. Add a health-check step that curls your application's health endpoint and validates the response:

- step:
    name: Verify Deployment
    script:
      - RESPONSE=$(curl -sf https://your-app.com/api/health)
      - echo "$RESPONSE" | grep -q '"status":"ok"' || exit 1
      - echo "Deployment verified successfully"

This catches scenarios where the deployment completed technically but the application is broken due to misconfiguration, missing environment variables, or failed migrations. I have prevented multiple production outages on client sites by including this simple verification gate.

How do Bitbucket Pipelines compare to GitHub Actions and GitLab CI for PHP?

Choosing a CI/CD platform depends on your existing code hosting, team size, budget, and specific workflow requirements. Each platform has distinct trade-offs for PHP and Laravel projects.

CriteriaBitbucket PipelinesGitHub ActionsGitLab CI/CD
Free tier minutes50 min/month (workspace)2,000 min/month (public repos)400 min/month (shared runners)
Docker flexibilityFull Docker support, custom imagesExtensive marketplace actionsNative container registry integration
Self-hosted runnersAvailable (Linux, macOS, Windows)Available (all platforms)Available (most flexible)
Integrated issue trackingJira integration (native)GitHub Issues/ProjectsGitLab Issues/Milestones
PHP ecosystem supportGood, fewer pre-built actionsExcellent (setup-php action)Excellent, strong PHP templates
Nepal payment/billingCredit card only (USD)Credit card only (USD)Credit card only (USD)
Best forJira-using teams, Atlassian stackOpen source, large communitiesSelf-hosted, enterprise, DevOps-heavy

For Nepal-based teams already using Jira for project management, Bitbucket Pipelines offers seamless integration that reduces context switching. However, if your primary concern is maximising free CI minutes for open-source Laravel packages or learning projects, GitHub Actions provides significantly more generous free tiers. For teams requiring full infrastructure control or operating behind corporate firewalls, GitLab CI with self-managed runners remains the most flexible option.

If you are a freelancer or agency owner evaluating costs, remember that all three platforms bill in USD. At current exchange rates, exceeding free tiers can cost Rs 1,500–5,000/month (~USD 11–37). Budget accordingly and monitor usage dashboards regularly. For more context on managing freelance infrastructure costs, see the Nepal income tax guide for freelancers.

How do you troubleshoot common Bitbucket Pipelines failures?

Pipeline failures fall into predictable categories. Understanding these patterns saves hours of debugging time.

Memory and resource limits

Laravel applications with large test suites or complex asset builds frequently exceed the default 1GB memory allocation. When you see "Out of memory" or "Killed" errors, increase the step size:

- step:
    size: 2x  # 4GB RAM, 2 CPU cores
    script:
      - ./vendor/bin/phpunit

Sizes available in 2026: 1x (default, 1GB), 2x (4GB), 4x (8GB), 8x (16GB). Start with 2x for Laravel projects; only escalate if profiling confirms genuine memory pressure. Larger sizes consume build minutes faster.

Service container connectivity

MySQL and Redis services take several seconds to initialise. If your tests fail with connection refused errors immediately after starting, add a wait loop:

script:
  - until mysqladmin ping -h127.0.0.1 -ularavel -psecret --silent; do sleep 1; done
  - php artisan migrate:fresh --force

This pattern is essential. I have seen countless pipelines that pass intermittently because the database was sometimes ready before tests ran and sometimes not. Deterministic waits eliminate this flakiness entirely.

Permission and ownership issues

Bitbucket Pipelines runs as root inside containers. When deploying artefacts to servers running as www-data or another non-root user, file permissions can prevent PHP-FPM from reading cached views or writing logs. Always set correct ownership during deployment:

script:
  - chown -R www-data:www-data storage/ bootstrap/cache/
  - chmod -R 775 storage/ bootstrap/cache/

For teams managing multiple Laravel applications or integrating CI/CD into broader DevOps workflows, working with a CI/CD pipeline setup expert in Nepal can prevent weeks of trial-and-error configuration.

Implementing Reliable Bitbucket Pipelines CI/CD Guide Workflows

Effective CI/CD is boring, predictable, and invisible when working correctly. Start with the base Laravel configuration shown above, add caching immediately, implement zero-downtime deployments with Deployer, and always include post-deployment verification. Monitor your build times weekly — if they creep above 10 minutes, profile and optimise before developer frustration sets in.

Your pipeline is production infrastructure. Treat it with the same rigour as your application code: version control your YAML, review changes in pull requests, and document non-obvious decisions. The Bitbucket Pipelines CI/CD Guide patterns here reflect real production systems serving Nepali businesses daily. Adapt them to your specific constraints, but do not skip the fundamentals.

Ready to automate your Laravel deployments or need help configuring pipelines for an existing project? Contact me to discuss your CI/CD requirements.

Frequently Asked Questions

Bitbucket Pipelines is a cloud-based CI/CD service integrated directly into Bitbucket repositories. It uses Docker containers to run build, test, and deployment steps defined in a bitbucket-pipelines.yml file at your repo root. Every push or pull request triggers the configured pipeline automatically without external Jenkins servers.

Free tier includes 50 build minutes monthly. Paid plans start around USD 3 per user/month for additional minutes. For Nepal agencies billing clients in NPR, expect Rs 400–600 per developer monthly. Overage charges apply if you exceed included minutes, so monitor usage closely during initial setup and testing phases.

GitHub Actions offers more community actions and faster Windows/macOS runners. Bitbucket Pipelines integrates tighter with Jira and Bitbucket Cloud repos, reducing context switching for teams already in that ecosystem. For pure Laravel deployments to Linux servers via Deployer, both work equally well; choose based on existing repository location and team workflow preferences rather than technical superiority.

Generate an ED25519 key pair locally using ssh-keygen -t ed25519. Add the private key as a secured repository variable named DEPLOY_SSH_KEY in Bitbucket settings. Add the public key to your target server's authorized_keys file. Reference the variable in your pipeline script using echo $DEPLOY_SSH_KEY | base64 -d > /tmp/deploy_key && chmod 600 /tmp/deploy_key before running deployment commands.

Yes. Install Deployer via Composer in your pipeline step, then run dep deploy production. Store SSH keys and environment variables as secured repository variables. Ensure your pipeline image has PHP 8.2+ and required extensions matching your production server. I have used this exact pattern on multiple legal-tech portals sharing infrastructure, achieving zero-downtime symlinked releases consistently across sister sites.

Common causes include incorrect key format, missing newline at end of private key variable, wrong file permissions on the temporary key file, or SSH host key verification failures. Always set chmod 600 on the key file and add ssh -o StrictHostKeyChecking=no to bypass interactive prompts. Verify the key works locally first before debugging inside the container environment where error messages are less verbose.

Use the caches keyword in your bitbucket-pipelines.yml definition. Specify composer under caches for your PHP step to persist the vendor directory between builds. This reduces install time from minutes to seconds on subsequent runs. Note that cache invalidation happens automatically when composer.lock changes, ensuring dependency updates trigger fresh installs while unchanged locks reuse cached artifacts efficiently.

Use php:8.2-cli or php:8.3-cli as your base image since Laravel 12 requires PHP 8.2 minimum. Install required extensions like pdo_mysql, mbstring, xml, zip, and bcmath via apt-get in a custom Dockerfile or inline script. Avoid latest tags; pin specific versions for reproducible builds. Node.js 22 LTS should be added separately if frontend asset compilation is needed during the pipeline execution.

Store sensitive values like database passwords, API keys, and SSH credentials as secured repository or workspace variables in Bitbucket settings, never committed to code. Mark them as secured to mask values in logs. Access them as standard environment variables in pipeline scripts. Rotate credentials regularly and audit variable access through Bitbucket audit logs to maintain security compliance for client projects handling legal or financial data.

Run php artisan migrate --force only after successful deployment and health checks, not during build steps. Use maintenance mode to prevent user access during schema changes. Always backup databases before migration in production pipelines. For Laravel apps, consider running migrations in a separate post-deploy step with rollback capability. Never run destructive migrations without tested down methods and verified restore procedures in place.

Enable verbose output by adding -v flags to commands. Use echo statements before critical operations to verify variable values and paths. Download artifacts from failed builds to inspect generated files. Reproduce failures locally using the same Docker image specified in your pipeline. Check Bitbucket Pipeline logs for truncated error messages; sometimes failures occur silently in shell scripts without proper exit code propagation causing misleading success indicators downstream.

Yes, define multiple parallel steps under the parallel keyword in your YAML configuration. Split test suites by directory or group across concurrent containers to reduce total runtime. Each parallel step gets its own isolated environment, so shared state requires external services like Redis or database fixtures. Monitor minute consumption carefully since parallel steps consume build minutes simultaneously, potentially exhausting free tier limits faster than sequential execution would.

Add the atlassian/slack-notify pipe after your deployment or test steps. Configure SLACK_WEBHOOK_URL as a secured repository variable. Customize message templates to include branch name, commit hash, and deployment environment. Trigger notifications only on failure for production deployments to avoid noise, but notify on all statuses for staging environments during active development sprints. Test webhook connectivity in a non-critical pipeline first to validate formatting before relying on alerts for production monitoring workflows.

Environment differences between local machines and Docker containers cause most issues. Missing system packages, different PHP versions, absent SSH agents, and hardcoded absolute paths break automated flows. File permissions inside containers differ from host systems. Cron jobs referencing release paths need updating after symlink swaps. Start by automating tests before attempting deployments. Validate every assumption about your environment explicitly in pipeline scripts rather than assuming parity with your development machine setup.

Use custom pipelines with variables to differentiate staging, production, and testing targets. Define separate deployment steps triggered manually or by branch patterns. Store environment-specific configurations as distinct secured variables prefixed with environment names. Use conditional logic or separate pipeline definitions for each target. On projects with shared infrastructure like legal service portals, I maintain single pipeline files with parameterized deploy targets to reduce duplication while keeping environment isolation strict and auditable.

Share this article

Quick Contact Options
Choose how you want to connect me: