This document describes the full lifecycle of the OntoKit API from local development through release and deployment.
The repository uses two long-lived branches:
| Branch | Purpose |
|---|---|
dev |
Default branch. All feature PRs target dev. Carries the -dev version suffix. |
main |
Release branch. Always reflects the latest tagged release. |
Version is managed in a single source of truth: ontokit/version.py.
VERSION = "0.2.0-dev" # current working version
VERSION_BASE = "0.2.0" # stripped for PyPI / __version__
TAG_NAME = "ontokit-0.2.0" # corresponding git tag- During development the version on
devcarries a-devsuffix (e.g.0.2.0-dev). - At release time the suffix is stripped and the result is merged into
main. pyproject.tomlreads the version dynamically via hatch, so there is nothing else to keep in sync.
cd ontokit-api
uv sync --dev # install all dependencies into .venv
source .venv/bin/activate# Either:
uvicorn ontokit.main:app --reload
# Or via the installed CLI entry point:
ontokit --reloaddocker compose up -d # start all services
docker compose exec api alembic upgrade head # apply migrationsThe development Dockerfile mounts source code as a read-only volume and enables hot reload.
ruff check ontokit/ --fix # lint with auto-fix
ruff format ontokit/ # format code
mypy ontokit/ # type checking (strict mode)
pytest tests/ -v --cov=ontokit # run tests with coveragealembic upgrade head # apply all pending migrations
alembic downgrade -1 # rollback one migration
alembic revision --autogenerate -m "description" # generate a new migrationThe GitHub Actions workflow (.github/workflows/release.yml) runs on every push and on pull requests that touch ontokit-api/.
| Job | What it does |
|---|---|
| lint | ruff check, ruff format --check, mypy |
| test | pytest with coverage |
| build | uv build + twine check --strict on the resulting sdist/wheel |
These three jobs run on pushes to main and dev (and on PRs). The publish jobs described in the next section only run when a release tag is pushed.
Releases follow a Weblate-inspired workflow. All commands below are run from the ontokit-api/ directory.
Create a release branch from dev and run the prepare script:
git checkout dev && git pull
git checkout -b release/0.2.0
python scripts/prepare-release.py
git push -u origin release/0.2.0This script:
- Reads the current version from
ontokit/version.py(e.g.0.2.0-dev). - Strips the
-devsuffix to produce the release version (0.2.0). - Writes the updated version back to
ontokit/version.py. - Creates a git commit:
chore: releasing 0.2.0.
gh pr create --base main --title "Release 0.2.0" \
--body "Merge release 0.2.0 into main for tagging."Wait for CI (lint, test, build) to pass and merge the PR.
git checkout main && git pull
git tag -s ontokit-0.2.0
git push origin --tagsTags must match the pattern ontokit-* to trigger the publish pipeline.
When the tag reaches GitHub, the CI workflow runs the lint/test/build jobs and then three publish jobs in parallel:
- publish_pypi — Uploads the wheel and sdist to PyPI using trusted publishing (
uv publish --trusted-publishing always). Requiresid-token: writepermission for OIDC-based authentication. - publish_github — Creates a GitHub Release with auto-generated release notes and attaches the build artifacts.
- publish_docker — Builds the production Docker image (
Dockerfile.prod) and pushes it to the GitHub Container Registry. The image is tagged with the release version, the major.minor version, andlatest. For example, the tagontokit-0.2.0produces:ghcr.io/<owner>/ontokit:0.2.0ghcr.io/<owner>/ontokit:0.2ghcr.io/<owner>/ontokit:latest
Create a branch from dev, bump the version, and open a PR:
git checkout dev && git pull
git checkout -b chore/next-dev-version
python scripts/set-version.py 0.3.0
git push -u origin chore/next-dev-version
gh pr create --base dev --title "chore: set version to 0.3.0-dev" \
--body "Start the next development cycle."This script:
- Updates
ontokit/version.pyto0.3.0-dev. - Creates a git commit:
chore: setting version to 0.3.0-dev.
Merge the PR to start the next development cycle.
dev dev
│ │
│ release/0.2.0 branch PR → main tag │
│ prepare-release.py (CI gate) (push) │
│ │ │ │ ┌─ PyPI │ chore/next-dev-version
▼ ▼ ▼ ▼ │ │ set-version.py 0.3.0
0.2.0-dev ──▸ 0.2.0 ──PR──▸ main (merge) ──tag──▸ CI ──────┼─ GitHub │ PR → dev
└─ GHCR └── 0.3.0-dev
Patch releases are for backporting critical bug fixes to an older release line after dev has already moved to the next development version. New features always ship in the next minor or major release on dev.
For example, if dev is at 0.3.0-dev but a bug is found in 0.2.0:
git checkout -b release/0.2.x ontokit-0.2.0git cherry-pick <commit-sha> # the fix from devEdit ontokit/version.py to set VERSION = "0.2.1", then commit:
git add ontokit/version.py
git commit -m "chore: releasing 0.2.1"git tag -s ontokit-0.2.1
git push -u origin release/0.2.x && git push --tagsCI will run the lint/test/build checks and publish to PyPI, GitHub Releases, and GHCR as usual. The release/0.2.x branch can be kept for future patches on the same line.
Note: The
latestDocker tag will point to the patch release. If the latest minor/major release should remainlatest, manually retag after publishing.
There are three ways to deploy the OntoKit API, depending on your needs.
Install the published package and run via the CLI entry point:
pip install ontokit # or: uv pip install ontokit
ontokit # starts uvicorn on 0.0.0.0:8000Pull the pre-built production image published by CI:
# latest release
docker pull ghcr.io/<owner>/ontokit:latest
# specific version
docker pull ghcr.io/<owner>/ontokit:0.2.0Run it directly:
docker run -d \
--name ontokit-api \
-p 8000:8000 \
-e DATABASE_URL=postgresql+asyncpg://... \
-e REDIS_URL=redis://redis:6379/0 \
ghcr.io/<owner>/ontokit:0.2.0Or reference it in a compose file instead of building locally:
services:
api:
image: ghcr.io/<owner>/ontokit:0.2.0
# ...Build the production image from source and run with compose:
docker compose -f compose.prod.yaml up -dDevelopment (Dockerfile) |
Production (Dockerfile.prod) |
|
|---|---|---|
| System deps | git, curl, libgit2-dev | curl only |
| Git repos dir | /data/repos (created + chown) |
not created |
| Source mount | read-only bind mount | baked into image |
| uvicorn | --reload |
--workers 4 |
- Run migrations —
alembic upgrade headinside the container. - Health check —
GET /healthreturns{"status": "healthy"}. - Verify version —
GET /returns the deployed version string.
The API requires the following environment variables (see .env.example for defaults):
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string (postgresql+asyncpg://...) |
REDIS_URL |
Redis connection string |
MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY |
Object storage |
ZITADEL_ISSUER, ZITADEL_CLIENT_ID, ZITADEL_CLIENT_SECRET |
OIDC auth |
GIT_REPOS_BASE_PATH |
Path for bare git repositories (default /data/repos) |
GITHUB_TOKEN_ENCRYPTION_KEY |
Encryption key for stored GitHub PATs |
SUPERADMIN_USER_IDS |
Comma-separated Zitadel user IDs with full access |