PHPackages                             ddr/forge-test-branches - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [DevOps &amp; Deployment](/categories/devops)
4. /
5. ddr/forge-test-branches

ActiveLibrary[DevOps &amp; Deployment](/categories/devops)

ddr/forge-test-branches
=======================

Create ephemeral test environments from git branches on Laravel Forge

v1.10.0(2mo ago)0722↓35.3%[3 PRs](https://github.com/danie1net0/forge-test-branches/pulls)MITPHPPHP ^8.4CI passing

Since Dec 29Pushed 1mo agoCompare

[ Source](https://github.com/danie1net0/forge-test-branches)[ Packagist](https://packagist.org/packages/ddr/forge-test-branches)[ Docs](https://github.com/danie1net0/forge-test-branches)[ GitHub Sponsors]()[ RSS](/packages/ddr-forge-test-branches/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (32)Versions (29)Used By (0)

Forge Test Branches
===================

[](#forge-test-branches)

[![Latest Version on Packagist](https://camo.githubusercontent.com/5c0667cabc71a14a9e748022caba4fb89373f60fb665065bd05c7d4c9e5ee03c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6464722f666f7267652d746573742d6272616e636865732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ddr/forge-test-branches)[![GitHub Tests Action Status](https://camo.githubusercontent.com/7c0b1877bb9d96d1588393b9dbc8c1df83ae10084bde34e25890df1b581f8c69/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f64616e6965316e6574302f666f7267652d746573742d6272616e636865732f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/ddr/forge-test-branches/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/14aefe822b6c7fe519a8865c93de17dda21b27a46b897ad381ba00711bb74e01/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f64616e6965316e6574302f666f7267652d746573742d6272616e636865732f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/ddr/forge-test-branches/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/53b2476d5513a8c2c1414660559e2ffe3005eaaf8543bd38fe4bb95284301071/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6464722f666f7267652d746573742d6272616e636865732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ddr/forge-test-branches)

**Languages:** English | [Español](README.es.md) | [Português](README.pt-BR.md)

Create ephemeral test environments (review apps) from git branches on Laravel Forge. Ideal for validating features with stakeholders before merging.

Purpose
-------

[](#purpose)

This package automates the creation and management of test environments on Laravel Forge. It eliminates the manual configuration needed to create temporary sites from git branches.

Without the package, creating a test environment requires:

- Manually create site on Forge
- Configure git repository and branch
- Create database
- Create database user
- Configure environment variables
- Set up SSL
- Initial deployment
- Remember to delete everything later

With the package, you run a command and all of this happens automatically. When the branch is deleted, a webhook cleans up the resources.

What it does
------------

[](#what-it-does)

The package manages the complete lifecycle of review environments:

**Creation**

1. Creates a site on Forge with domain based on branch name
2. Installs git repository on the specified branch
3. Creates database with configurable prefix
4. Creates database user with access only to the created database
5. Configures custom environment variables
6. Runs deploy script (migrations, composer, npm)
7. Sets up Let's Encrypt SSL certificate
8. Enables quick deploy (optional)

**Destruction**

- Webhook detects when branch is deleted
- Removes site, database, and database user
- Cleans local records

Use cases
---------

[](#use-cases)

### 1. Feature validation with product managers

[](#1-feature-validation-with-product-managers)

You're developing a new checkout interface. The PM needs to validate before merging to production.

```
git checkout -b feat/new-checkout

# ... develop the feature ...

# Create test environment
php artisan forge-test-branches:create --branch=feat/new-checkout
```

Result: `https://feat-new-checkout.review.myapp.com` with its own database, valid SSL, ready for validation.

The PM accesses the link, validates the feature. After merge, the webhook automatically deletes the environment.

### 2. Testing with external APIs

[](#2-testing-with-external-apis)

Payment feature integrating with gateway. Each branch needs to use sandbox credentials.

```
// config/forge-test-branches.php
'env_variables' => [
    'PAYMENT_GATEWAY_URL' => 'https://sandbox.gateway.com',
    'PAYMENT_GATEWAY_KEY' => 'sandbox_key_{slug}',
],
```

Each review environment has its own credentials, avoiding interference between tests.

### 3. Client demonstrations

[](#3-client-demonstrations)

Agency developing custom features. Client wants to see progress before delivery.

```
# .gitlab-ci.yml
review_app:
    stage: review
    script:
        - php artisan forge-test-branches:create
        - php artisan forge-test-branches:deploy
    when: manual
```

Developer creates branch, opens MR and clicks "Deploy Review". Client accesses isolated environment without affecting other environments.

### 4. Schema migration testing

[](#4-schema-migration-testing)

New migration that changes database structure. Need to validate in clean environment before merge.

```
git checkout -b feat/add-user-preferences

# Create environment and run migrations automatically
php artisan forge-test-branches:create
```

Automatic deployment runs migrations. If it fails, you fix it before merging to develop/main.

### 5. Debugging production bugs

[](#5-debugging-production-bugs)

Bug reported in production. You need to investigate with similar data but without risk.

```
git checkout -b fix/payment-timeout production
php artisan forge-test-branches:create
```

Environment identical to production, with realistic data seeders. You investigate, apply fix, test and validate before merging.

### 6. Performance testing with real data

[](#6-performance-testing-with-real-data)

Report export feature. Need to test with realistic data volume.

```
// config/forge-test-branches.php
'deploy' => [
    'seed' => true,
    'seed_class' => 'PerformanceSeeder',
],
```

Each deployment runs the seeder that creates 100k+ records. You test performance in isolation.

How it works
------------

[](#how-it-works)

```
CREATION:
Branch created → CI/CD triggers commands → Site + DB + SSL created
                                            ↓
                          https://branch-name.review.mysite.com

DESTRUCTION:
Branch deleted → Webhook triggers → Site + DB removed

```

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11+
- Laravel Forge account with API Token

Installation
------------

[](#installation)

```
composer require ddr/forge-test-branches
```

Interactive installation (recommended):

```
php artisan forge-test-branches:install
```

This command:

- Publishes configuration
- Configures environment variables
- Optionally adds job to GitLab CI

Configuration
-------------

[](#configuration)

Add to `.env`:

```
FORGE_API_TOKEN=your-forge-token
FORGE_SERVER_ID=123456
FORGE_REVIEW_DOMAIN=review.mysite.com
FORGE_GIT_PROVIDER=gitlab
FORGE_GIT_REPOSITORY=username/repository
```

Full configuration in `config/forge-test-branches.php`:

```
return [
    'forge_api_token' => env('FORGE_API_TOKEN'),
    'server_id' => env('FORGE_SERVER_ID'),

    'domain' => [
        'base' => env('FORGE_REVIEW_DOMAIN'),
        'pattern' => '{branch}.{base}', // feat-hu-123.review.mysite.com
    ],

    'git' => [
        'provider' => env('FORGE_GIT_PROVIDER', 'gitlab'), // gitlab, github, bitbucket
        'repository' => env('FORGE_GIT_REPOSITORY'),
    ],

    'branch' => [
        'patterns' => ['*'], // ['feat/*', 'review/*', 'fix/*']
    ],

    'database' => [
        'prefix' => env('FORGE_DB_PREFIX', 'review_'),
    ],

    'site' => [
        'php_version' => env('FORGE_PHP_VERSION', 'php84'),
        'project_type' => env('FORGE_PROJECT_TYPE', 'php'),
        'directory' => env('FORGE_WEB_DIRECTORY', '/public'),
        'isolated' => env('FORGE_ISOLATED', false),
    ],

    'deploy' => [
        'script' => null, // null = Forge default script
        'quick_deploy' => true,
        'seed' => env('FORGE_SEED', false),
        'seed_class' => env('FORGE_SEED_CLASS'),
    ],

    'webhook' => [
        'enabled' => env('FORGE_WEBHOOK_ENABLED', true),
        'secret' => env('FORGE_WEBHOOK_SECRET'),
        'path' => 'forge-test-branches/webhook',
    ],

    'env_variables' => [
        // Custom variables for site .env
        // 'APP_URL' => 'https://{slug}.review.mysite.com',
    ],
];
```

Usage
-----

[](#usage)

### Commands

[](#commands)

```
# Create environment
php artisan forge-test-branches:create --branch=feat/new-feature

# Deploy updates
php artisan forge-test-branches:deploy --branch=feat/new-feature

# Destroy environment
php artisan forge-test-branches:destroy --branch=feat/new-feature

# Update deploy script for an existing environment
php artisan forge-test-branches:update-script --branch=feat/new-feature

# Test Forge API connection
php artisan forge-test-branches:test-connection

# List all environments (shows Active/Orphan status)
php artisan forge-test-branches:list

# List only orphaned environments (branch no longer exists on remote)
php artisan forge-test-branches:list --orphans

# Destroy all orphaned environments (with confirmation)
php artisan forge-test-branches:list --destroy-orphans
```

In CI/CD, the `CI_COMMIT_REF_NAME` variable is automatically detected:

```
php artisan forge-test-branches:create
```

### Facade

[](#facade)

```
use Ddr\ForgeTestBranches\Facades\ForgeTestBranches;

// Create
$env = ForgeTestBranches::create('feat/new-feature');
echo $env->domain; // feat-new-feature.review.mysite.com

// Check existence
if (ForgeTestBranches::exists('feat/new-feature')) {
    //
}

// Find
$env = ForgeTestBranches::find('feat/new-feature');

// Deploy
ForgeTestBranches::deploy('feat/new-feature');

// Destroy
ForgeTestBranches::destroy('feat/new-feature');

// List all environments
$environments = ForgeTestBranches::listAll();
```

CI/CD Integration
-----------------

[](#cicd-integration)

### GitLab

[](#gitlab)

Add to `.gitlab-ci.yml`:

```
stages:
    - review

review_app:
    stage: review
    image: php:8.4-cli
    before_script:
        - apt-get update && apt-get install -y git unzip
        - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
        - composer install --no-interaction --prefer-dist
    script:
        - php artisan forge-test-branches:create --branch=$CI_COMMIT_REF_NAME
        - php artisan forge-test-branches:deploy --branch=$CI_COMMIT_REF_NAME
    environment:
        name: review/$CI_COMMIT_REF_SLUG
        url: https://$CI_COMMIT_REF_SLUG.review.mysite.com
        on_stop: stop_review
    rules:
        - if: $CI_MERGE_REQUEST_ID
          when: manual

stop_review:
    stage: review
    image: php:8.4-cli
    before_script:
        - apt-get update && apt-get install -y git unzip
        - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
        - composer install --no-interaction --prefer-dist
    script:
        - php artisan forge-test-branches:destroy --branch=$CI_COMMIT_REF_NAME
    environment:
        name: review/$CI_COMMIT_REF_SLUG
        action: stop
    rules:
        - if: $CI_MERGE_REQUEST_ID
          when: manual
```

The `on_stop: stop_review` ensures GitLab automatically destroys the environment when the merge request is merged.

### Webhook for Automatic Cleanup

[](#webhook-for-automatic-cleanup)

The webhook detects when a branch is deleted and automatically removes the review environment.

**How it works:**

- GitLab: Sends a Push Hook with `after: "0000000000000000000000000000000000000000"` when branch is deleted
- GitHub: Sends a `delete` event when branch is deleted

**GitLab Configuration:**

1. Go to **Settings &gt; Webhooks**
2. URL: `https://your-app.com/forge-test-branches/webhook`
3. Secret token: **same value as `FORGE_WEBHOOK_SECRET` in .env**
4. Check only: **Push events**
5. Uncheck "Enable SSL verification" if using development domain
6. Click "Add webhook"

**Test webhook:**After configuring, click "Test" &gt; "Push events" in GitLab. You should see HTTP 200 and message "Event ignored" or "Not a branch deletion" (normal, as the test is not a real deletion).

**GitHub Configuration:**

1. Settings &gt; Webhooks
2. Payload URL: `https://your-app.com/forge-test-branches/webhook`
3. Secret: value from `FORGE_WEBHOOK_SECRET`
4. Events: **Branch or tag deletion**

### GitHub Actions

[](#github-actions)

```
name: Review App

on:
    pull_request:
        types: [opened, synchronize]

jobs:
    deploy-review:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4

            - name: Setup PHP
              uses: shivammathur/setup-php@v2
              with:
                  php-version: "8.4"

            - name: Install Dependencies
              run: composer install --no-interaction

            - name: Create Review Environment
              env:
                  FORGE_API_TOKEN: ${{ secrets.FORGE_API_TOKEN }}
                  FORGE_SERVER_ID: ${{ secrets.FORGE_SERVER_ID }}
                  FORGE_REVIEW_DOMAIN: ${{ secrets.FORGE_REVIEW_DOMAIN }}
                  FORGE_GIT_REPOSITORY: ${{ github.repository }}
              run: |
                  php artisan forge-test-branches:create --branch=${{ github.head_ref }}
                  php artisan forge-test-branches:deploy --branch=${{ github.head_ref }}
```

Advanced settings
-----------------

[](#advanced-settings)

### Custom deploy script

[](#custom-deploy-script)

```
'deploy' => [
    'script' =>  [
    'APP_ENV' => 'staging',
    'APP_DEBUG' => 'true',
    'APP_URL' => 'https://{slug}.review.mysite.com',
    'CACHE_PREFIX' => '{slug}_cache',
],
```

The `{slug}` placeholder is replaced by the sanitized branch name.

### Branch filters

[](#branch-filters)

```
'branch' => [
    'patterns' => ['feat/*', 'fix/*'],
],
```

Only branches matching the patterns will have environments created.

### Orphan cleanup

[](#orphan-cleanup)

Environments become orphaned when their branch is deleted without triggering the webhook (e.g., deleted via merge request). The `list` command detects these by comparing environments against remote branches via `git ls-remote`.

```
# See all environments with status
php artisan forge-test-branches:list

# Output:
# +---------------------+-------------------------------------------+--------+---------+
# | Branch              | Domain                                    | Status | Site ID |
# +---------------------+-------------------------------------------+--------+---------+
# | feat/active-branch  | feat-active-branch.review.mysite.com      | Active | 123456  |
# | feat/deleted-branch | feat-deleted-branch.review.mysite.com     | Orphan | 123457  |
# +---------------------+-------------------------------------------+--------+---------+

# Destroy all orphans (asks for confirmation)
php artisan forge-test-branches:list --destroy-orphans

# Destroy without confirmation (for scheduled tasks)
php artisan forge-test-branches:list --destroy-orphans --force
```

You can also schedule orphan cleanup in your `routes/console.php`:

```
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Schedule::command('forge-test-branches:list --destroy-orphans --force')
    ->weekly();
```

### Logging

[](#logging)

The package logs all operations to a dedicated channel. Logs are written to `storage/logs/forge-test-branches-YYYY-MM-DD.log` with daily rotation.

Events logged:

- Environment creation, destruction, and deployment
- Webhook received, processed, ignored, or rejected
- Signature validation failures

Configuration in `config/forge-test-branches.php`:

```
'logging' => [
    'enabled' => env('FORGE_LOG_ENABLED', true),
    'channel' => 'forge-test-branches',
    'driver' => 'daily',
    'path' => storage_path('logs/forge-test-branches.log'),
    'days' => 14,
    'level' => env('FORGE_LOG_LEVEL', 'debug'),
],
```

Set `FORGE_LOG_ENABLED=false` to disable logging or `FORGE_LOG_LEVEL=info` to reduce verbosity.

### Database seeding

[](#database-seeding)

```
'deploy' => [
    'seed' => true,
    'seed_class' => 'ReviewSeeder',
],
```

Or via `.env`:

```
FORGE_SEED=true
FORGE_SEED_CLASS=ReviewSeeder
```

Troubleshooting
---------------

[](#troubleshooting)

### "Site creation failed"

[](#site-creation-failed)

Check:

- `FORGE_API_TOKEN` is correct
- `FORGE_SERVER_ID` exists and is accessible
- Base domain is configured in DNS

### "Database creation failed"

[](#database-creation-failed)

Check:

- Server has MySQL/PostgreSQL installed
- Database prefix doesn't conflict with existing databases

### Webhook not working

[](#webhook-not-working)

**1. Check if webhook is being called:**

- In GitLab: Settings &gt; Webhooks &gt; click webhook &gt; "Recent events"
- See if there are requests and what status code was returned

**2. HTTP 401 - Unauthorized:**

- `FORGE_WEBHOOK_SECRET` in `.env` must be EXACTLY the same as configured in GitLab
- Check for whitespace or extra characters
- Or remove secret: leave `FORGE_WEBHOOK_SECRET=` empty in `.env`

**3. HTTP 404 - Not Found:**

- Check if `FORGE_WEBHOOK_ENABLED=true` in `.env`
- Run `php artisan config:clear`
- Run `php artisan route:list | grep webhook` to see if route exists

**4. HTTP 500 - Server Error:**

- Check package logs: `tail -f storage/logs/forge-test-branches-*.log`
- Check application logs: `tail -f storage/logs/laravel.log`

**5. Webhook doesn't trigger when deleting branch:**

- Make sure to check **only** "Push events" in GitLab
- Wait a few seconds after deleting branch
- Check "Recent events" in GitLab to see if webhook was triggered

**6. Manual webhook test:**

```
# Replace values with yours
curl -X POST https://your-app.com/forge-test-branches/webhook \
  -H "X-Gitlab-Event: Push Hook" \
  -H "X-Gitlab-Token: your-secret-token" \
  -H "Content-Type: application/json" \
  -d '{
    "ref": "refs/heads/feat/test-branch",
    "after": "0000000000000000000000000000000000000000"
  }'
```

If it returns `{"message":"Environment not found"}` it's working! (webhook is active, environment just doesn't exist in database)

### SSL not generated

[](#ssl-not-generated)

Certificate is automatically generated after site creation. If it fails:

- Check if domain points to the server
- Wait for DNS propagation (a few minutes)

Testing
-------

[](#testing)

```
composer test
composer test:coverage
composer analyse
```

Changelog
---------

[](#changelog)

See [CHANGELOG](CHANGELOG.md) for recent changes.

Credits
-------

[](#credits)

- [Daniel Neto](https://github.com/danie1net0)
- [All Contributors](../../contributors)

License
-------

[](#license)

MIT License. See [LICENSE.md](LICENSE.md) for more information.

###  Health Score

48

—

FairBetter than 95% of packages

Maintenance89

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity63

Established project with proven stability

 Bus Factor1

Top contributor holds 68.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

25

Last Release

60d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e4706950c5eb49b787a08f7098e9da3c4bb9067f954354e4708a323cd083cbf2?d=identicon)[ddr](/maintainers/ddr)

---

Top Contributors

[![danie1net0](https://avatars.githubusercontent.com/u/19168799?v=4)](https://github.com/danie1net0 "danie1net0 (64 commits)")[![semantic-release-bot](https://avatars.githubusercontent.com/u/32174276?v=4)](https://github.com/semantic-release-bot "semantic-release-bot (27 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

laravelforgegitlab-cireview-appsephemeral-environments

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/ddr-forge-test-branches/health.svg)

```
[![Health](https://phpackages.com/badges/ddr-forge-test-branches/health.svg)](https://phpackages.com/packages/ddr-forge-test-branches)
```

###  Alternatives

[spatie/laravel-horizon-watcher

Automatically restart Horizon when local PHP files change

2631.9M](/packages/spatie-laravel-horizon-watcher)[spatie/laravel-prometheus

Export Laravel metrics to Prometheus

2651.3M6](/packages/spatie-laravel-prometheus)[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[codebar-ag/laravel-docuware

DocuWare integration with Laravel

1221.1k](/packages/codebar-ag-laravel-docuware)[codebar-ag/laravel-zammad

Zammad integration with Laravel

106.1k](/packages/codebar-ag-laravel-zammad)[basillangevin/laravel-data-json-schemas

Transforms Spatie Data objects into JSON Schemas with built-in validation

1312.2k1](/packages/basillangevin-laravel-data-json-schemas)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
