From messy commits to clean releases in one command.
pyforge-deploy is now focused on a single, opinionated flow:
pyforge releaseIt analyzes commits, suggests the next version, generates a clean changelog, asks for confirmation, and finalizes the release.
Release automation tools often become noisy and over-configured.
pyforge-deploy now optimizes for one thing: a reliable release experience with sensible defaults.
read commits manually
guess bump level
write changelog by hand
tag and publish with multiple commands
pyforge release
- Detect latest tag (or initial release)
- Collect commits since the last release
- Analyze commits (Conventional → heuristic → AI fallback)
- Suggest next version
- Generate changelog preview
- Ask for confirmation
- Update version + changelog + git commit/tag
- Publish via CI by default (local publish is optional)
Release intelligence highlights in the current codebase:
- Pre-1.0 SemVer scaling: when current version is
0.x.y, breaking-change signals are downgraded from MAJOR to MINOR to match SemVer initial-development expectations. - Schema migration risk detection: migration paths (
migrations/,alembic/,versions/) are recognized, and destructive operations (for exampleDROP TABLE,op.drop_column) add high-impact risk scoring. - Security hotfix override: commits mentioning CVE/GHSA/security/hotfix terms receive dominant PATCH confidence to avoid low-confidence interactive blocking.
- Deterministic historical density scoring: file-size denominator is resolved from git blobs at the analyzed commit revision (not the live working tree).
- Timeout-safe git collection: per-commit changed-file and diff collection now fails closed on subprocess timeout/errors instead of crashing release planning.
Install from PyPI:
pip install pyforge-deployDocker must be installed and running for Docker-related features.
pyforge releaseExample output:
Analyzing commits since v1.2.0...
Detected changes:
- feat: add authentication -> MINOR
- fix: login bug -> PATCH
Suggested version: 1.3.0
Generated changelog:
---
## v1.3.0
- Added
- add authentication
- Fixed
- login bug
---
Continue? (y/n)
Use pyforge release for normal releases.
Legacy commands still work, but are deprecated and now considered advanced usage.
Generate a CI/CD workflow file in your repository:
pyforge-deploy initThis creates:
.github/workflows/pyforge-deploy.yml
Automatically detect project dependencies and build an image.
pyforge-deploy docker-buildSpecify entry point and image tag:
pyforge-deploy docker-build \
--entry-point src/pyforge_deploy/cli.py \
--image-tag my-app:1.0.0Build and publish a release.
Bump patch version automatically:
pyforge-deploy deploy-pypi --bump shameUse Pride-style stable bumps:
pyforge-deploy deploy-pypi --bump default
pyforge-deploy deploy-pypi --bump proudPublish a specific version to TestPyPI:
pyforge-deploy deploy-pypi --version 2.1.0 --testView detected dependencies:
pyforge-deploy show-depsCheck current project version:
pyforge-deploy show-versionCheck release readiness:
pyforge-deploy statusSee auto-detected entry point candidates:
pyforge-deploy show-entry-pointGenerate release changelog intelligence (dry-run preview):
pyforge-deploy release --dry-runGenerate release changelog and trigger CI-managed release publish:
pyforge-deploy releaseRelease command behavior (default):
- Generates/updates
CHANGELOG.mdwith the new release section - Verifies changelog exists in the project root
- Finalizes release git operations (commit changelog + push canonical tag
vX.Y.Z) - Exits after handing off publish work to the tag-triggered GitHub Actions workflow
Local publish mode (explicit opt-in):
pyforge-deploy release --local-publishWhen --local-publish is enabled, the same command also performs:
- PyPI build + publish
- Docker build + push
- GitHub Release publication from the matching changelog section
If GITHUB_TOKEN/GH_TOKEN is unavailable locally, GitHub Release publishing is skipped.
If you intentionally need to release from a non-clean working tree:
pyforge-deploy release --allow-dirtyEquivalent environment override:
PYFORGE_RELEASE_ALLOW_DIRTY=1
pyforge-deploy prefers GitHub OIDC (Passwordless / Trusted Publishing) in CI
environments: when running inside GitHub Actions with id-token: write
permissions, the action can mint short-lived PyPI tokens so you do NOT need to
store PYPI_TOKEN as a repository secret. This is the recommended and secure
default for automated releases.
Locally (or outside OIDC-capable CI) you may still provide a static token. To
use a token locally, set it via a .env file or environment variable:
PYPI_TOKEN=pypi-your-token-here
Use PYPI_TOKEN only for local/manual runs; in CI prefer OIDC/trusted
publishing so secrets are not stored long-term.
pyforge-deploy reads settings from the [tool.pyforge-deploy] table in
pyproject.toml. CLI arguments override values in pyproject.toml, which in
turn override environment variables and built-in defaults. Example configuration:
[tool.pyforge-deploy]
default_bump = "shame" # default bump when releasing
docker_push = true # whether docker-build should push by default
docker_platforms = "linux/amd64" # platforms for buildx (comma-separated)
auto_confirm = true # skip interactive prompts
docker_image = "myorg/myapp:latest" # default image tag
docker_python = "3.12" # override python base image (short form '3.12')
docker_wheelhouse = false # build a local wheelhouse for Docker builds
docker_non_root = false # install into non-root user in final image
pypi_retries = 3 # upload retry attempts
pypi_backoff = 2 # backoff base seconds for retries
plugin_timeout = 300 # per-hook command timeout in seconds
[tool.pyforge-deploy.changelog]
custom_prompt = "Generate release notes in English with concise bullets." # optional
[tool.pyforge-deploy.plugins]
# release/build hooks (string or list of strings)
before_release = [
"ruff check .",
"pytest -q",
]
after_release = [
"echo Release complete",
]
before_build = [
"python -m pip check",
]
after_build = []Set one or more of the following keys (first match is used):
OPENAI_API_KEYANTHROPIC_API_KEYGEMINI_API_KEY
Optional overrides:
OPENAI_BASE_URL(OpenAI-compatible local/self-hosted endpoint)OPENAI_MODEL(default:gpt-4o-mini)ANTHROPIC_MODEL(default:claude-3-5-haiku-latest)PYFORGE_AI_PROVIDER(force provider selection)PYFORGE_AI_API_KEY(shared key for selected provider)PYFORGE_AI_BASE_URL(preferred OpenAI-compatible base URL override)
Not all keys are required — the CLI will fall back to sensible defaults when a
setting is omitted. See src/pyforge_deploy/builders for how each option is
used at runtime.
Hook execution is best-effort by design:
- Commands run with shell execution.
- Non-zero exit, missing executable, and timeout errors are logged as warnings.
- Main Docker/PyPI pipeline continues (does not crash).
- Successful hook output is shown in verbose mode; failure output is shown to aid debugging.
Hook context environment variables are injected for each command:
PYFORGE_HOOK_STAGEPYFORGE_HOOK_COMMAND_INDEXPYFORGE_HOOK_COMMAND
CI hook timeout can be overridden with:
PYFORGE_PLUGIN_TIMEOUT_SECONDS
[tool.pyforge-deploy.plugins]
before_release = [
"ruff check .",
"pytest -q",
][tool.pyforge-deploy.plugins]
before_release = [
"bandit -r src/ --exclude tests/",
][tool.pyforge-deploy.plugins]
before_build = [
"mypy src/",
"python -m pip check",
][tool.pyforge-deploy.plugins]
after_release = [
"echo '[pyforge] release completed'",
][tool.pyforge-deploy.plugins]
pre_build = ["ruff check ."]
post_deploy = ["echo done"]Tip: prefer canonical stage names (before_build, after_build,
before_release, after_release) for new projects.
pyforge-deploy includes a reusable GitHub Action for automated releases.
After running:
pyforge-deploy initA workflow file will be generated.
Example workflow (OIDC-enabled template produced by pyforge-deploy init):
name: PyForge Release
on:
push:
tags:
- 'v*'
- '[0-9]*.[0-9]*.[0-9]*'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
id-token: write
jobs:
deploy_pypi:
name: Deploy / PyPI
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v5
- name: PyForge / PyPI Deploy
uses: ertanturk/pyforge-deploy@main
with:
pypi_deploy: 'true'
docker_build: 'false'
bump: 'shame'
plugin_timeout_seconds: '300'
target_branch: ${{ github.event.repository.default_branch }}
env:
PYFORGE_JSON_LOGS: '1'
deploy_docker:
name: Deploy / Docker
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v5
- name: PyForge / Docker Deploy
uses: ertanturk/pyforge-deploy@main
with:
pypi_deploy: 'false'
docker_build: 'true'
docker_platforms: 'linux/amd64,linux/arm64'
plugin_timeout_seconds: '300'
target_branch: ${{ github.event.repository.default_branch }}
env:
PYFORGE_JSON_LOGS: '1'
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}This template enables GitHub OIDC (id-token: write) so PyPI tokens can be
minted dynamically during the workflow. You only need to provide Docker
credentials as secrets if you build/push images.
Quality/security/lint/test steps are intentionally plugin-driven. Add them under
[tool.pyforge-deploy.plugins] in your project and run them in categorized
hook stages (for example in before_release or before_build).
The tool is structured into modular components.
Responsible for resolving and updating project versions.
Sources include:
pyproject.toml.pyforge-deploy-cache/version_cache
It also fetches the latest version from PyPI to prevent version conflicts.
Version resolution behavior highlights:
- dry-run still fetches latest PyPI version (read-only) for realistic previews
- static
pyproject.tomlversions are used by default unless explicit bump intent exists - explicit bump intent (
--bump/ auto-increment) applies against the best available base version - missing PyPI package (404) is treated as initial-release information
ChangelogEngine provides release-note intelligence and changelog automation.
Core behavior:
- extracts commits since latest release reference
- parses commits in parallel for speed
- applies strict Conventional Commit rules and fuzzy fallback heuristics
- routes malformed commits to AI providers (OpenAI/Anthropic/Gemini)
- chunks large commit histories and merges markdown outputs safely
- supports user-defined prompt override via
[tool.pyforge-deploy.changelog]
CommitAnalyzer behavior used by pyforge release also includes:
- confidence-gated bump decisions with AI-assisted low-confidence fallback
- release-noise filtering (
chore(release): ..., merge noise, trivial typo/WIP) - blast-radius + structural + dependency + deprecation + migration scoring layers
DockerBuilder detects project dependencies and Python version, renders a
Dockerfile using a Jinja2 template, and builds the Docker image. It implements
several optimizations to produce small, cache-friendly images:
- Multi-stage builds to keep the final image minimal
- BuildKit-aware commands and
--mount=type=cacheusage for pip caching - Layer caching via careful ordering of dependency installation
- Heavy-hitter detection (large packages like
numpy,pandas) and separation intoheavy-requirements.txtso they can be installed in a dedicated layer for better cache reuse - Optional local wheelhouse (
wheels/) build to enable--no-indexinstalls and reproducible builds - Automatic
.dockerignoretuning to reduce build context size - Runtime dependency sync to prevent missing-module errors in final container
src/project entry-point normalization for correctCMDpath resolution
These features make Docker builds faster, more deterministic, and more cache-efficient.
Handles package distribution:
- Cleans old build artifacts
- Builds source and wheel distributions
- Uploads them to PyPI or TestPyPI. When
uvis available on the system, the distributor usesuv buildanduv publish(ultra-fast) for building and publishing, otherwise it falls back topython -m buildandtwine upload.
Publishing in CI prefers OIDC-based short-lived tokens; for local/manual runs
PYPI_TOKEN is still supported.
This project is licensed under the MIT License.
See the LICENSE file for details.