Releases & Tags
Versioned releases anchored on git tags, file assets stored on the local filesystem, and an integrated OCI container registry mounted at /v2.
A release in ProxifAI is a row in the Release table tied to a git tag, optionally with binary assets and a release-notes body. The full surface — list/get/create/update/delete plus per-asset upload/download — is available at /api/v1/forge/{owner}/{repo}/releases/.... The integrated OCI registry is a separate but co-located concern, mounted at /v2/ on the same host.
Creating a release
Tag the commit
Either push a new tag from your local clone (git tag v1.2.3 && git push origin v1.2.3), or let the release endpoint create the tag for you by passing targetCommit (a branch name or SHA).
Create the release
From the UI: Releases tab → Draft a new release. Pick the tag, write the title and notes, attach assets, optionally mark as draft or pre-release, then publish. From the CLI:
pfai release create v1.2.3 \
--target main \
--title "v1.2.3 — Auth & analytics improvements" \
--body "$(cat CHANGELOG-v1.2.3.md)" \
--prereleaseFrom the API:
curl -X POST http://localhost:3000/api/v1/forge/{owner}/{repo}/releases \
-H "Authorization: Bearer $PFAI_TOKEN" \
-d '{
"tagName":"v1.2.3",
"targetCommit":"main",
"name":"v1.2.3",
"body":"…",
"draft":false,
"prerelease":true
}' Attach assets
curl -X POST http://localhost:3000/api/v1/forge/{owner}/{repo}/releases/v1.2.3/assets \
-H "Authorization: Bearer $PFAI_TOKEN" \
-F "file=@dist/proxifai-linux-amd64.tar.gz"The upload endpoint accepts a multipart form. Server-side, asset bytes go to the configured storage backend (internal/storage/), and a release_asset row records name, contentType, size, and downloadCount.
Release schema
Release
├── id int64
├── tagName string (the git tag this release points at)
├── name string (display title)
├── body string (Markdown release notes)
├── draft bool (not visible to non-maintainers)
├── prerelease bool (won't be marked "Latest")
├── author ForgeUser
├── targetCommit string (resolved when tagName didn't pre-exist)
├── publishedAt timestamp?
└── assets []ReleaseAsset
├── id, name, label, contentType, size
├── downloadUrl
└── downloadCount
PATCH /releases/{tag} updates name, body, draft, prerelease (each is nullable in the input — only fields you pass change). Deleting the release does not delete the underlying git tag — tag deletion is a separate operation and is gated by tag protection.
Auto-generating release notes from merged PRs since the last tag isn’t implemented in OSS — write the body yourself or pipe a generator like git-cliff into pfai release create --body-file -.
Asset storage
Assets are written to the storage backend defined by internal/storage/:
| Backend | Selected when | Path layout |
|---|---|---|
| Local filesystem (default) | ASSET_STORAGE_PATH is set, or fallback to $DATA_DIR/assets | <base>/<owner>/<repo>/<release-id>/<asset-id>-<filename> |
Asset rows reference a stable internal id (text like ast_xxx); the API serializes IDs as int64 for backward compatibility, so the canonical handle is the downloadUrl field. To delete:
pfai release delete-asset v1.2.3 dist-linux-amd64.tar.gz
# or
curl -X DELETE http://localhost:3000/api/v1/forge/{owner}/{repo}/releases/v1.2.3/assets/{assetId} \
-H "Authorization: Bearer $PFAI_TOKEN"
Tags
| Lightweight tag | Annotated tag | |
|---|---|---|
| Stored as | A refs/tags/<name> ref pointing at a commit SHA | A separate object with message, tagger, date |
| Created by | git tag <name> | git tag -a <name> -m "..." |
| Surfaced in API | Tag with empty message/tagger | Tag with full metadata |
Both work as anchors for releases. Use annotated tags when the tag itself should carry release notes independent of a Release row.
Protected tags
Configure protection patterns at Repository Settings → Tag Protection or via pfai protected-tag. Each rule is a glob (v*, release-*, …); matching tags can only be deleted by users with admin access on the repo. This guards against accidental deletes that would orphan releases.
Deleting a git tag (via git push --delete origin <tag> or the UI’s Tags tab) does not delete the corresponding Release row, but the release becomes unanchored — its commit reference may become invalid if the commit isn’t reachable from any branch. Protected tags prevent this.
OCI container registry
A full OCI Distribution Spec implementation is mounted at /v2/* on the same host as the rest of the platform (internal/registry). There is no separate registry hostname.
# Auth uses your pfai token as both username and password
echo $PFAI_TOKEN | docker login http://localhost:3000 -u <user> --password-stdin
# Push
docker tag my-image:v1 localhost:3000/my-org/my-repo/my-image:v1.0.0
docker push localhost:3000/my-org/my-repo/my-image:v1.0.0
# Pull
docker pull localhost:3000/my-org/my-repo/my-image:v1.0.0
Behind the scenes, every blob upload (POST /v2/<repo>/blobs/uploads/), manifest push (PUT /v2/<repo>/manifests/<tag>), and pull goes through the in-process handler. Tags and manifests are queryable via OCIQuerier, which also powers the Packages tab in the repo UI.
| Format | Supported |
|---|---|
| OCI image manifest v2 | ✅ |
| Docker manifest v2 | ✅ |
| Multi-arch manifest list / OCI index | ✅ |
For production, front the registry with TLS via your reverse proxy (Cloudflare tunnel, nginx, Caddy) and tell Docker to trust the host. For development, --insecure-registry flags work against localhost.
Versioning
Semantic versioning (MAJOR.MINOR.PATCH) is convention, not enforcement. The releases list sorts by publishedAt descending; the topmost non-draft non-prerelease release is highlighted as Latest in the UI.
| Bump | When |
|---|---|
| MAJOR | Breaking changes |
| MINOR | Backward-compatible features |
| PATCH | Bug fixes |
Pre-releases (prerelease=true) participate in the list but never become the Latest badge holder, so you can ship v1.2.3-rc.1 alongside v1.2.2 without confusing consumers.
REST endpoint reference
| Method · Path | Purpose |
|---|---|
GET /api/v1/forge/{owner}/{repo}/releases | List |
POST /api/v1/forge/{owner}/{repo}/releases | Create |
GET /api/v1/forge/{owner}/{repo}/releases/{tag} | Read |
PATCH /api/v1/forge/{owner}/{repo}/releases/{tag} | Update |
DELETE /api/v1/forge/{owner}/{repo}/releases/{tag} | Delete release (not the tag) |
POST /api/v1/forge/{owner}/{repo}/releases/{tag}/assets | Upload asset (multipart) |
GET /api/v1/forge/{owner}/{repo}/releases/{tag}/assets/{assetId} | Download asset |
DELETE /api/v1/forge/{owner}/{repo}/releases/{tag}/assets/{assetId} | Delete asset |
GET /api/v1/forge/{owner}/{repo}/tags | List tags |
/v2/... | OCI Distribution API |
See also
Branch protection, push rules, and the rest of the forge surface.
Merge into the branch you'll tag for the next release.
Pipelines that build and publish images to the integrated registry on tag.
CLI for releases and protected tags — list, create, update, delete, and asset upload/download.