🚀Version management8 min readUpdated: Nov 5, 2026

GitHub Actions CI/CD: A Practical Workflow Example

GitHub Actions CI/CD runs builds, tests and deployments from your GitHub repository. Here's a deployment workflow that also records the version in AboutTheApp.

👨‍💻
Roman
AboutTheApp

Overview & context

GitHub Actions CI/CD is GitHub's built-in automation. It can, for example, run your tests on every pull request and, when you release a version, build the app and deploy it to the environment you choose. Below are two workflow files you can copy. They include a step that records the version and its release notes in AboutTheApp after a successful deployment.

1. What is GitHub Actions?

GitHub Actions runs automated jobs in response to events in your repository, such as a push, an approved pull request or a new tag. The jobs of a workflow are defined in a YAML file in the .github/workflows folder. They run on a runner, which is a virtual machine that GitHub prepares for each job and discards once the job is done.

CI (continuous integration) means that every change is built and tested automatically. CD (continuous delivery or deployment) deploys the finished build. You can set up both in one workflow or in several workflows within the same repository. The full syntax is in GitHub's workflow syntax reference.

TermWhat it meansExample
WorkflowYAML file with the automation.github/workflows/ci.yml
Event (on)What starts the workflowpull_request, a pushed v* tag, manual workflow_dispatch
JobGroup of steps on one runnertest, deploy
StepOne command or one actionrun: npm test
Action (uses)Ready-made step from GitHub Marketplaceactions/checkout@v5
SecretEncrypted value for workflowssecrets.DEPLOY_TOKEN
EnvironmentDeployment target with its own secrets and rulesstaging, production

2. GitHub Actions CI workflow: tests on every pull request

A GitHub Actions CI workflow installs the dependencies, runs the tests and builds the app on every pull request and every push to the main branch. GitHub marks a pull request with failing tests red and you can use branch protection to block it from being merged at all. The example is for a Node.js app. For another language, replace setup-node and the npm commands.

.github/workflows/ci.yml
name: CI

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm test
      - run: npm run build

3. GitHub Actions deployment workflow on a version tag

In this setup, the deployment workflow starts as soon as you push a version tag such as v1.4.2 to the repository. The workflow takes the version number from the tag without the "v" prefix and finds the previous tag, which marks where the changes since the last release begin. How tags get created depends on your branching strategy and on your versioning scheme.

The deploy job above runs in the "production" environment. In the repository's Settings → Environments, you can add required reviewers to it and store the server credentials as environment "secrets". The deploy.sh script stands in for however you actually deploy: rsync to a server, pushing a Docker image or your hosting provider's command.

.github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: Resolve release metadata
        id: release
        run: |
          echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
          previous_tag=$(git describe --tags --match 'v*' --abbrev=0 HEAD^ 2>/dev/null || true)
          echo "previous-tag=$previous_tag" >> "$GITHUB_OUTPUT"

      - uses: actions/setup-node@v5
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm test
      - run: npm run build
        env:
          VITE_APP_VERSION: ${{ steps.release.outputs.version }}

      - name: Deploy
        run: ./scripts/deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

4. Record the version and deployment in AboutTheApp

The Report release to AboutTheApp action (abouttheapp/sync) records the version and deployment in AboutTheApp as the last step of the deployment workflow. It saves the version number, its release notes and the installation for a customer in a specific environment.

Without this step, the deployment details stay only in the workflow run log and in commits, where neither support nor the customer can see them.

New versions aren't public

Without version-visibility: public, the action creates the version as private. Only signed-in users or someone with the unlock code can see it.

  1. 1

    Create the product and the customer

    In the AboutTheApp dashboard, create the product and a customer with an alias. If you don't need to track deployments per customer or environment, the product alone is enough.

  2. 2

    Generate a CI/CD token

    In the user menu, open Account → CI/CD and click Generate token. You'll see the token only once and it works for all products in the account. Generating a new token invalidates the old one.

  3. 3

    Store the token as a secret

    In your GitHub repository, open Settings → Secrets and variables → Actions and save the token as ABOUTTHEAPP_TOKEN.

  4. 4

    Add the step at the end of the deployment

    Paste the step below at the end of the deploy job. It runs only if all the previous steps, including the deployment, succeeded.

.github/workflows/release.yml
      - name: Report release to AboutTheApp
        uses: abouttheapp/sync@v1
        with:
          token: ${{ secrets.ABOUTTHEAPP_TOKEN }}
          alias: room-booking
          version: ${{ steps.release.outputs.version }}
          changelog-source: git-log
          changelog-since: ${{ steps.release.outputs.previous-tag }}
          version-visibility: public
          customer-alias: client
          environment: production

5. Release notes from commits or CHANGELOG.md

If you set changelog-source: git-log, the release notes are built from the commits between the previous tag and the current one, without merge commits, with each commit message as one bullet. Since the first release has no previous tag, its version is recorded without notes.

If you write the notes by hand in a CHANGELOG.md file in the Keep a Changelog format, use changelog-source: file. The action uses the section whose heading matches the version, for example ## [1.4.2] - 2026-11-05. You'll find what good release notes look like in the software release notes template.

6. Verify that the deployed app runs the new version

You turn on deployment verification with the verify-url parameter. Before recording the version, the action then calls the deployed app's URL repeatedly until it gets a response with the deployed version. If the app doesn't return it within 300 seconds, the step fails and records nothing.

The response can be JSON, for example {"version": "1.4.2"}, or plain text with the version on the first line. If you're not sure how to get the version into your build, read How to display the app version. The URL has to be reachable from the runner without signing in and the returned value has to match the version exactly.

          verify-url: https://rooms.example.com/version.json
          verify-timeout: 300

7. Staging, production and multiple customers

Split staging and production into two jobs in GitHub Actions. The staging job deploys to staging automatically. Set up the production job with needs: staging and its own environment with a required reviewer, so production only gets deployed after approval. Each job has its own abouttheapp/sync step with the matching environment value.

It works the same way for an app you run for several customers. Record the installation separately for each customer and environment, either with its own step or with a matrix strategy over the list of customer aliases. Recording a version is idempotent, so a rerun only updates it. An installation record, however, is added on every run, so rerunning the workflow records another deployment in AboutTheApp.

8. Where you'll see the result

The recorded version appears in the product's version catalog in the dashboard, along with its release notes. A public version shows up on the About page in the Product Versions tab. The About box in your app shows the current version with its notes.

The customer's About page shows in the Environments & Versions tab which version runs for the customer on staging and in production and when it was deployed. Support and the customer see the version the customer actually uses. They can also check the state of the other environments.

Frequently asked questions

Is GitHub Actions free?

For public repositories, yes. Standard GitHub-hosted runners are free there. For private repositories, every GitHub plan includes a monthly credit for compute time and you pay extra once you go over it. Larger runners are billed even for public repositories.

What's the difference between CI and CD?

CI (continuous integration) builds and tests every change automatically. CD (continuous delivery or deployment) gets the tested build ready for release or deploys it right away. In GitHub Actions, that's usually two workflow files or two jobs in one.

Where are GitHub Actions workflows stored?

In the .github/workflows folder of your repository, as files with a .yml or .yaml extension. Each file is a separate workflow with its own events.

How do I run a GitHub Actions workflow only on a new tag?

Add push with a tags filter to the on section, for example tags: ["v*"]. The workflow then runs only when you push a tag starting with "v", not on a regular push to a branch. You push the tag with git push origin v1.4.2.

How do I know which version is deployed for a customer?

The deployment has to record the version somewhere, because support and customers can't read it from the workflow run log. In AboutTheApp, the abouttheapp/sync step at the end of the deployment workflow records the version and the customer's installation in the given environment. AboutTheApp doesn't read GitHub on its own, so the record only exists if this step or an API call creates it.

In short

Two workflow files cover tests on pull requests and deployments on tags. The abouttheapp/sync step at the end makes sure every version shows what changed in it and where it runs.

AboutTheApp

Want to see which version runs for which customer?

Add your app to AboutTheApp for free, put the abouttheapp/sync step in your workflow and every deployment gets recorded automatically.