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.
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.
| Term | What it means | Example |
|---|---|---|
| Workflow | YAML file with the automation | .github/workflows/ci.yml |
| Event (on) | What starts the workflow | pull_request, a pushed v* tag, manual workflow_dispatch |
| Job | Group of steps on one runner | test, deploy |
| Step | One command or one action | run: npm test |
| Action (uses) | Ready-made step from GitHub Marketplace | actions/checkout@v5 |
| Secret | Encrypted value for workflows | secrets.DEPLOY_TOKEN |
| Environment | Deployment target with its own secrets and rules | staging, 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.
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 build3. 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.
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.
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
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
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
Store the token as a secret
In your GitHub repository, open Settings → Secrets and variables → Actions and save the token as ABOUTTHEAPP_TOKEN.
- 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.
- 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: production5. 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: 3007. 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.
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.