Local Composer for Everyone! A Conference-Friendly Satis Setup

Bruno Skvorc

While preparing my technical materials for WebSummerCamp, I realized my workshop would rely on a fairly stable internet connection, as we’d have a lot of ground to cover and a lot of packages to install. Rather than rely on the gods of live demos, or pre-installing everything and ruining the experience, I picked another route.

In this post, I’ll show you how to set up a local Satis instance and have it host the packages over the network it’s currently on, so that everyone who’s also connected to it can put the address into composer.json as a custom repository source, and retrieve all packages from your machine locally – no internet connection required!

Composer logo with deal with it sunglasses


Due to a habit of never installing programming-related software on my main OS (more about that here), I’m using Homestead Improved, as usual. If you have a working PHP installation on your host machine, or prefer Docker or something similar, please feel free to use that. Note, however, that for the sake of cross-platform friendliness and simplicity, this tutorial will be Homestead Improved-specific.

Before booting up the VM, make sure you share an arbitrary port. This can be done by editing the bottom section of Homestead.yaml. I picked 6789, so that my local “packagist” will be hosted on IP:6789 where IP will be the IP address of my host machine.


Inside the VM, in an arbitrary location of your choice (I picked /home/vagrant/Code/) install a new Satis project with:

composer create-project composer/satis --stability=dev --keep-vcs

This will create the subfolder satis.

How Satis Works

Satis accepts a satis.json file which tells it which repositories to download, which versions of those repos to download, where to place them after the download, and more. For a more comprehensive overview of all options you can use in satis.json, see the docs.

Let’s create satis.json in the Code/satis folder created earlier. Then, inside satis.json, we place the packages we need. For example:

    "name": "NoFW Websc",
    "homepage": "http://nofw.websc:6789",
    "repositories": [
        { "type": "vcs", "url": "https://github.com/twigphp/Twig" },
        { "type": "vcs", "url": "https://github.com/sitepoint/Rauth" },
        { "type": "vcs", "url": "https://github.com/PHP-DI/PHP-DI" },
        { "type": "vcs", "url": "https://github.com/nikic/FastRoute" },
        { "type": "vcs", "url": "https://github.com/guzzle/guzzle" },
        { "type": "vcs", "url": "https://github.com/Respect/Validation" },
        { "type": "vcs", "url": "https://github.com/doctrine/annotations" },
        { "type": "vcs", "url": "https://github.com/thephpleague/glide" },
        { "type": "vcs", "url": "https://github.com/tamtamchik/simple-flash" },
        { "type": "vcs", "url": "https://github.com/Seldaek/monolog" },
        { "type": "vcs", "url": "https://github.com/cakephp/orm" },
        { "type": "vcs", "url": "https://github.com/Bee-Lab/bowerphp" },
        { "type": "vcs", "url": "https://github.com/markstory/mini-asset" },
        { "type": "vcs", "url": "https://github.com/natxet/CssMin" },
        { "type": "vcs", "url": "https://github.com/linkorb/jsmin-php" },
        { "type": "vcs", "url": "https://github.com/consolidation-org/Robo" },
        { "type": "vcs", "url": "https://github.com/symfony/var-dumper" },
        { "type": "vcs", "url": "https://github.com/consolidation-org/Robo" },
        { "type": "vcs", "url": "https://github.com/twigphp/Twig-extensions" }
    "require-all": true,
    "require-dependencies": true,
    "require-dev-dependencies": true,
    "archive": {
        "directory": "dist"

As you can see, the list is non-trivial. Depending on the conference connection, downloading all this in a room of 50 people would indeed be overly optimistic, especially considering mine is only one of three simultaneous workshops on the same internet connection, not counting the hotel guests. Keep in mind that in order to target VCS repos (they can be of any type), the full URL to their repo is required – that’s why we’re using Github URLs here, and not the package names like we would in a typical require.

The require-* values mean all versions and all their dependencies are to be installed. The archive section makes sure the distributions of the packages are downloaded into dist for full offline access.

Note that I’ve used an actual host name for the URL, not the IP of my machine. I could have used an IP and it would have worked, but only at home – at the conference, the IP of my machine would change, and things would break. You should always define the URL with a host name, rather than an IP.

Once we’ve listed everything we need, we build the local repo with:

php bin/satis build satis.json web/

This will read the satis.json file and save all the packages at all their required versions into web/ (depending on the number of packages and the version required, this can take a while). You’ll likely be asked for a Github token to get past the download limit. If so, just follow the on screen instructions.

Note that the above setup is absolutely brutal in terms of installation times because Satis will download and install all version of all packages, and all their binary distributions. This is handy when altering/developing these packages, but not in everyday use. I recommend you define the exact versions of packages you need, rather than requiring “all”.


Now that the packages have been downloaded, we need to point a server at the web folder, so that it’s accessible via the web. Composer will download packages from it via HTTP, so some basic web hosting needs to be put in place. This doesn’t need to be robust, so a simple PHP built-in webserver instance will do just fine:

cd web
php -S

The server is now live, and the index page should be accessible from the host machine. For example, if you added an etc/hosts entry like nofw.websc (this was my host entry for the conference), you should be able to open it as such:

nofw.websc satis opens

This screen lists all installed packages at their installed versions, and lets you search them.

If you have a phone connected to your wifi, you should be able to access the same screen by using your host machine’s IP address. At the time of writing, my Macbook’s IP address was, so I could access it via

opening the satis webhost on a mobile device

That’s not very useful, though, so let’s try and access it from another machine to make sure it works. I’ll try my Windows 10 laptop:

opened the satis instance in the Edge browser on Windows 10

Sure enough, it works.


Finally, let’s try and pull some of those packages in on the Windows machine. Naturally, I use Vagrant there as well. I’ll assume you’ve also set up a new Homestead Improved instance for trying this out.

Once it’s up and running, we need to modify the etc/hosts file of the VM to include the “address” of our repo – corresponding to the one in satis.json. In my case, that’s: nofw.websc

Then, as per instructions on the home page of our Satis repo, we add the repositories key into the composer.json file of the projects we want to pull from our Satis server:

    "repositories": [
            "type": "composer",
            "url": "http://nofw.websc:6789"
    "config": {
        "secure-http": false

We have to use secure-http: false because we’re serving from a local server, one without an SSL certificate. By default, Composer won’t let us install anything over HTTP.

Now, we can require packages in that project as usual:

composer require twig/twig beelab/bowerphp

Composer should now take into account our new repo address, and pull from there:

Composer pulling from the custom address


This section will list some common problems and their solutions.

Windows won’t open “intranet” (192.168.x.x) sites

The issue can be a WiFi extender which mimics the main network – it replicates the SSID and password to allow for seamless connection throughout the house. The problem is that if the devices aren’t connected to the same device (all on router, or all on extender) this intranet communication won’t work.

To solve the problem, make sure all devices are connected to the same router / extender by, for example, powering off the extender.

Connection refused

Make sure you opened the ports in Homestead.yaml, like so:

     - send: 6789
       to: 6789

If you’re using another Vagrant box which doesn’t have this simple setup procedure, go raw and modify the ports in the Vagrantfile.

Why not use Ngrok or Localtunnel?

Because Ngrok and Localtunnel require internet access, and need a stable connection. They’re just for sharing a local server with the public, not for sharing stuff via WiFi.


In this tutorial, we saw how easy it is to host your own required Composer packages locally, so that everyone in an offline environment, or an environment with an unstable internet connection, can still connect and download all packages. This is useful not only for conferences, but also as a “packagist backup” for companies – no more downtime when Github is down!

Another idea is putting together a small Raspberry Pi box with this exact same setup, and carry it with you. That way, there’s no need to host a heavy Vagrant image, and you still have all your needed packages with you – ready to be shared with anyone connecting to your Wifi. Developing on a train or plane and want to share your work with another tethered colleague? No problem! Just boot up the RasPi hosting your Satis and it’s ready to roll!