Post Image
microsoftApr 12, 2026

Building Your First CI/CD Pipeline with GitHub Actions

Ruchi Yadav
Ruchi Yadav5 min read

A first CI/CD pipeline should automate the checks you already trust when preparing a change for release. If your usual routine involves installing dependencies, running tests, and building the application, start there. Continuous integration means running those checks automatically as changes arrive, helping developers catch problems before merging. Continuous delivery adds a repeatable route to release, while continuous deployment takes the further step of releasing qualifying changes automatically. GitHub Actions supports these workflows through YAML files stored in the .github/workflows directory of your repository. Create a file such as ci.yml and give the workflow a descriptive name so its purpose is clear in the Actions tab. The on section defines which events trigger it. For a typical application, begin with pull_request events targeting main and push events on main. This checks proposed changes before merging and verifies the resulting branch afterward. A workflow contains jobs, and each job contains steps. The runs-on setting selects the runner that executes a job; a GitHub-hosted Ubuntu runner is a straightforward starting point for many projects. Steps can execute shell commands through run or invoke reusable actions through uses. Start by checking out the repository with actions/checkout, then configure the language runtime your application requires. Keep the first workflow small enough to understand without constantly consulting documentation. You should be able to explain what starts it, what each step checks, and which failure should prevent the change from progressing. GitHub Docs

For a Node.js application, the first useful job can configure Node through actions/setup-node, install dependencies with npm ci, run the tests, and build the application. Choose a runtime version supported by your project rather than relying on whatever happens to be installed on the runner. Commit the package lockfile so npm ci can install the dependency tree recorded in the repository. If the lockfile disagrees with package.json, the installation should fail, giving you a concrete issue to fix. Run the same scripts developers use locally, such as npm run lint, npm test, and npm run build, provided those scripts exist. Configure test tools to finish in CI instead of waiting indefinitely in watch mode. Give steps clear names so someone reviewing a failed run can distinguish a dependency problem from a test failure. After committing the workflow, open the Actions tab and inspect the output, including successful steps. Then deliberately introduce a failing test on a temporary branch to confirm that the workflow catches it. A green badge has limited value if tests are skipped or failures are ignored. Once the basic checks work, configure npm caching through setup-node to reduce repeated downloads. That cache does not replace npm ci, and the workflow must still work when no cache is available. If the build produces files needed later, upload them as workflow artifacts. Artifacts preserve outputs such as compiled applications and test reports; dependency caches serve a different purpose and should not become your release storage. GitHub Docs

Add deployment only after the verification job behaves reliably. Create a separate deploy job and use needs to make it depend on successful checks. Jobs can otherwise run independently, so placing deployment underneath testing in the YAML file does not establish an execution order. Restrict deployment to the intended event and branch, such as a push to main, so a pull request cannot accidentally trigger a production release. Where possible, deploy the artifact produced by the verified build instead of rebuilding the application with potentially different inputs. The deployment command depends on your hosting platform: it might publish static files, push a container image, or update a managed application service. Keep those instructions explicit and give the destination a clear name. GitHub environments let you associate deployment jobs with targets such as staging and production. Depending on your plan and repository visibility, protection rules can require approval or restrict which branches may deploy. Store sensitive credentials in the appropriate secret settings rather than writing them into workflow files. If your cloud provider supports OpenID Connect, configure a trust relationship that allows the deployment job to obtain short-lived credentials. Limit that trust to the intended repository and deployment context. Grant id-token: write only where token issuance is required, alongside the job’s other necessary permissions. After deployment, run a focused smoke check against the released application. A successful upload confirms that files moved; a successful application check provides better evidence that the release actually works. GitHub Docs

Once the pipeline works from commit to deployment, make it dependable enough for everyday use. Configure repository rules to require the relevant CI checks before merging; running tests automatically does not, by itself, stop someone from merging a failing change. Keep GITHUB_TOKEN permissions narrow, typically starting with contents: read and adding privileges only to jobs that need them. Treat third-party actions as executable dependencies because they run within your workflow’s security context. Pin actions to full commit SHAs when you need immutable references, and establish a process for updating them. Be especially careful with contributions from forks: do not combine untrusted pull request code with privileged credentials through pull_request_target. Avoid inserting pull request titles or other user-controlled text directly into shell scripts, since apparently harmless metadata can become executable input. Concurrency controls can cancel outdated CI runs when a newer commit arrives on the same branch, reducing wasted work. Deployment requires a more deliberate policy: preventing overlapping releases is useful, but canceling a deployment midway can leave incomplete changes. Set job timeouts, retain useful failure reports, and investigate recurring flaky tests instead of repeatedly rerunning them until they pass. Add a version matrix only when supporting multiple runtimes is a real requirement, since every extra combination increases execution time and maintenance. Finally, document how to identify the deployed version and restore a previous release. Your first pipeline is ready for regular use when a teammate can understand a failure, trace a release to its source commit, and recover from a bad deployment without guessing.