Private Composer Repositories with GitLab

Published On19 Aug 2020

GitLab Composer Repositories
GitLab provides free package repositories for package managers of various languages such as NPM, Maven, and Go. The recent GitLab version 13.2 adds support for Composer package registries.

With GitLab, you can now have your own private Composer repository for Git repositories hosted on GitLab. All for free, for those who use the hosted and those who self-host GitLab.

How Composer uses repositories is the default repository for Composer. Unless otherwise disabled, Composer will look up the packages in Anyone with a Packagist account and add a Git repository to Packagist, and with a webhook, Packagist keeps an up to date list of versions available for a given package.

Composer supports adding custom repositories. Composer will lookup packages in the configured repositories as well.

Composer 2 supports fine-tuning custom repositories with filters, canonical repositories, and priorities.

VCS and Path repositories

It is possible to add a local path on disk, or a URL to a Version Control System (such as Git) to the base composer.json file, and Composer will lookup available versions in said paths and URLs.

    "repositories": [
        {"type": "vcs", "url": ""},
        {"type": "path", "url": "../../packages/my-package"}

When a package is requested, Composer now looks up package versions in the configured VCS and path locations.

This is often quite slow, because Composer needs to load a lot of data about the repositories to determine the available versions and check the composer.json file of each version.

Artifacts and individual packages

Composer also provides features to lookup Zip files within a remote location (useful with CI build systems that emit Zip artifacts). It is even possible to declare a package within the composer.json file itself, and point it to a specific URL that Composer will fetch. These ad-hoc packages do not need to contain a composer.json file of its own either.

Artifacts and individual packages are not as used as widely as Composer repositories or VCS/path repositories, and are only mentioned for the sake of completion of this article.

Composer repositories

Composer can work with custom Composer repositories as well. is the default repository, but if as long as the custom repository implements the REST API Composer communicates with, Composer can search and download packages from custom repositories., from the creators of Composer project, is probably the most popular commercial service.

Open source alternatives include Satis from Composer project, and various project-specific repositories.

    "repositories": [
        {"type": "composer", "url": ""}

With a custom composer repo, Composer can query the repo URL to find packages, and call the API once a package is downloaded if the repository is interested in keeping count of package downloads.

Using a composer repository is by far, the fastest and most secure way to host and use private packages because it provides a cleaner API to lookup package information, and granular access to packages and package information.

Private composer repositories from GitLab

GitLab offers free unlimited private repositories and a CI/CD platform in its free plan. The recent addition of package repositories in the free edition now enables Composer users to easily run private Composer repositories.

With a private Composer repository, you can create a collection of PHP Composer packages, and have Composer download them from GitLab package repository. This can provide ease of use with a UI to view packages, and delete them from the UI.

A few things to keep in mind:

  • Packages and package versions must be published via the API. There is no UI to create packages and versions. Packages and their versions can be viewed and deleted from the browser UI.
  • A personal access token is necessary to publish a package.
  • We use GitLab CI to automatically publish new Git tags to the Package registry.
  • A personal access token is required to access the private Composer repository.

1. Create a Project Group

If you do not have one already, create a new project group. All projects published under a project group will be available in the Composer repository.

Once created, you will notice the group is a given a numeric group ID. This ID will be used in the repository URL, making all packages within the group share the same URL.

GitLab group group ID

2. Create a project under the group

With a Project Group created, you can now create a project under the newly created group. If you already have a project group and a project ID, you can skip this step.

3. Enable Package Registry

In project SettingsGeneral page, and under Visibility, project features, permissions, enable "Pipelines" and "Packages" features.

Enable Packages feature in GitLab package registry

4. Create a Personal Access Token to publish

In order to publish packages, a user must be authenticated to the Package Registry. Under a user account that has access to the group, create a Personal Access Token.

Create a Personal Access Token

Enter an appropriate name, optionally an expiration date. Under Scopes, grant api access.

5. Add Personal Access Token as a masked CI variable

In project SettingsCI / CD page, and under Variables, add a new variable. This variable will be used in GitLab CI to publish the package automatically.

Create a Personal Access Token
Make sure to enable "Mask variable". This will hide the token in CI logs. Unless you are using a strict protected branch for releases, or tag patterns, disable the "Protect variable" option.

In this example, we use the key DEPLOY_TOKEN, which we call in the next step.

6. GitLab CI task to automatically publish versions

GitLab Packages features does not automatically publish each Git tag to a version. There is no user-interface to create Composer compatible versions either.

GitLab provides an API endpoint that can be called to publish a package with a specific version or branch. GitLab also provides a free CI/CD platform that can be used to publish new versions of the Composer package to GitLab Package Registry.

If you do not have already, create a new file in Git repository root with name .gitlab-ci.yml (mind the dot at the beginning of the file name) with contents below:

    - tags
  stage: test
    - curl -sS --show-error --fail --data tag=${CI_COMMIT_TAG} "https://__token__:${DEPLOY_TOKEN}${CI_PROJECT_ID}/packages/composer"

Note that this is an overly simplified example, but a slightly more complete GitLab CI .gitlab-ci.yml file would be:

  - test
  - deploy

  stage: test
  image: phpwatch/docker-php:latest
    - php -v
    - composer install --prefer-dist -q --no-progress
    - ./vendor/bin/phpunit -v --coverage-text --colors=never --stderr

    - tags
  stage: deploy
    - curl -sS --show-error --fail --data tag=${CI_COMMIT_TAG} "https://__token__:${DEPLOY_TOKEN}${CI_PROJECT_ID}/packages/composer"

In deloy_composer step, that runs at deploy stage (which means the previous steps such test are successful), we issue a curl call to POST to GitLab packages API to publish the newly created tag. only: [tags] clause ensures that we do not make this API call on other events such as standard commit pushes, merge requests, etc.

${DEPLOY_TOKEN} value is the CI environment variable set at the previous step.

If a valid composer.json file is present, GitLab will create a new package version and return a 201 response. If you use the snippet above, the CI job will only be successful if GitLab accepted the new tag.

7. List published packages

You can view and delete published packages at Packages & RegistriesPackages Registry page.

Packages Registry - Listing

How to use GitLab private repositories

Consuming packages hosted in GitLab private composer repositories is straight forward.

Add private repository as a composer repository

You can add your new private Composer repository to the root composer.json file.

    "repositories": [
            "type": "composer",
            "url": "<GROUP_ID>/-/packages/composer/packages.json"

The <GROUP_ID> value is the Group ID the project belongs to. See the screenshot at step 1: Create a Project Group.

Alternately, you can navigate to any package already published, and instructions to use the package will be shown at the end of the page:

View package

Private repository authentication

Before Composer can consume the new private package repository, it needs to be authenticated.

This is can be done with Basic Authentication supported in Composer.

A Personal Access Token is used for this. See Step 4: Create a Personal Access Token to publish. Ideally, use an access token with read_repository access, but not api access. An api token is read/write and can be used to publish packages.

A Personal Access Token should never be committed to the Git repository. Use the global auth.json, a git-ignored local auth.json file, or the COMPOSER_AUTH environment` variable to inject access credentials.

    "http-basic":  {
        "":  {
            "username":  "___token___",
            "password":  "<ACCESS_TOKEN>"
  • __TOKEN__ is a special username GitLab expects to use in API calls. Do not change this.
  • Replace <ACCESS_TOKEN> with a valid personal access token.

You can create this configuration from command line too:

Per-project configuration (Make sure to add auth.json to .gitignore file):

composer config ___token___ <ACCESS_TOKEN>

Global configuration:

composer g config ___token___ <ACCESS_TOKEN>

Although not recommended, it is possible to embed the credentials to the composer.json file itself too:

    "repositories": [
            "type": "composer",
            "url": "https://___token___:<ACCESS_TOKEN><GROUP_ID>/-/packages/composer/packages.json"
  • Replace <GROUP_ID> with the group ID (from step #1).
  • Replace <ACCESS_TOKEN> with the "personal" access token.

It's highly recommended to not embed the personal access tokens in the composer.json file. It appears that there is a problem with Composer authenticating to GitLab with auth.json credentials. This post will be updated when it is fixed.

GitLab package repositories provide free Composer repositories for organizations, as an alternative to (which offers a lot more features, and also proxies as a repository). It is easy to setup and run compared to a self-hosted option.

Note that under the personal free accounts, there is a limit of 500MB of storage for package registries. This limit is across the whole account. Free for open source packages. Package files themselves are stored in the registry ( Consider using a .gitattributes file with export-ignore rules to exclude unnecessary files from being packaged to the stored zip files.

GitHub also provides package registry features, but they do not support Composer (yet?).

Recent Articles on PHP.Watch

All ArticlesFeed 
How to fix `mysql_native_password` not loaded errors on MySQL 8.4

How to fix mysql_native_password not loaded errors on MySQL 8.4

How to fix the SQLSTATE[HY000] [1524] Plugin 'mysql_native_password' is not loaded errors caused in MySQL 8.4 no longer enabling the mysql_native_password plugin by default.
How to fix PHP Curl HTTPS Certificate Authority issues on Windows

How to fix PHP Curl HTTPS Certificate Authority issues on Windows

On Windows, HTTPS requests made with the Curl extension can fail because Curl has no root certificate list to validate the server certificates. This article discusses the secure and effective solutions, and highlights bad advice that can leave PHP applications insecure.
AEGIS Encryption with PHP Sodium Extension

AEGIS Encryption with PHP Sodium Extension

The Sodium extension in PHP 8.4 now supports AEGIS-128L and AEGIS256 Authenticated Encryption ciphers. They are significantly faster than AES-GCM and CHACHA20-POLY1305. This article benchmarks them and explains how to securely encrypt and decrypt data using AEGIS-128L and AEGIS256 on PHP.
Subscribe to PHP.Watch newsletter for monthly updates

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.