Skip to content

Automatic Version Management in CI/CD

Overview

The ProductifyFW CLI generates CI/CD pipelines that automatically increment project versions on main branch pushes. This ensures every deployment has a unique, incrementing version number without manual intervention.

How It Works

On Every Push to Main

  1. Version Bump: CLI increments patch version in project.yaml
  2. Commit: Changes committed with [skip ci] to avoid loops
  3. Build: Docker image built with new version
  4. Tag: Image tagged with version number only (no SHA/branch tags)
  5. Push: Versioned image pushed to registry

Version Tags

Only semantic version tags are used:

  • my-app:1.2.3 - Exact version number
  • my-app:latest - Latest main branch build

No commit/branch tags:

  • [NO] my-app:abc123def (SHA)
  • [NO] my-app:main (branch)
  • [NO] my-app:feature-xyz (branch slug)

GitHub Actions Workflow

Generated CI Pipeline

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
    - name: Install pfy CLI
      run: |
        curl -LO https://github.com/ProductifyFW/cli/releases/latest/download/pfy-linux-amd64
        chmod +x pfy-linux-amd64
        sudo mv pfy-linux-amd64 /usr/local/bin/pfy
    - name: Bump version
      run: |
        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: Build and push multi-arch image
      uses: docker/build-push-action@v6
      with:
        platforms: linux/amd64,linux/arm64
        push: true
        tags: |
          ghcr.io/my-app:${{ env.NEW_VERSION }}
          ghcr.io/my-app:latest

Execution Flow

Push to main

Run tests & security scans (on version 1.2.3)

Bump version: 1.2.3 → 1.2.4

Commit project.yaml with [skip ci]

Push to repository

Build Docker image

Tag: my-app:1.2.4, my-app:latest

Push to registry

GitLab CI Workflow

Generated Pipeline

yaml
push:
  stage: deploy
  script:
    - |
      if [ "$CI_COMMIT_BRANCH" == "main" ]; then
        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
        
        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

Build Stage

Both platforms build using the current version from project.yaml:

yaml
# Current version (before bump)
tags: ghcr.io/my-app:0.1.0

Configuration

Required Permissions

GitHub Actions:

yaml
permissions:
  contents: write # Push version commits
  packages: write # Push Docker images

GitLab CI:

  • Repository write access via CI_JOB_TOKEN
  • Registry access via CI_REGISTRY_PASSWORD

Skip CI Commits

Version bump commits include [skip ci] to prevent infinite loops:

bash
git commit -m "chore: bump version to 1.2.4 [skip ci]"

Version Strategy

Automatic Patch Bumps

Every main branch push increments patch version:

1.0.0 → 1.0.1 → 1.0.2 → 1.0.3 ...

Manual Minor/Major Bumps

Use CLI for feature releases or breaking changes:

bash
pfy version bump --part minor  # 1.0.3 → 1.1.0
pfy version bump --part major  # 1.1.0 → 2.0.0

Example Deployment Scenario

Initial Setup

bash
pfy init --name my-service  # version: 0.1.0
pfy ci update
git add .
git commit -m "Initial setup"
git push origin main

CI runs: Tests → Bump (0.1.0 → 0.1.1) → Build → Push my-service:0.1.1

Development Cycle

Feature branch:

bash
git checkout -b feature/auth
git push origin feature/auth

CI runs tests only (no version bump, no push).

Merge to main:

bash
git checkout main
git merge feature/auth
git push origin main

CI runs: Tests → Bump (0.1.1 → 0.1.2) → Build → Push

Release Preparation

bash
pfy version bump --part minor  # 0.1.2 → 0.2.0
git commit -am "Release v0.2.0"
git push origin main

CI runs: Tests → Bump (0.2.0 → 0.2.1) → Build → Push

Version History Tracking

Git Log Shows All Versions

bash
$ 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]
789abc0 chore: bump version to 0.1.2 [skip ci]
012def3 chore: bump version to 0.1.1 [skip ci]

Registry Shows All Images

bash
$ docker images
my-service  0.2.1   ...
my-service  0.2.0   ...
my-service  0.1.2   ...
my-service  0.1.1   ...
my-service  latest  ...  (points to 0.2.1)

Troubleshooting

CI Loop: Version Keeps Bumping

Ensure [skip ci] is in commit message:

bash
git commit -m "chore: bump version to X.Y.Z [skip ci]"

Permission Denied on Push

yaml
# GitHub: Use GITHUB_TOKEN
- uses: actions/checkout@v6
  with:
    token: ${{ secrets.GITHUB_TOKEN }}

# GitLab: Use CI_JOB_TOKEN
git push https://oauth2:${CI_JOB_TOKEN}@...

Version Not Incrementing

Ensure CLI is installed:

yaml
- name: Install pfy
  run: |
    curl -LO https://github.com/ProductifyFW/cli/releases/latest/download/pfy-linux-amd64
    chmod +x pfy-linux-amd64
    sudo mv pfy-linux-amd64 /usr/local/bin/pfy

Best Practices

1. Let CI Handle Version Bumps

bash
# [OK] Use CLI
pfy version bump --part patch

# [NO] Don't manually edit
sed -i 's/0.1.0/0.1.1/' project.yaml

2. Use Minor/Major for Releases

bash
pfy version bump --part minor  # Feature release
pfy version bump --part major  # Breaking changes

3. Tag Releases in Git

bash
VERSION=$(pfy version show)
git tag "v$VERSION"
git push --tags

Integration with Deployments

Kubernetes

yaml
containers:
- name: app
  image: ghcr.io/my-service:1.2.3  # Specific version
  # or
  image: ghcr.io/my-service:latest  # Always latest

Docker Compose

yaml
services:
  app:
    image: ghcr.io/my-service:${VERSION:-latest}

Deploy: VERSION=1.2.3 docker-compose up -d

See Also