Automatic Version Management in CI/CD
pfy ci update can generate a pipeline that bumps the patch version in project.yaml on every push to main, commits the change, and pushes a Docker image tagged with the new version. What that pipeline does, which projects get it, and how to work with it is below.
Node.js projects only
The automatic bump and image push are generated only when ci.language is node. For Go projects the generated pipeline runs secret scanning, lint, tests, and a deployment-drift check; releases are cut by GoReleaser when you push a v* tag, and project.yaml is never changed by CI. Go teams bump the version themselves with pfy version bump.
How it works
On every push to main, the Node.js pipeline:
- Runs tests and security scans against the current version.
- Runs
pfy version bump --part patch, which rewritesversion:inproject.yaml. - Commits
project.yamlwith the messagechore: bump version to X.Y.Z [skip ci], so the commit does not start another run. - Builds the image and pushes it with two tags: the new version and
latest.
Feature branches and pull requests only run the checks. Nothing is bumped or pushed.
Image tags
Only two tags are ever pushed:
my-app:1.2.3: the exact versionmy-app:latest: the newestmainbuild
No commit-SHA or branch-name tags are produced.
The generated GitHub Actions job
This is the version-and-push job from .github/workflows/ci.yml as pfy ci update --language node writes it (registry and image name come from project.yaml):
version-and-push:
runs-on: ubuntu-latest
needs: build-and-scan
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Bump version
run: |
# Increment patch version
pfy version bump --part patch
echo "NEW_VERSION=$(pfy version show)" >> $GITHUB_ENV
- name: Commit version bump
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add project.yaml
git commit -m "chore: bump version to $NEW_VERSION [skip ci]"
git push
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push multi-arch image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/my-app:${{ env.NEW_VERSION }}
ghcr.io/my-app:latestThe generated Node.js job does not install pfy
The "Install pfy CLI" step in the template is inside a Go-only block, so the Node.js workflow above calls pfy without installing it and the Bump version step fails on a fresh runner. Until the template is fixed, add an install step before Bump version:
- name: Install pfy CLI
run: |
PFY_VERSION=$(curl -fsSL https://api.github.com/repos/ProductifyFW/cli/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
curl -fsSLO "https://github.com/ProductifyFW/cli/releases/download/v${PFY_VERSION}/pfy_${PFY_VERSION}_linux_amd64.tar.gz"
tar -xzf "pfy_${PFY_VERSION}_linux_amd64.tar.gz" pfy
sudo mv pfy /usr/local/bin/pfyThe GitLab template has the same gap (its push job only has a comment where the install should be).
The generated GitLab CI job
The push job from .gitlab-ci.yml (Node.js):
push:
stage: deploy
image: docker:latest
services:
- docker:dind
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- docker load -i image.tar
- apk add --no-cache git nodejs npm
# Install pfy CLI here if Node.js version available
script:
- |
if [ "$CI_COMMIT_BRANCH" == "main" ]; then
# Bump version
git config user.name "GitLab CI"
git config user.email "ci@gitlab.com"
pfy version bump --part patch
NEW_VERSION=$(pfy version show)
git add project.yaml
git commit -m "chore: bump version to $NEW_VERSION [skip ci]"
git push https://oauth2:${CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git HEAD:${CI_COMMIT_BRANCH}
# Tag and push images
docker tag $IMAGE_NAME:0.1.0 $IMAGE_NAME:$NEW_VERSION
docker tag $IMAGE_NAME:0.1.0 $IMAGE_NAME:latest
docker push $IMAGE_NAME:$NEW_VERSION
docker push $IMAGE_NAME:latest
fi
only:
- main
- tags0.1.0 here is the version that was in project.yaml when you ran pfy ci update. The build stage builds the image with that tag; the push stage re-tags it with the bumped version. If you change the version by hand, run pfy ci update again so the two stay in step.
Permissions the pipeline needs
GitHub Actions: the job declares them itself:
permissions:
contents: write # push the version commit
packages: write # push the imageIf your repository restricts the default GITHUB_TOKEN, allow "Read and write permissions" for workflows, or the git push step fails.
GitLab CI: CI_JOB_TOKEN must be allowed to push to the repository, and CI_REGISTRY_USER/CI_REGISTRY_PASSWORD must be able to push to the registry.
Version strategy
Every push to main bumps patch: 1.0.0 → 1.0.1 → 1.0.2 …
For a feature release or a breaking change, bump minor or major yourself before merging:
pfy version bump --part minor # 1.0.3 → 1.1.0
pfy version bump --part major # 1.1.0 → 2.0.0
git add project.yaml
git commit -m "Release v1.1.0"
git push origin mainCI then bumps once more (1.1.0 → 1.1.1) and pushes that image. If you want the exact version you set to be the one that ships, tag it and let the release.yml workflow build from the tag instead.
Example
pfy init --name my-service --language node # version: 0.1.0
pfy ci update
git add .
git commit -m "Initial setup"
git push origin mainCI: tests → bump (0.1.0 → 0.1.1) → build → push my-service:0.1.1 and my-service:latest.
The bump commits show up in history:
$ git log --oneline --grep="bump version"
abc123d chore: bump version to 0.2.1 [skip ci]
def456e chore: bump version to 0.2.0 [skip ci]Troubleshooting
If the pipeline runs in a loop, the bump commit is missing [skip ci]. Check the commit message in the generated job.
If git push is denied: on GitHub, make sure the workflow has contents: write and the checkout step passes the GITHUB_TOKEN secret as its token input (as in the generated job above). On GitLab, the push URL must use oauth2:${CI_JOB_TOKEN} and the job token must be allowed to write.
pfy: command not found means the generated Node.js job did not install the CLI; see the warning above.
If the image tag is stale: the build stage tags with the version that was in project.yaml when the pipeline file was generated. Re-run pfy ci update after a manual bump.
Using the tags in deployments
Nomad deployments rendered with pfy nomad deploy take the tag from --tag or from project.yaml with --use-project-version (see Nomad Deployment). For other consumers, refer to the image by version:
image: ghcr.io/my-service:1.2.3Avoid latest in production; it moves on every merge.
Related
- Version Management: the
pfy versioncommands - ci: what
pfy ci updategenerates for Go and Node.js - Project Configuration:
project.yaml