Setting up and securing a private Composer repository

Over at Spatie we're doing a lot of free open-source work. Sadly that doesn't pay the bills around here and we did just move to a very fancy new office. That's why, as an effort to build more cool things, we've released four paid products in the past year with a couple more on our roadmap. Some of these paid products like Mailcoach and Media Library Pro include Composer packages to install into your own applications.

In the first of this two-part blog series we'll take a look at how we're distributing these packages by hosting our own private Composer repository using Satis and how you can do that too. Part two on how to secure a Satis server can be read here.

Honourable mentions

As you might know, most Composer packages are hosted on Packagist. It's the default composer package repository and it's also free for open-source composer packages. If you don't want your packages to be open for the world to see and use, Private Packagist is for you. However at €12/year per user it quickly adds up. Even though it's probably the easiest way to get a private package into your customer's vendor directory, it is also the most expensive.

Another easy but lazy solution is to distribute the package code as a ZIP archive straight from the Spatie site. We can even use some clever GitHub actions to auto-publish every new version automatically. However, this is probably the worst solution for developer experience. The customer gets full access to the package code, but there's no easy way for them to install or update it.

Introducing Satis

Satis is an open-source solution for self-hosting composer repositories. In fact, it's more like a cached Composer proxy meets static site generator.

The way it works is by downloading the required packages from any source Composer supports (e.g. GitHub or Packagist) and then re-hosting those packages on your own server.

Additionally, Satis is a static site generator. This means that once it's set-up and Satis has generated the necessary files, we can serve our packages straight from any high performance webserver (like NGINX or S3) without taking a performance hit from running PHP.

Setting up Satis with private repositories

The Satis docs are pretty straight forward: download Satis from GitHub, add your packages to the satis.json config file and run the build command.

Here's a watered-down version of what that satis.json config looks like on

  "name": "spatie/",
  "homepage": "",
  "output-dir": "public",
  "repositories": [
    { "type": "vcs", "url": "" },
    { "type": "vcs", "url": "" }
  "archive": {
    "directory": "dist",
    "skip-dev": false
  "require-all": true

A couple of notes:

Building Satis

Next we'll generate the static Satis files and package archives using the build command:

php bin/satis build

We don't have to specify any other parameters. All necessary configuration options are read from the above satis.json, including the output-dir where our finalised build will end up.

However, you might've noticed that the GitHub repos in our satis.json config above are all private. Locally we might already have access to these private repositories thanks to the GitHub CLI but we'll still need to give our live Satis server access to these private repositories. This can be achieved by generating a GitHub OAuth token (give it repo permissions) and adding that to the ~/.composer/auth.json file on your Satis server. You might need to create the directory and/or file if it doesn't exist yet.

Here's ours:

	"github-oauth": {
		"": "oauth_token_goes_here",

Now, we can finally run php bin/satis satis.json on the production satis server to generate the Satis repository files. If everything goes well, the public/ directory will be created with a couple of interesting files:

Satis UI

Congratulations, we've just proxied packages on our private GitHub repositories to a public Satis server from which they can be installed using composer. However, we're only halfway there: in the next article we'll take a look at securing your precious packages.