Set up a private packagist using a server and open source

Publishing private composer packages is a fiddly business - especially if you want a usable UI along with it.

After much research I came across Packeton - an open source fork of Packagist which you can run on a web server of your choosing.

This walkthrough sets Packeton up with Docker which requires the least amount of server setup.

The following how-to runs through setting it up and some hurdles I came across. It expects CLI experience and you need to be comfortable with SSH.

Where to run

You need a server or VPS for this - I opted for a cloud server from Hetzner with Ubuntu 24 running.

Server set up

Update the server applications and install caddy (which allows web traffic to docker images) and docker itself.

apt update && apt upgrade -y
apt install -y caddy
curl -fsSL https://get.docker.com | sh

DNS

Point your domain (e.g. packages.yourdomain.com) at the server's public IP

Firewall

Set up a firewall with the following inbound rules - I used the firewall built into the Hetzner control panel

Port Protocol Source
22 TCP Any IPv4, Any IPv6
80 TCP Any IPv4, Any IPv6
443 TCP Any IPv4, Any IPv6

Generate an app secret

This can be run on the server or your local machine - you just need a 32 character string

openssl rand -hex 32

Copy the output for use in the next step. Keep it static — it's used to encrypt SSH keys in the database.

Create your Docker compose file

I chose to keep all my Packeton-related files in /opt/packeton. Start off by making the folder & file

mkdir -p /opt/packeton
nano /opt/packeton/docker-compose.yml

This utilises a few different settings & configuration. Some points worth noting

  • This include configuration for using Mailgun (we use it on the free tier) for sending the password reset emails
  • This includes watchtower which will keep packeton updated
services:
  packeton:
    image: packeton/packeton:latest
    container_name: packeton
    restart: unless-stopped
    environment:
      APP_SECRET: <output from step 5>
      ADMIN_USER: admin
      ADMIN_PASSWORD: changeme
      ADMIN_EMAIL: [email protected]
      PACKAGIST_DIST_HOST: https://packages.yourdomain.com
      TRUSTED_PROXIES: 127.0.0.1
      MAILER_DSN: smtp://you%40yourdomain.com:[email protected]:587
      MAILER_FROM: Your Name <[email protected]>
    ports:
      - '127.0.0.1:8080:80'
    volumes:
      - ./data:/data
  watchtower:
    image: containrrr/watchtower
    restart: unless-stopped
    environment:
      DOCKER_API_VERSION: "1.40"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --interval 86400
Note: ADMIN_USER and ADMIN_PASSWORD only apply on first run. Change the password afterwards via the console (see Post-setup).
Note: If using Mailgun, make sure you use the EU SMTP host (smtp.eu.mailgun.org) if your domain is on the EU region. Use the full email address as the SMTP username, URL-encoding the @ as %40.

Configure Caddy

Caddy allows a domain name to be forwarded to a running docker container.

Replace the entire contents of /etc/caddy/Caddyfile with:

packages.yourdomain.com {
    reverse_proxy localhost:8080
}

Then reload caddy:

systemctl reload caddy

Start Packeton

cd /opt/packeton
docker compose up -d

Verify it all works

Visit https://packages.yourdomain.com and log in with the admin credentials you set.

Post-setup

Change the admin password

The ADMIN_PASSWORD env var only applies on first run. Change it via the console:

docker exec -it packeton bin/console packagist:user:manager admin --password=newpassword

Create additional admin users

docker exec -it packeton bin/console packagist:user:manager newusername --password=newpassword --admin --no-interaction

Configure GitLab OAuth

First, create a GitLab OAuth application at https://gitlab.com/-/profile/applications:

  • Redirect URIs:
    https://packages.yourdomain.com/oauth2/gitlab/install
    https://packages.yourdomain.com/oauth2/gitlab/check
    
  • Scopes: api, read_user, read_repository

Then create /opt/packeton/data/config.yaml:

packeton:
    integrations:
        gitlab:
            base_url: 'https://gitlab.com/'
            clone_preference: 'clone_https'
            gitlab:
                client_id: 'xxx'
                client_secret: 'xxx'

Restart Packeton to apply:

cd /opt/packeton
docker compose restart packeton

Then go to the Packeton integrations page in the UI and click Install Integration, then Connect to complete the OAuth flow.

Bonus Notes

Data

All Packeton data lives in /opt/packeton/data on the host, mapped to /data inside the container. Back this directory up — it contains the database, config, and any stored artifacts.

Ongoing maintenance

Watchtower checks for a new packeton/packeton:latest image daily and recreates the container automatically. No action needed.

Monitor the Packeton releases for any updates that require manual migration steps before they land.

CLI

View this post on Github

You might also enjoy…

Mike Street

Written by Mike Street

Mike is a CTO and Lead Developer from Brighton, UK. He spends his time writing, cycling and coding. You can find Mike on Mastodon.