GitHub Packages is a service that hosts software packages, making it easy to share and consume them across your projects.
Let’s see it in action. Imagine you’ve built a reusable Python library, my-awesome-utils, and you want to publish it so other projects can pip install my-awesome-utils.
First, you’ll need to configure pip to look at GitHub Packages. This involves creating or editing your pip.conf (or pip.ini on Windows) file. On Linux/macOS, this is typically in ~/.config/pip/pip.conf. You’ll add a section like this:
[global]
index-url = https://pypi.python.org/simple/
extra-index-url = https://<username>:<token>@<package-type>.pkg.github.com/<owner>/<repo>/simple/
Replace <username> with your GitHub username, <token> with a Personal Access Token (PAT) that has read:packages scope, <package-type> with either nuget, npm, maven, gradle, rubygems, docker, or containers depending on your package type, and <owner> and <repo> with the GitHub owner (user or organization) and repository where your package will be published. The simple/ at the end is crucial for Python package indexing.
To publish your Python package, you’ll use setuptools and twine. Your setup.py (or pyproject.toml) will define your package. After building your package (e.g., python setup.py sdist bdist_wheel), you’ll upload it using twine. The command would look something like this:
twine upload --repository-url https://<package-type>.pkg.github.com/<owner>/<repo>/ dist/*
You’ll be prompted for your GitHub username and the PAT you created earlier.
The real magic happens when you want to consume these packages. If you’re using Python and have configured your pip.conf as shown above, pip install my-awesome-utils will automatically try to fetch it from your GitHub Packages registry if it’s not found on PyPI.
This system is incredibly powerful for internal project dependencies. Instead of managing a private PyPI server or relying on Git submodules for shared code, you can leverage GitHub Packages. It integrates directly with your GitHub workflows, meaning you can set up CI/CD pipelines to automatically build, test, and publish new versions of your packages whenever you merge changes to your main branch.
For example, a GitHub Actions workflow could look like this:
name: Publish Python Package
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Publish to GitHub Packages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -m twine upload --repository-url https://<package-type>.pkg.github.com/<owner>/<repo>/ --username $GITHUB_ACTOR --password $GITHUB_TOKEN dist/*
Here, secrets.GITHUB_TOKEN is a built-in token provided by GitHub Actions that has permissions to push packages to the repository where the action is running. This avoids the need to manually create and manage PATs for your CI/CD.
The most surprising thing about GitHub Packages is how seamlessly it handles different package types. While we’ve focused on Python, the same principles apply to npm packages for Node.js, Maven for Java, Docker images, and more. The underlying mechanism involves specifying the correct registry URL and using authentication tokens, but the commands and tooling vary by ecosystem. For instance, with npm, you’d configure your .npmrc file with registry=https://npm.pkg.github.com/ and publish using npm publish.
A common point of confusion is the difference between publishing to a user-owned repository versus an organization-owned repository. The <owner> in the registry URL can be your username or the name of an organization you belong to. Permissions for accessing and publishing packages are then managed at the repository level, often controlled by branch protection rules and collaboration settings.
Once you’ve mastered publishing and consuming packages, the next logical step is to explore fine-grained access control for your packages, allowing you to restrict which repositories or teams can install specific packages.