Deploying Laravel from CI with a pipeline token

Deploy a Laravel app from GitHub Actions or GitLab CI with a Brewless pipeline token in BREWLESS_API_TOKEN, after your tests pass.

In this post
  1. How do you deploy Laravel from CI with Brewless?
  2. Making a pipeline token
  3. What the token may do
  4. What the client needs
  5. Run the tests first
  6. GitHub Actions
  7. GitLab CI
  8. When a deploy fails in CI
  9. Revoking a token
  10. Summary

Deploying from your laptop works until two people do it on the same afternoon, or until someone deploys a branch that never passed the tests. Most teams end up wanting the same thing: push to main, run the tests, and deploy only when they pass.

This post sets that up for a Laravel app with Brewless, on GitHub Actions and on GitLab CI, with a pipeline token that can be revoked in one click.

How do you deploy Laravel from CI with Brewless?

Make a pipeline token in the Brewless console, store it in your CI's secrets as BREWLESS_API_TOKEN, install the client with composer global require brewless/cli, and run brewless deploy production --no-interaction. With that variable set, the client needs no sign-in, deploys the commit that is checked out, and fails the pipeline when the release fails.

Making a pipeline token

A pipeline cannot approve a sign-in in a browser, which is how brewless login works on your own machine. It uses a token instead:

  1. In the console, open Account and go to Pipeline tokens.
  2. Name the token after the pipeline that will hold it, such as GitHub Actions shop, and choose Create token.
  3. Copy the token. It starts with bl_ and is shown this once.

Brewless keeps only a keyed hash of the token, so it can check one but never show it again. A token can only be made in the console while you are signed in there; a token cannot make another one.

What the token may do

Acts as You, with your role
Reaches The one organisation you made it in
Expires Never; it works until revoked
Stops working When you revoke it, or when you leave the organisation
Shows up In the audit trail, under your name

A token with your rights can do everything you can, not only deploy. That is why the name matters: when the list on the Account page shows a token that was last used yesterday, you want to know which pipeline holds it.

Warning

Keep the token in your CI's secret store, never in the repository or in a workflow file. Anyone who holds it can deploy and change the organisation as you.

What the pipeline does not need is your application's secrets. The variables of the environment stay in Scaleway Secret Manager in your own project, and the release takes them from there. No .env file goes into CI; see Laravel secrets without a .env file.

What the client needs

The client needs PHP 8.3 or newer and git. Install it with Composer, or download the built brewless file that comes with every release of brewless/cli on GitHub. In a pipeline it does not look for newer versions of itself.

Three behaviours matter in CI:

  • Only committed code is deployed. The client packs the commit that is checked out. Uncommitted files, build output from an earlier step and anything in .gitignore stay behind. The build runs inside your own Scaleway project.
  • --no-interaction asks nothing. Domains in brewless.yml that the environment does not answer on yet are only added with --domains, because adding a domain can move its visitors.
  • The exit code follows the release. The client waits until the release is live or has failed, and exits non-zero on a failure.

The organisation comes from brewless.yml, so the token has to be one of that organisation. When Brewless refuses it, the client says so and the pipeline fails.

Run the tests first

Brewless builds your code and runs your release commands, such as the migrations. It does not run your test suite. Put the tests in a job of their own and make the deploy job depend on it, so a red build never reaches production.

Keep the two jobs apart for a second reason: the test job has your dev dependencies and a test .env; the deploy job needs neither, only PHP, git and the client.

GitHub Actions

Store the token under Settings, Secrets and variables, Actions as BREWLESS_API_TOKEN.

.github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

concurrency:
  group: deploy-production

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
      - run: composer install --no-interaction --prefer-dist
      - run: cp .env.example .env && php artisan key:generate
      - run: php artisan test

  deploy:
    needs: tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
      - run: composer global require brewless/cli
      - run: echo "$(composer global config bin-dir --absolute --quiet)" >> "$GITHUB_PATH"
      - run: brewless deploy production --no-interaction
        env:
          BREWLESS_API_TOKEN: ${{ secrets.BREWLESS_API_TOKEN }}

needs: tests keeps the deploy from starting until the tests pass. The concurrency group lets a second push wait for the first deploy, instead of starting a deploy that Brewless would refuse while one is under way.

GitLab CI

Add BREWLESS_API_TOKEN under Settings, CI/CD, Variables, masked and protected, so only pipelines on protected branches such as main get it.

.gitlab-ci.yml
stages:
  - test
  - deploy

test:
  stage: test
  image: composer:2
  script:
    - composer install --no-interaction --prefer-dist
    - cp .env.example .env
    - php artisan key:generate
    - php artisan test

deploy:
  stage: deploy
  image: composer:2
  resource_group: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  script:
    - composer global require brewless/cli
    - export PATH="$(composer global config bin-dir --absolute --quiet):$PATH"
    - brewless deploy production --no-interaction

The deploy stage only starts when every job in test passed. resource_group: production makes GitLab run one deploy to production at a time. Use an image with the PHP extensions your test suite needs; the deploy job only needs PHP and git.

Tip

Deploy staging from a develop branch with the same job and another environment name. One token covers every environment of the organisation.

When a deploy fails in CI

A failed release never goes live: the release that was serving visitors keeps doing so. The pipeline log shows which step failed. When the build failed, the client prints the last lines of the build log under the error, read from your Scaleway project, so you see why without leaving CI.

Two failures happen before anything is built:

  • Missing variables. When the environment's latest revision lacks a variable Brewless left for you to fill in, the deploy is refused with the missing names. Add them with brewless env:push from your own machine.
  • A refused brewless.yml. A name under variables that looks like a secret, or a resource the file would switch for another, stops the deploy with the line it is about. Run brewless plan production locally to see this before you push.

When a release went live and turns out to be wrong, deploy a fix or roll back; rollbacks in Laravel covers what that does and does not undo.

Revoking a token

Revoke a token on the Account page when a pipeline is retired, when a CI provider reports a leak, or when you are unsure who holds it. It stops working at once. Make a new one, put it in the CI secrets, and the next pipeline run uses it. The full reference is on Deploying from a pipeline.

Summary

  1. Make a pipeline token under Account, Pipeline tokens, and store it in CI as BREWLESS_API_TOKEN.
  2. Run the tests in their own job; let the deploy job depend on it.
  3. Install the client with composer global require brewless/cli and run brewless deploy production --no-interaction.
  4. Let CI run one deploy per environment at a time.
  5. Revoke a token as soon as you are unsure who holds it.

Frequently asked questions

How do I deploy Laravel from GitHub Actions with Brewless?

Make a pipeline token in the Brewless console and store it as the repository secret BREWLESS_API_TOKEN. In the workflow, install the client with composer global require brewless/cli and run brewless deploy production --no-interaction with that secret in the environment.

Does my CI pipeline need my production .env file?

No. The variables of the environment stay in Scaleway Secret Manager in your own project, and the release takes them from there. The pipeline only needs the pipeline token.

What happens when a deploy fails in CI?

The client follows the release until it is live or has failed, and ends with a non-zero exit code when it failed, so the pipeline fails with it. A failed release never goes live; the previous release keeps serving visitors.

Does a pipeline token expire?

No. It works until you revoke it on the Account page, or until its maker is removed from the organisation. The list of tokens shows when each one was last used.

Can two pipelines deploy to the same environment at once?

No. An environment has one release under way at a time, and a second deploy is refused until the first finishes. Let your CI queue deploys per environment so the second run waits instead of failing.

All posts Read this post as markdown

Try it on your own app

Brewless deploys your Laravel app to Scaleway and bunny.net, in your own accounts. We open access to Laravel teams in small batches.