Introduction
Modern software delivery requires more than writing code and pushing it to a repository. Applications need reliable processes for testing, building, and deploying changes with minimal manual intervention.
In this project, I built an automated CI/CD pipeline using GitHub Actions. What started as a basic workflow evolved into a multi-stage pipeline that validates repository changes, executes a custom reusable composite action, generates a dynamic status card, and deploys a website to GitHub Pages.
During implementation, I encountered and resolved several issues, including a Bash syntax error, an intentional CI validation failure, a temporary GitHub Actions server error, and a YAML configuration mistake.
This article documents the implementation, troubleshooting process, and final deployment.
Project Overview
The objective was to build a working CI/CD pipeline that automates repository validation, website generation, and deployment.
The project covered five major areas:
- Creating a GitHub repository and configuring a basic workflow.
- Implementing automated repository checks.
- Building and integrating a custom GitHub composite action.
- Creating a multi-stage CI/CD pipeline.
- Deploying the generated website to GitHub Pages.
Technologies used:
- Git and GitHub
- GitHub Actions
- YAML
- Bash
- HTML and CSS
- GitHub Pages
1. Repository Setup and the First GitHub Actions Workflow
I started by creating a public GitHub repository named pipeit-tutorial.
The first task was to establish a basic GitHub Actions workflow and understand how workflow execution is triggered by repository events.
I created .github/workflows/hello.yml:
name: hello
on:
push:
workflow_dispatch:
jobs:
say-hello:
runs-on: ubuntu-latest
steps:
- name: Say hello
run: echo "Hello! My first robot is working."
- name: Show the date and time
run: date
The workflow runs whenever code is pushed to the repository. I also included workflow_dispatch, which allows it to be started manually from the GitHub Actions interface.
After committing and pushing the file, GitHub Actions detected the workflow and executed its job on an Ubuntu runner.
The workflow successfully printed the greeting and displayed the execution date and time.
This established the foundation for the automation that followed.
In addition to automatic execution on code pushes, I tested the pipeline using GitHub Actions' manual trigger (workflow_dispatch).
Steps followed:
- Opened the pipeit-tutorial repository on GitHub.
- Navigated to the Actions tab.
- Selected the
hello workflowfrom the workflow list. - Clicked Run workflow.
- Selected the main branch.
- Clicked Run workflow to initiate execution.
- Opened the resulting workflow run to inspect the execution logs.
- Verified that all steps completed successfully, displaying the greeting message and execution date and time.
This confirmed that the workflow could be triggered on demand without requiring a new code push.
The workflow_dispatch event provides flexibility by allowing workflows to be executed manually whenever needed, alongside their automated triggers.
2. Implementing Continuous Integration Checks
The next objective was to introduce a basic validation process.
A CI pipeline should not simply execute commands, it should also verify that repository changes meet defined requirements.
For this project, I created a Bash script to check whether the README file contained a Markdown heading.
I created tests/check.sh:
#!/usr/bin/env bash
set -e
if grep -q '^#' README.md; then
echo "✅ README has a title"
else
echo "❌ README.md needs a title line that starts with #"
exit 1
fi
The script checks whether the README contains a Markdown heading. If the heading exists, the check passes. Otherwise, the script prints an error message and exits with a non-zero status.
I then created .github/workflows/ci.yml:
name: ci
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Get my code
uses: actions/checkout@v4
- name: Run my checks
run: bash tests/check.sh
This workflow runs on both pushes and pull requests.
The actions/checkout step retrieves the repository contents so that the runner can access the files. The next step executes the Bash validation script.
Testing the Failure Scenario
To verify that the validation was meaningful, I deliberately removed the Markdown heading from the README file and pushed the change.
The workflow failed as expected.
The log displayed:
❌ README.md needs a title line that starts with #
This confirmed that the pipeline was enforcing the condition defined in the test script.
I restored the README heading and pushed the correction. The next CI execution passed successfully.
A useful CI pipeline must be capable of detecting invalid changes rather than simply reporting success.
3. Creating a Custom GitHub Composite Action
After establishing the CI checks, I moved on to reusable automation.
GitHub Actions supports custom actions, allowing teams to package repeated workflow steps into reusable components.
For this project, I created a custom composite action that generates a dynamic HTML status card.
The action was created in .github/actions/build-card/action.yml.
It accepts a name and an optional message, generates a website inside the site directory, and writes a summary to the GitHub Actions run page.
The generated page includes information about the build, such as the commit reference, workflow run number, triggering event, and build time.
The action uses a Bash script and an HTML template to generate the page dynamically. One important implementation detail was the use of an unquoted heredoc (<<EOF) so that shell variables could be expanded while generating the HTML.
I also configured the action to expose the generated page path as an output.
Integrating the Custom Action into the CI Workflow
Once the composite action was created, I integrated it into the existing CI workflow.
The additional step was:
- name: Try my own action
uses: ./.github/actions/build-card
with:
name: Rahimah Sulayman
This allowed the workflow to invoke the action directly from the repository.
After pushing the changes, the CI workflow executed successfully. The custom action generated the HTML status card and displayed its summary in the workflow run.
I also corrected the generated page footer to:
<p>Built with GitHub Actions</p>
This demonstrated how reusable composite actions can simplify workflows and package related operations into a single component.
4. Building the Complete CI/CD Pipeline
With the validation script and custom action working, I moved on to the main pipeline.
The goal was to combine testing, website generation, and deployment into a single automated workflow.
Before configuring deployment, I enabled GitHub Pages in the repository settings and selected GitHub Actions as the deployment source.
I then created .github/workflows/pipeline.yml.
The workflow was configured with three jobs:
-
test: checks the repository. -
build: generates the website and packages it for deployment. -
deploy: publishes the website to GitHub Pages.
The pipeline also included a scheduled trigger and a manual trigger.
Pipeline Configuration
The workflow uses the following structure:
name: pipeline
on:
push:
branches: [main]
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Run my checks
run: bash tests/check.sh
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Build my status card with my own action
uses: ./.github/actions/build-card
with:
name: Rahimah Sulayman
message: Updated automatically through continuous deployment
- name: Package the website
uses: actions/upload-pages-artifact@v5
with:
path: site
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Publish to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
Understanding the Job Dependencies
One important feature of this pipeline is the use of the needs keyword.
The build job depends on the successful completion of the test job:
build:
needs: test
Similarly, deployment depends on the build job:
deploy:
needs: build
This establishes the execution order:
Test → Build → Deploy
The deployment process cannot proceed unless the preceding stages complete successfully.
The workflow also defines permissions required for GitHub Pages deployment:
permissions:
contents: read
pages: write
id-token: write
The concurrency configuration ensures that only the relevant Pages deployment workflow runs at a time, cancelling an older in-progress run when a newer one takes its place.
The scheduled trigger runs the pipeline daily at 08:00 UTC, while workflow_dispatch allows manual execution.
5. Deploying the Website to GitHub Pages
After configuring the pipeline, I pushed the workflow to the repository.
GitHub Actions executed the three jobs in sequence.
The test job validated the repository, the build job generated the website and packaged the site directory, and the deploy job published the artifact to GitHub Pages.
The workflow completed successfully.
The deployed website was available at:
https://rahimahisah17.github.io/pipeit-tutorial/
At this point, the project had moved beyond basic CI automation. It now had a workflow that could validate changes, generate a website, and deploy the result.
6. Verifying Automatic Deployment
A successful initial deployment was not enough. I also needed to confirm that subsequent changes would automatically reach the live website.
To test this, I updated the custom action's message input in the pipeline:
message: Updated automatically through continuous deployment
I committed and pushed the change to the main branch.
This triggered the pipeline automatically.
GitHub Actions executed the test, build, and deployment jobs again. After the deployment completed, I refreshed the live website and confirmed that the updated content was visible.
This verified the continuous deployment process.
The final workflow supported both initial publication and automatic updates whenever changes were pushed to the configured branch.
7. Troubleshooting and Lessons Learned
The implementation was not entirely error-free. Several issues occurred during development, and resolving them was an important part of completing the project.
Issue 1: Bash Syntax Error
During the initial implementation of tests/check.sh, the conditional statement contained a syntax error.
The script did not execute as expected because the if statement was not structured correctly.
I corrected the conditional block and ensured that the then, else, and fi keywords were properly placed.
The corrected script subsequently executed successfully.
Lesson: Shell scripts require careful attention to syntax. A small structural mistake can cause an entire CI job to fail.
Issue 2: Deliberate README Validation Failure
I intentionally removed the heading from the README to test whether the CI workflow could detect an invalid repository change.
The workflow failed and displayed the expected validation message.
This was not an unexpected pipeline defect. It was a controlled test to confirm that the validation script was working.
After restoring the heading, the workflow passed.
Lesson: Testing failure conditions is just as important as testing successful execution. A pipeline should fail when its defined requirements are not met.
Issue 3: GitHub Actions Internal Server Error
During the execution of the initial workflow, GitHub Actions returned a temporary internal server error.
The issue was not caused by the repository's workflow configuration.
I reran the failed jobs, and the workflow completed successfully.
Lesson: Not every CI/CD failure originates from application code or configuration. External platform issues can also affect workflow execution, so it is important to inspect the error details before changing working code.
Issue 4: Duplicate YAML Key
While updating the message passed to the custom action, I accidentally introduced a duplicate key:
message: message: Updated automatically through continuous deployment
This resulted in a YAML configuration error.
I corrected the line to:
message: Updated automatically through continuous deployment
After committing the correction, the pipeline executed successfully.
Lesson: YAML configuration must be structured carefully. Duplicate or incorrectly formatted keys can prevent a workflow from being parsed and executed.
8. Repository Structure
The completed repository is organized as follows:
pipeit-tutorial/
│
├── .github/
│ ├── actions/
│ │ └── build-card/
│ │ └── action.yml
│ │
│ └── workflows/
│ ├── hello.yml
│ ├── ci.yml
│ └── pipeline.yml
│
├── tests/
│ └── check.sh
│
├── README.md
└── site/
└── index.html
The site/index.html file is generated by the custom action during the build process.
The repository structure separates workflow definitions, reusable actions, validation scripts, documentation, and generated website content.
9. Project Release and Documentation
After completing the implementation, I updated the repository documentation to explain the project, its architecture, technologies, and deployment process.
I also published a GitHub release:
Release: v1.0.0
This release marks the completed version of the project and provides a reference point for the implementation.
The repository includes the workflow definitions, custom composite action, validation script, and documentation required to understand how the pipeline works.
10. What This Project Demonstrates
The project provided practical experience with several important DevOps and CI/CD concepts:
- Continuous Integration: Automatically validating repository changes through GitHub Actions.
- Workflow Triggers: Running automation in response to pushes, scheduled events, and manual requests.
- Reusable Automation: Packaging repeated operations into a custom composite action.
-
Job Dependencies: Controlling pipeline execution using
needs. - Artifact Management: Packaging generated website content for deployment.
- Continuous Deployment: Automatically publishing updates to GitHub Pages.
- Troubleshooting: Diagnosing shell script errors, YAML configuration issues, validation failures, and external platform errors.
It also reinforced the importance of designing pipelines that provide clear feedback and stop execution when validation requirements are not satisfied.
Conclusion
Building this CI/CD pipeline was a practical exercise in bringing together the essential stages of automated software delivery.
What started as a simple GitHub Actions workflow evolved into a multi-stage pipeline capable of validating repository changes, executing reusable automation, generating dynamic website content, and deploying updates automatically.
One of the most valuable aspects of the project was troubleshooting the issues encountered during implementation. Understanding why a workflow fails, identifying whether the problem comes from the script, configuration, or external platform, and applying the appropriate correction are important parts of working with automation.
The completed project demonstrates how GitHub Actions can be used to build structured and reusable CI/CD workflows without requiring a complex deployment environment.
It also provides a foundation for future improvements, such as introducing more comprehensive automated tests, adding security and dependency scanning, and extending the deployment process to a containerized application.
The pipeline is built, tested, and deployed.
Project Links
- GitHub Repository: https://github.com/rahimahisah17/pipeit-tutorial
- Live Website: https://rahimahisah17.github.io/pipeit-tutorial/
- GitHub Release: https://github.com/rahimahisah17/pipeit-tutorial/releases/tag/v1.0.0
Summary
This project demonstrates how GitHub Actions can automate a complete software delivery workflow, from code validation to deployment.
By the end of the implementation, I had:
- Created GitHub Actions workflows for basic automation and continuous integration.
- Implemented a Bash script to validate repository documentation.
- Built a reusable custom composite action to generate a dynamic HTML status card.
- Configured a multi-job pipeline with testing, building, and deployment stages.
- Enabled automated deployment to GitHub Pages.
- Verified that changes pushed to the
mainbranch triggered a new deployment. - Documented troubleshooting experiences and published the project as a GitHub release.
The result is a working CI/CD implementation that demonstrates workflow automation, reusable actions, pipeline dependencies, and continuous deployment using GitHub Actions.



























Top comments (1)
A pipeline is only as reliable as the process behind it. This project reinforced the importance of automated validation, reusable workflows, and controlled deployments. Every successful run and every failure provided an opportunity to improve the delivery process.