Building a Django Package — Part 4: Publishing django-healthkit to PyPI
In this final part, we prepare django-healthkit for release, build and validate the package, test it on TestPyPI, publish it to PyPI, and create a Git tag and GitHub release.
Django
Packages
OpenSource
Python
Aug 26, 2026 · 8 min read
In the previous parts, we built the core functionality of django-healthkit, implemented database and cache health checks, and exposed them through a reusable health endpoint. Now the package is ready to become a real Python package that other developers can install and use.
In this part, we will focus on the complete publishing workflow: building the package, validating the distribution files, testing on TestPyPI, creating a PyPI API token, publishing the package to PyPI, and finally creating a Git tag and GitHub release.
The complete workflow will look like this:
Build
↓
Validate
↓
TestPyPI
↓
PyPI
↓
Git Tag
↓
GitHub Release
Preparing the Release
Before publishing the package, we need to make sure the package metadata and version are finalized.
For example:
[project]
name = "django-healthkit"
version = "0.1.0"
The version is important because every published version on PyPI should be unique. If we discover a problem after publishing 0.1.0, we should fix it and release a new version such as 0.1.1 instead of trying to overwrite the existing release.
Building the Package
Python packages are normally distributed as both source distributions and wheels.
We can install the build tool with:
python -m pip install --upgrade build
Then build the package:
python -m build
This creates a dist/ directory containing files similar to:
dist/
├── django_healthkit-0.1.0-py3-none-any.whl
└── django_healthkit-0.1.0.tar.gz
The .whl file is the wheel distribution, while the .tar.gz file is the source distribution.
These are the actual artifacts that will be uploaded to the package index.
Validating the Distribution
Before uploading anything, it is a good idea to validate the generated distributions.
We can install Twine with:
python -m pip install --upgrade twine
Then run:
twine check dist/*
A successful validation should look similar to:
Checking django_healthkit-0.1.0-py3-none-any.whl: PASSED
Checking django_healthkit-0.1.0.tar.gz: PASSED
This helps catch common packaging and metadata problems before publishing the package.
Testing with TestPyPI
Before publishing to the real Python Package Index, we can test the entire publishing workflow with TestPyPI.
TestPyPI is a separate package index designed for testing package publishing and installation.
We first need to create a TestPyPI account and generate an API token. TestPyPI has its own accounts and tokens, separate from the main PyPI service.
Once we have the token, we can upload the package:
twine upload --repository testpypi dist/*
When using an API token, the username is:
__token__
and the password is the API token itself.
Installing from TestPyPI
After uploading the package, we should test it exactly as a real user would install it:
pip install --index-url https://test.pypi.org/simple/ django-healthkit
This is an important step because we are no longer importing the package directly from our local source code. We are testing the actual distribution that was built and uploaded.
We can now verify that the package installs correctly and that the imports, metadata, dependencies, and Django integration work as expected.
Creating a PyPI API Token
Once the package has been successfully tested on TestPyPI, we can prepare the real PyPI release.
PyPI supports API tokens for package publishing. We can create a token from the account settings and use it with Twine.
When authenticating with a PyPI API token, the username is:
__token__
and the password is the generated API token.
The token should never be committed to Git, added to the repository, or hard-coded inside the project.
It is also a good practice to create the token with the smallest scope required for the job.
Publishing to PyPI
After the TestPyPI package has been verified, we can publish the distribution files to the real PyPI repository:
twine upload dist/*
Once the upload succeeds, django-healthkit becomes available through the Python Package Index.
Developers can then install it normally with:
pip install django-healthkit
At this point, the project is no longer just a GitHub repository. It has become a real Python package that can be installed by any compatible Django project.
Storing Credentials Securely
If we publish packages manually multiple times, entering the API token every time can become inconvenient.
Tools such as keyring can be used to store credentials securely.
For PyPI:
keyring set https://upload.pypi.org/legacy/ __token__
For TestPyPI:
keyring set https://test.pypi.org/legacy/ __token__
For CI/CD, however, there is an even better approach: Trusted Publishing.
Git Tag
After publishing version 0.1.0, we should create a Git tag that points to the exact source code associated with the release.
git add .
git commit -m "release: v0.1.0"
git tag v0.1.0
git push origin main
git push origin v0.1.0
Now the repository has a permanent reference to the source code used for version 0.1.0.
This becomes especially useful when debugging old releases or investigating issues reported against a specific package version.
Creating a GitHub Release
The next step is to create a GitHub Release based on the v0.1.0 tag.
For the first release, the release notes can contain a short summary such as:
Initial release of django-healthkit.
Features:
- Database health check
- Cache health check
- Health check manager
- Health endpoint
- Check latency measurement
- Configurable health checks
GitHub Releases and PyPI serve different purposes.
GitHub is where we manage the source code, tags, and release notes, while PyPI is responsible for distributing the Python package.
GitHub Release
↓
Source Code + Release Notes
PyPI
↓
Python Package Distribution
Automating the Release
Manual publishing works well for the first release, but it is not ideal as a long-term workflow.
For future releases, we can automate the process with GitHub Actions.
PyPI supports a mechanism called Trusted Publishing that uses OpenID Connect (OIDC) to authenticate supported CI/CD workflows.
Instead of storing a long-lived PyPI API token in GitHub Secrets, GitHub Actions can authenticate directly with PyPI through an established trusted relationship.
A simplified workflow can look like this:
name: Publish
on:
release:
types:
- published
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v6
with:
python-version: "3.x"
- name: Install build
run: python -m pip install --upgrade build
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
The important part is:
permissions:
id-token: write
This allows GitHub Actions to obtain an OIDC identity token that PyPI can use for authentication.
The release workflow can then become:
Create GitHub Release
↓
GitHub Actions
↓
Build Package
↓
PyPI Trusted Publishing
↓
Published Package
This removes the need to store a long-lived PyPI API token in the GitHub repository.
The Complete Release Workflow
The complete django-healthkit release lifecycle now looks like this:
Development
↓
Write Health Checks
↓
Run Tests
↓
Build Distribution
↓
twine check
↓
TestPyPI
↓
Test Installation
↓
PyPI
↓
Git Tag
↓
GitHub Release
From the user's perspective, all of this eventually becomes a single command:
pip install django-healthkit
That is the point where a local reusable component becomes a real Python package.
What We Built
Across these four parts, we started with an empty package and gradually turned it into a usable Django library.
We started by creating the package structure and packaging configuration.
Then we implemented independent health checks for Django's database and cache.
After that, we introduced the health check manager, latency measurement, configuration, and HTTP endpoint.
Finally, we built and validated the package, tested the distribution on TestPyPI, published it to PyPI, and created the corresponding Git tag and GitHub release.
The architecture now looks like this:
django-healthkit
│
▼
Health Check Manager
│
┌────────────┼────────────┐
▼ ▼ ▼
Database Cache Future
Check Check Checks
│ │
└────────────┴────────────┐
▼
Health Result
│
▼
Health Endpoint
│
▼
Django App
And because the package is now published to PyPI, any compatible Django application can install it without needing to know anything about its internal implementation.
Final Thoughts
Building a Python package is more than writing reusable code.
The code is only one part of the process.
A production-ready package also needs clear metadata, versioning, tests, build artifacts, distribution validation, a secure publishing process, release tags, documentation, and a reproducible release workflow.
django-healthkit started as a small health-check implementation for a real project. Instead of keeping that code inside a single Django application, we separated it into a reusable package that can now be installed and used by other Django projects.
And that is the real value of turning internal code into an open-source package.
pip install django-healthkit
From a few health checks to a package that anyone can install.
That's the complete journey of building django-healthkit.
Related Notes
Building a Django Package — Part 3: Health Check Manager and Endpoint
In Part 3, we connect the database and cache health checks, build the health check manager, expose a Django health endpoint, measure check latency, and return a structured health status.
Building a Django Package — Part 2: Database and Cache Health Checks
In this part, we implement the first health checks for django-healthkit, covering database connectivity and Django cache functionality with simple, independent, and testable checks.
Building a Django Package — Part 1: Setting Up django-healthkit
In this part, we build the initial structure of django-healthkit, configure the package with pyproject.toml, and prepare it for development.
Building a Secure Webhook Receiver for Server-to-Server Communication
How I built a secure FastAPI webhook receiver using RSA signatures to enable authenticated server-to-server communication, proxy requests, and connect applications across different network environments.
Why I Switched to Conventional Git Commit Messages
Why I switched from inconsistent Git commit messages to a Conventional Commits style, with practical examples for cleaner and more maintainable Git history.
Building django-healthkit
How a simple health endpoint for Bidar turned into django-healthkit, a lightweight Django health-check package.
Designing a Gold Jewelry E-Commerce Database with Django
Learn how to design a scalable Django database for a gold jewelry e-commerce platform by modeling products, attributes, and purchasable product variants using real-world domain-driven design principles.
How to Fix Common Next.js 16 Build Errors (Proxy, Dynamic Rendering & Revalidation)
Learn how to fix common Next.js 16 build errors, understand Proxy, Dynamic Rendering, and Revalidation, and improve your application's SEO and performance.
Why I Built cyber-ui: A Minimal Design System for Developers
How my projects, from backend systems to monitoring tools, led me to create a minimal and personal UI foundation.