Skip to main content
MeteorGPLby WebbingApps

Guide

Composer tokens in CI: GitHub Actions and GitLab CI

Give your pipeline a scoped Composer token through COMPOSER_AUTH, cache the downloads, and report the lock file to your dependency dashboard, without the token ever touching the repository.

Composer tokens in CI: GitHub Actions and GitLab CI

About this guide

Difficulty
Intermediate
Time to complete
20 minutes
Reading time
4 min read

Prerequisites

  • A project whose composer.json lists the MeteorGPL repository
  • Permission to add secrets (GitHub) or CI/CD variables (GitLab) to the repository
  • For a team, an organisation on MeteorGPL, so the token does not belong to one person
In this guide
  1. Step 1 — Create a token for the pipeline
  2. Step 2 — Save it as a secret
  3. Step 3 — GitHub Actions
  4. Step 4 — GitLab CI
  5. Step 5 — Report the lock file (optional)
  6. Keep the token out of sight
  7. Troubleshooting

A pipeline has to install packages without anyone typing credentials, and without the token ending up in the repository or in a build log. Composer reads credentials from the COMPOSER_AUTH environment variable, and CI systems hand secrets to jobs as environment variables. This guide connects the two, and limits what a leaked token could do.

Step 1 — Create a token for the pipeline

For a team project, create an organisation token (your organisation → Composer tokens): it belongs to the team and keeps working when people leave. For a personal project, a personal token (Settings → Composer tokens) is enough.

Limit the token when you create it:

  • Packages — only the Composer names the project uses. A pattern such as meteorgpl-plugin/* allows every plugin.
  • Networks — only the IP ranges your runners use. This suits self-hosted runners with fixed addresses; leave it empty for hosted runners whose addresses change.
  • Expiry — the date the token stops working. You are notified when one of your tokens expires within seven days.

Copy the token when it is shown: it is shown once.

Step 2 — Save it as a secret

Name the secret METEORGPL_TOKEN in both systems:

  • GitHub: the repository's Settings → Secrets and variables → Actions → New repository secret.
  • GitLab: the project's Settings → CI/CD → Variables. Mark it masked, and protected if only protected branches install packages.

Step 3 — GitHub Actions

COMPOSER_AUTH holds the same JSON as an auth.json file. The user name is the literal word token; the password comes from the secret:

name: Build

on: [push]

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

      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'

      - name: Composer cache directory
        id: composer-cache
        run: echo "dir=$(composer config cache-dir)" >> "$GITHUB_OUTPUT"

      - uses: actions/cache@v4
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: composer-${{ hashFiles('composer.lock') }}
          restore-keys: composer-

      - name: Install PHP dependencies
        env:
          COMPOSER_AUTH: '{"http-basic":{"composer.meteorgpl.ddev.site":{"username":"token","password":"${{ secrets.METEORGPL_TOKEN }}"}}}'
        run: composer install --no-interaction --prefer-dist

Dists never change once published, so the cache is safe to keep between runs; it is keyed on composer.lock, so a new lock file starts a fresh one.

Step 4 — GitLab CI

GitLab expands $METEORGPL_TOKEN inside the variable's value. GitLab only caches paths inside the project directory, so point Composer's cache there:

install:
  image: composer:2
  variables:
    COMPOSER_AUTH: '{"http-basic":{"composer.meteorgpl.ddev.site":{"username":"token","password":"$METEORGPL_TOKEN"}}}'
    COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache"
  cache:
    key:
      files:
        - composer.lock
    paths:
      - .composer-cache/
  script:
    - composer install --no-interaction --prefer-dist

Bitbucket Pipelines and CircleCI work the same way; the CI documentation has their snippets.

Step 5 — Report the lock file (optional)

The dependency dashboard compares a site's composer.lock with the repository and shows each package as up to date, behind (with the changelog entries you are missing), withdrawn or taken down. Create a project under Settings → Projects (or your organisation's Projects), then let the pipeline upload the lock file after each build of the main branch:

      - name: Report composer.lock to the dependency dashboard
        if: github.ref == 'refs/heads/main'
        run: curl -fsS -u "token:${{ secrets.METEORGPL_TOKEN }}" -F [email protected] https://meteorgpl.ddev.site/api/projects/<project-id>/lock

The token must belong to the account or the organisation that owns the project.

Keep the token out of sight

  • Never print COMPOSER_AUTH or the secret. GitHub masks secrets in logs and GitLab masks masked variables, but only in the forms they recognise.
  • Never commit an auth.json. With COMPOSER_AUTH there is no file to commit.
  • Rotate without downtime: create the new token, update the secret, run the pipeline, revoke the old token.
  • A token used from many different networks in one day is treated as shared: you are told, and a clearly leaked token is revoked automatically.

Troubleshooting

  • 401 Unauthorized — the secret is missing, mistyped, expired or revoked. On GitHub, workflows started by pull requests from forks do not receive repository secrets.
  • 403 Forbidden — the token is valid, but the account or organisation it belongs to has no repository access, or the token's package or network limits exclude this request.
  • Composer asks for a user name — the host in COMPOSER_AUTH must be exactly composer.meteorgpl.ddev.site: no https://, no path.

Send feedback

What works, what does not, what is missing: a few words help.

Only if you would like a reply.

Taken in your browser, without this window. Passwords, what you typed into forms, keys and tokens are left out as grey blocks; you can hide more before sending. We keep screenshots for 90 days.

We store it with the page address, your language, window size, browser and IP address, and your e-mail address if you give one. The IP address and browser are deleted after 90 days. Privacy policy.

All languages