Deploying With Private Composer Packages

If you've read anything on Fly.io, you've probably noticed a snippet like this:

We transmogrify Docker containers into lightweight micro-VMs and run them on our own hardware in racks around the world, so your apps can run close to your users.

Fly deploys an application by building it into a Docker image, and then doing a whole lotta stuff to run it as a "for-real" virtual machine.

To deploy an application, all we need to care about is creating a working Docker image.

The Problem

Deploying a Laravel application involves installing Composer dependencies "at build time" - when the Docker image is being built, just before releasing the deployment.

However, installing a private package such as Laravel Nova or Spark will fail unless we provide authentication tokens!

You can set secrets for your application, but these are only available at run-time. They aren't available when building your Docker image. For that, we need build-time secrets.

Authenticating Private Packages

Authenticating Composer packages comes in a few forms.

You can create an auth.json file in your $COMPOSER_HOME path, or perhaps populate a $COMPOSER_AUTH environment variable with some JSON (examples here).

A short-cut to this is running the following command (or something like it, as it varies a bit per package):

# Authenticate Composer for Laravel Nova
composer config http-basic.nova.laravel.com \
    "username@example.org" "some-auth-token"

Let's see how to use this command in our Dockerfile to pull in Laravel Nova.

Secrets in Docker Builds

Our goal is to authenticate private packages within Docker builds, without leaking secrets.

We'll make use of Docker secrets to accomplish this.

You may have used the fly launch command to generate a Dockerfile, or perhaps you created your own. Either way, you should have a Dockerfile already.

To get some secrets into a build, edit the Dockerfile, find the composer install command (it may be composer update), and update it:

# Mount 2 secrets, configure composer, install dependencies
RUN --mount=type=secret,id=nova_user \
    --mount=type=secret,id=nova_pass \
    \
    composer config http-basic.nova.laravel.com \
        "$(cat /run/secrets/nova_user)" "$(cat /run/secrets/nova_pass)" \
    \
    && composer install --optimize-autoloader --no-dev \
    ...

We defined 2 secrets. The secrets are named nova_user and nova_pass. Mounting secrets in Docker creates a file per secret in /run/secrets. These are available only during the building of the container image (e.g. when you run fly deploy).

In our case, we use the secrets to run composer config... and add the authentication details used to install Laravel Nova.

The --mount directive is not a shell command, so there's no need to add && after it as you commonly see when chaining commands.

We can pass the values of those secrets when we run fly deploy:

fly deploy \
    --build-secret nova_user=fideloper@fly.io \
    --build-secret nova_pass=LARAVEL_NOVA_AUTH_TOKEN_HERE 

You should see that the composer install command works - it authenticates and pulls down Laravel Nova!