Operations and releases
Production operation has two separate jobs: deploy released images safely and publish new releases correctly.
Production updates
Section titled “Production updates”The supported production update path is the rolling updater, not ad-hoc docker compose pull across mutable tags.
Preview the next coherent API release:
python scripts/update_rolling.py --env-file .env --dry-runApply it:
python scripts/update_rolling.py --env-file .envThe updater discovers the newest coherent CalVer release through the configured stable repositories, verifies that all selected services report the same product release, pulls their exact release tags, and atomically writes immutable image digests to .env only after every selected image resolves successfully.
DLP image variables are untouched unless explicitly included:
python scripts/update_rolling.py --env-file .env --dlp --dry-runpython scripts/update_rolling.py --env-file .env --dlpThen apply/recreate the stack as appropriate:
docker compose --env-file .env config --quietdocker compose --env-file .env up --detachdocker compose --env-file .env psHealth and logs
Section titled “Health and logs”Gateway health:
curl --fail-with-body --silent --show-error http://127.0.0.1:8080/healthTarget logs rather than dumping the entire rendered configuration:
docker compose --env-file .env logs --tail=100 serverdocker compose --env-file .env logs --tail=100 threads twitterAvoid logging credentials, cookie contents, authorization headers, DLP request bodies, or full rendered Compose environment.
Gluetun recreation
Section titled “Gluetun recreation”Do not use:
docker restart gluetunA raw restart can lose/reuse runtime state in ways that do not reapply the intended Compose environment and has caused VPN authentication problems. Recreate through Compose instead:
docker compose --env-file .env up --detach --force-recreate gluetunThe normal scraper/gateway services share its network namespace, so plan a short interruption while it is recreated.
Product version
Section titled “Product version”VERSION in the parent API repository is the product release source of truth and uses:
YY.MM.ITERATIONExamples within one month increment the final integer; the first release in a new month resets the iteration to 1. Python packages use the PEP 440 equivalent without zero-padded numeric segments (26.08.7 → 26.8.7).
Normal release
Section titled “Normal release”Run from a clean parent checkout with authenticated git, uv, and gh:
python scripts/version.py bump --publish -n "Describe the release here"For multiline notes:
python scripts/version.py bump --publish -F RELEASE_NOTES.mdFor an explicit version:
python scripts/version.py set YY.MM.N --publish -n "Release notes"The current release tool performs the full version synchronization:
- Requires clean parent/submodule worktrees.
- Synchronizes and initializes submodules.
- Fetches each submodule’s remote default branch and safely advances stale parent pins; divergent history aborts.
- Updates parent
VERSION, Python submoduleproject.version, and matchinguv.lockfiles. - Commits version metadata in affected submodules.
- Pushes submodule HEADs so every parent pointer is remotely reachable.
- Commits/pushes the parent
VERSIONand updated submodule pointers. - With
--publish, creates the matching annotated tag and delegates toscripts/publish_release.pyto push it and create the GitHub Release throughgh.
Use --local-only only when you intentionally want the old metadata-only behavior without synchronization/commits/pushes:
python scripts/version.py bump --local-onlyPublish an existing local tag
Section titled “Publish an existing local tag”If the version commit is complete and the matching tag already exists locally on the current HEAD:
python scripts/publish_release.py -n "Describe the release here"The publisher requires:
- clean worktree;
- submodules matching the parent pins;
scripts/version.py check --tag v<VERSION>to pass;- local tag to exist and resolve to current HEAD.
It pushes the branch/tag and creates or updates the GitHub Release.
--retag exists only to repair an unpushed local tag that was created before the final release commit. It refuses to move a tag already present on origin.
Never force-move a published release tag as routine release practice. If an already-published release needs a correction, prefer a new patch iteration unless you have explicitly established that the remote tag produced no release artifacts and are intentionally repairing it.
Docker tag policy
Section titled “Docker tag policy”Official images use:
ghcr.io/pinchana/pinchana-api/<service>:<tag>- Push to
mainpublishes the development snapshot channellatest. - Exact release tag
vYY.MM.Npublishes immutableYY.MM.N, rolling monthlyYY.MM, andstable. - Release CI validates that the Git tag matches
VERSION. - The legacy
stableGit branch is not a release source.
Production should discover through stable only via update_rolling.py, then persist immutable digests.
DLP rollout
Section titled “DLP rollout”Do not combine an ordinary API update with an unplanned DLP enablement. DLP has its own preflight/canary sequence. Updating DLP image digests with --dlp is safe while DLP_ENABLED=false; advertising the capability is a separate operational decision.