PHPackages                             innocenzi/deployer-recipe-forge - 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. innocenzi/deployer-recipe-forge

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

innocenzi/deployer-recipe-forge
===============================

Seamless zero-downtime deployment on Forge with Deployer

0.3.4(1y ago)1916.6k1MITPHPPHP ^8.2|^8.3

Since Sep 9Pushed 1y ago1 watchersCompare

[ Source](https://github.com/innocenzi/deployer-recipe-forge)[ Packagist](https://packagist.org/packages/innocenzi/deployer-recipe-forge)[ Docs](https://github.com/innocenzi/deployer-recipe-forge)[ RSS](/packages/innocenzi-deployer-recipe-forge/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (2)Versions (17)Used By (0)

Forge recipe for Deployer
-------------------------

[](#forge-recipe-for-deployer)

 [ ![release version](https://camo.githubusercontent.com/a633fbe4af298bcc576d0ecc92be253df71623fc49fdd319b8332de4145ae95e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f696e6e6f63656e7a692f6465706c6f7965722d7265636970652d666f7267653f696e636c7564655f70726572656c6561736573) ](https://packagist.org/packages/innocenzi/deployer-recipe-forge)

 Seamless zero-downtime deployment on Forge with Deployer

 ```
composer require --dev innocenzi/deployer-recipe-forge
```

About
=====

[](#about)

`deployer-recipe-forge` is a recipe for [Deployer](https://deployer.org/) that helps implementing zero-downtime on [Laravel Forge](https://forge.laravel.com/servers).

It uses [Forge's API](https://forge.laravel.com/api-documentation) to fetch a site's credentials, such as its IP address and the remote username, so you don't have to hardcode them in your `deploy.php`. It automatically finds the right Forge site using your repository's name, so the only configuration to do happens on Forge.

Additionally, it's able to send notifications on Slack with either [Deployer's integration](https://deployer.org/docs/7.x/contrib/slack), which requires that you setup a webhook, or by pinging [Forge's deployment URL](https://forge.laravel.com/docs/sites/deployments.html#using-deployment-triggers).

Installation
============

[](#installation)

Install the package as a development dependency:

```
composer require --dev innocenzi/deployer-recipe-forge
```

Then, create a `deploy.php` file at the root of your project. [Import the autoloader](https://github.com/deployphp/deployer/issues/3605), and call `\Deployer\Forge::make()->configure();`. You may then customize your deployment script as usual with Deployer.

```
namespace Deployer;

// This is required
require __DIR__ . '/vendor/autoload.php';
\Deployer\Forge::make()->configure();

// This is your custom deployment script
task('deploy:build', function () {
	  // build assets here
    runLocally('bun i');
    runLocally('bun run build');
});
```

Usage
=====

[](#usage)

Setting up the repository
-------------------------

[](#setting-up-the-repository)

This recipe is designed to be used within a GitHub workflow, using the [action provided by Deployer](https://github.com/deployphp/action).

Setting it up requires at least two [repository secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository):

- Your server's *private* SSH key, which will be used by `deployphp/action` to SSH
- Your [Forge API token](https://forge.laravel.com/docs/accounts/api.html), which will be used by the recipe to fetch your site and server's credentials

These repository secrets must then be forwarded to the action as environment variables, or, for the SSH key, as the `private-key` argument:

```
- uses: deployphp/action@v1
  with:
    private-key: ${{ secrets.PRIVATE_SSH_KEY }}
    dep: deploy
  env:
    FORGE_API_KEY: ${{ secrets.FORGE_API_KEY }}
    REPOSITORY_BRANCH: ${{ github.ref_name }}
    REPOSITORY_NAME: ${{ github.repository }}
```

Warning

Note that the `REPOSITORY_BRANCH` and `REPOSITORY_NAME` variables are required for the recipe to work, since they are used to match your site and branch to your site Forge.

Note

For convenience, if you are using a paid GitHub plan, you may setup your Forge API token and Slack webhook as [organization-wide secrets](https://docs.github.com/en/codespaces/managing-codespaces-for-your-organization/managing-secrets-for-your-repository-and-organization-for-github-codespaces#adding-secrets-for-an-organization).

Setting up Forge
----------------

[](#setting-up-forge)

### Creating a new site

[](#creating-a-new-site)

The following steps explain how to setup a new site on Forge to work with this recipe on GitHub Actions.

1. Create an [isolated](https://forge.laravel.com/docs/sites/user-isolation.html#overview) site.

    > Currently, this recipe only works with isolated Forge sites. You must then create an isolated site.
2. Install the repository using the [GitHub integration](https://forge.laravel.com/docs/sites/the-basics.html#git-repository).

    > You may uncheck "install composer dependencies" to make the installation faster, as the site directory will be erased anyway.
    > If you haven't [associated the server's deploy key with your GitHub repository](https://forge.laravel.com/docs/servers/ssh.html#server-ssh-key-git-project-access), make sure that you do or that you [create a deploy key for this site](https://forge.laravel.com/docs/servers/ssh.html#deploy-keys).
3. If necessary, add the server's public key (`~/.ssh/id_rsa.pub`) to the [repository's deploy keys](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys#deploy-keys).

    > This will allow Deployer to clone the repository from its SSH session on the server.
4. SSH into the server to copy its public key (`~/.ssh/id_rsa.pub`) and add it to the [server's authorized keys](https://forge.laravel.com/docs/accounts/ssh.html#adding-ssh-key-to-existing-servers) on Forge.

    > This is the first step to allow the Deployer action to SSH into the server.
    > The key should be associated to the correct isolated site username.
5. Finally, copy the server's private key (`~/.ssh/id_rsa`) and add it to the [repository's secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).

    > This is the last step to allow the Deployer action to SSH into the server.
    > We suggest naming the secret after the target environment. For instance, if you are creating a staging site, it may be named `STAGING_SSH_KEY`.
6. You may then trigger the deployment workflow.

### Adding to an existing site

[](#adding-to-an-existing-site)

First, make sure the website is isolated. It must be located at `/home//` on the server. Otherwise, you must create a [new isolated site](#creating-a-new-site).

You must first **backup your `.env` file and `storage` directory**, since Deployer will delete the site's directory to replace it with a symlink.

After backing them up, you may follow steps 3 to 6. Once the deployment is complete, you may restore your `.env` and `storage` in `/home//deployer//shared`.

Warning

Proceed with caution. Make sure to put the site on maintenance and think of the potential side-effects that may happen during this migration.

Slack notifications
-------------------

[](#slack-notifications)

### Using Deployer

[](#using-deployer)

This recipe supports sending Slack notifications using a webhook. To create it, you may follow [Slack's documentation](https://api.slack.com/messaging/webhooks) on the topic. You must then add the webhook as a repository or organization secret, and forward it to the action as an environment variable.

```
- uses: deployphp/action@v1
  with:
    private-key: ${{ secrets.PRIVATE_SSH_KEY }}
    dep: deploy
  env:
    # ...
    # Add these two
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    RUNNER_ID: ${{ github.run_id }}
```

Note

Note that `RUNNER_ID` is required, because the Slack notifications link to the associated GitHub workflow.

### Using Forge

[](#using-forge)

Alternatively, you may [set up Slack notifications on Forge](https://forge.laravel.com/docs/sites/deployments.html#slack), and trigger a deployment on Forge. This way, you also get to use Forge's deployment history.

To trigger the deployment on Forge, simply call `triggerDeploymentsOnForge`:

```
\Deployer\Forge::make()
	->triggerDeploymentsOnForge()
	->configure();
```

Warning

When using this strategy, make sure to empty the default deployment script configured by Forge.

Multiple environments
---------------------

[](#multiple-environments)

This recipe supports deploying to multiple environments, such as staging or production, without any specific configuration, other than adding the different private SSH keys to your repository secrets.

It works by associating the repository branch to the one defined on the Forge site. For instance, you may have a `main` branch associated to `example.com`, and a `develop` branch associated to `staging.example.com`.

### For two environments

[](#for-two-environments)

The private SSH key must be provided to the `private-key` argument, for instance using a conditional, as follows:

```
- uses: deployphp/action@v1
  with:
    private-key: ${{ github.ref_name == 'develop' && secrets.STAGING_SSH_KEY || secrets.PRODUCTION_SSH_KEY }}
    dep: deploy
  env:
    # ...
```

### For more environments

[](#for-more-environments)

If you have more than two environments, you may simply duplicate the actions and use an [`if` statement](https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution) to ensure they run for the correct branches:

```
steps:
  - name: Deploy (staging)
    if: github.ref_name == 'develop'
    uses: deployphp/action@v1
    with:
      private-key: ${{ secrets.STAGING_SSH_KEY }}
      dep: deploy
    # ...

  - name: Deploy (production)
    if: github.ref_name == 'main'
    uses: deployphp/action@v1
    with:
      private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
      dep: deploy
    # ...
```

Building on CI or on server
---------------------------

[](#building-on-ci-or-on-server)

By default, the recipe will build assets on CI, and upload them on the server after. To build on the server, call `buildOnServer`:

```
\Deployer\Forge::make()
    ->buildOnServer()
    ->configure();
```

Note that, by default, no `deploy:build` task is provided, and you should write your own. For instance, you may build assets using Bun:

```
\Deployer\Forge::make()->configure();

task('deploy:build', function () {
    runLocally('bun i');
    runLocally('bun run build');
});
```

Obviously, make sure Bun is [installed on CI](https://github.com/oven-sh/setup-bun) before using that example.

Example workflow
----------------

[](#example-workflow)

This workflow deploys on production or staging, depending on the branch that pushed, after running the `test.yml` and `style.yml` workflows. It will skip deployments if the commit body contains `[skip deploy]`, and will notify about deployments on Slack.

Additionally, you may [dispatch the workflow manually](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow).

```
name: Deploy

on:
  workflow_dispatch:
  push:
    branches: [main, develop]

concurrency:
  group: ${{ github.ref }}

jobs:
  test:
    uses: ./.github/workflows/test.yml

  style:
    uses: ./.github/workflows/style.yml

  deploy:
    needs: [test, style]
    if: ${{ !contains(github.event.head_commit.message, '[skip deploy]') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - uses: shivammathur/setup-php@v2
        with:
          php-version: 8.2

      - uses: oven-sh/setup-bun@v1
        with:
          bun-version: latest

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      - uses: deployphp/action@v1
        with:
          private-key: ${{ github.ref_name == 'develop' && secrets.STAGING_SSH_KEY || secrets.PRODUCTION_SSH_KEY }}
          dep: deploy
        env:
          FORGE_API_KEY: ${{ secrets.FORGE_API_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          REPOSITORY_BRANCH: ${{ github.ref_name }}
          REPOSITORY_NAME: ${{ github.repository }}
          RUNNER_ID: ${{ github.run_id }}
```

Using `dep` locally
-------------------

[](#using-dep-locally)

Since the recipe depends on multiple environment variables, you cannot easily call `vendor/bin/dep` locally.

You may work around that limitation by adding `FORGE_API_KEY` to your `.env`, as well as `REPOSITORY_NAME` and `REPOSITORY_BRANCH`:

```
FORGE_API_KEY="abc...123"
REPOSITORY_NAME="yourorg/repo"
REPOSITORY_BRANCH="develop"
```

Then, you may add the following script to `composer.json`, and run `composer dep` instead of `vendor/bin/dep`:

```
"dep": "set -o allexport && source ./.env && set +o allexport && vendor/bin/dep"
```

Warning

Using `dep` locally will create a `.deployer_cache` file, which you should add to `.gitignore`.

Note

If you get denied SSH access, make sure to [add your public key on your server](https://forge.laravel.com/docs/accounts/ssh.html#adding-ssh-key-to-existing-servers) and associate it to your site's user.

Q&amp;A
=======

[](#qa)

**Why not [Envoyer](https://envoyer.io/)?**

> Envoyer's integration with Forge removes the convenience of being able to manage everything within Forge directly, instead forcing us to juggle between two different user interfaces. Our developer experience is better with Deployer and this recipe.

**Why are issues closed?**

> This recipe is mostly used internally at Jetfly, and we are not willing to do support for it nor add many features. Pull requests for fixes and small additions may be welcome, though.

###  Health Score

35

—

LowBetter than 79% of packages

Maintenance32

Infrequent updates — may be unmaintained

Popularity31

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~19 days

Recently: every ~71 days

Total

16

Last Release

684d ago

PHP version history (2 changes)v0.1.0PHP ^8.2

0.3.4PHP ^8.2|^8.3

### Community

Maintainers

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

---

Top Contributors

[![innocenzi](https://avatars.githubusercontent.com/u/16060559?v=4)](https://github.com/innocenzi "innocenzi (44 commits)")

---

Tags

deployerdeploymentlaravellaravel-forgezero-downtimelaraveldeploymentdeployerzero-downtimelaravel-force

###  Code Quality

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/innocenzi-deployer-recipe-forge/health.svg)

```
[![Health](https://phpackages.com/badges/innocenzi-deployer-recipe-forge/health.svg)](https://phpackages.com/packages/innocenzi-deployer-recipe-forge)
```

###  Alternatives

[ngmy/webloyer

Webloyer is a Web UI for managing Deployer deployments

2181.1k](/packages/ngmy-webloyer)

PHPackages © 2026

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