The Release workflow publishes one version to PyPI and GHCR, then creates a GitHub release
containing the Python distributions, the same version-pinned compose.yaml, the generated
openapi.json, and the example environment as default.env.example (GitHub's public asset
name for the source .env.example). It accepts only tags exactly matching
vMAJOR.MINOR.PATCH.
The one-time owner setup is complete:
-
D77 records explicit acceptance of the preliminary naming risk.
CLA.md, the trademark policy, pull-request template, and metadata-onlyCLAworkflow are present. The emittedCLAstatus is a requiredmaincheck with administrator enforcement. -
The GitHub repository is
writeitai/remember-stack. Update each existing clone if needed:git remote set-url origin git@github.com:writeitai/remember-stack.git
The readable hyphen belongs only to repository and container URLs; the product remains RememberStack and the Python distribution/import remain
rememberstack. GitHub redirects ordinary repository and Git traffic after a rename, but the final name must be in place before configuring PyPI because the trusted identity includes the repository name. -
The GitHub environment
pypirequires an owner review, so a tag cannot publish to PyPI without explicit approval. -
The PyPI account uses two-factor authentication and has a pending Trusted Publisher with these exact values:
Field Value PyPI project name rememberstackGitHub owner writeitaiRepository remember-stackWorkflow release.ymlEnvironment pypiA pending publisher does not reserve the PyPI name. Configure it only after the repository rename and publish promptly once the release gates are clear.
-
The active
Protect release tagsruleset restricts creation, update, and deletion of tags matchingv*to repository administrators.
No PyPI password or long-lived API token belongs in GitHub secrets. The workflow requests a
short-lived OpenID Connect credential and grants id-token: write only to the PyPI job.
Prepare a normal pull request that updates both project.version in pyproject.toml and the
GHCR tag in compose.yaml. Update release-facing documentation in the same pull request. The
contract check rejects drift:
uv run python scripts/check_release_contract.py --tag v0.2.0That release pull request also refreshes the PostgreSQL foundation pins in
Dockerfile.postgres: use the reviewed PostgreSQL 19 prerelease/GA base digest,
pin the intended PGDG pgvector and pg_partman package versions, and pin the
pg_textsearch source revision plus source, compatibility-patch, license, and
notice checksums. Build the Dockerfile for both architectures, record each
embedded artifact manifest and immutable image digest, and run the extension
and graph release matrix before tagging. The tag workflow then rebuilds and
publishes ghcr.io/writeitai/remember-stack-postgres:19beta3-VERSION for both
architectures and attaches postgres-image-digests.json to the GitHub release;
UMC consumes the recorded manifest digest, never that human-readable tag.
Updating the foundation pins remains a manual, reviewable patch cadence;
publishing and digest capture are mechanical and mutable database-image tags
are not used.
After that pull request is merged and main is green, tag its exact merge commit:
git switch main
git pull --ff-only
git tag -a v0.2.0 -m "RememberStack 0.2.0"
git push origin v0.2.0The workflow validates the tag, runs the release test suite, builds the wheel and source
distribution, and publishes rememberstack==0.2.0 plus
ghcr.io/writeitai/remember-stack:0.2.0 and the multi-architecture PostgreSQL
foundation. It creates the GitHub release only after both registries accept
their artifacts and the PostgreSQL manifest proves amd64 plus arm64 digests.
PyPI and GHCR do not support an atomic cross-registry transaction. Never reuse a published version after a partial failure: fix the cause, complete the missing publish when safe, or cut the next patch version.
The remember-stack container package was made public after the v0.1.0 image push, so later
versions in the same package support anonymous Compose pulls without another visibility step. A
new package namespace would default to private and require the same one-time review. GitHub warns
that a public package cannot be made private again.
The image carries standard OCI source labels generated from the repository metadata, which links the package back to this repository. Docker Hub is intentionally not a second publication target.
Run these checks from a clean machine or temporary directory:
uvx --from rememberstack==0.2.0 remember --version
docker pull ghcr.io/writeitai/remember-stack:0.2.0
gh release download v0.2.0 --repo writeitai/remember-stack \
--pattern compose.yaml --pattern default.env.example --pattern openapi.json
found=$(jq -r '.info.version' openapi.json) || {
echo "cannot read openapi.json" >&2
exit 1
}
[ "$found" = "0.2.0" ] || {
echo "openapi.json is version $found, expected 0.2.0" >&2
exit 1
}
cp default.env.example .env
docker compose --env-file .env up --no-build --pull always --detach --wait
curl --fail http://localhost:8000/healthz
docker compose --env-file .env down --volumesThe final command deletes the disposable verification deployment and its volumes.