A reliable CI/CD pipeline turns a code change into a controlled, observable release. This practical CI/CD pipeline tutorial explains how to design the workflow, choose useful quality gates, protect secrets, manage approvals, and prepare a deployment rollback strategy that works across GitHub Actions, GitLab CI, Jenkins, and similar tools.
Overview
Continuous integration and continuous delivery are most useful when they make the path from commit to production predictable. The specific platform may change, but the core workflow remains similar: a developer creates a change, automated checks validate it, an artifact is built and secured, an environment receives the change, and the result is monitored.
A good pipeline is not simply a long list of commands. It is a series of deliberate handoffs with clear evidence at each stage. Before implementing one, define what “ready to deploy” means for the application. That definition might include passing unit tests, a successful build, a vulnerability scan, an approved change, a healthy deployment, and a documented rollback option.
Start by mapping the delivery path:
- Source: A commit or pull request enters the workflow.
- Validation: Formatting, linting, unit tests, integration tests, and policy checks run.
- Build: The pipeline creates a versioned artifact, container image, package, or deployment bundle.
- Promotion: The same artifact moves through environments rather than being rebuilt differently for each one.
- Release: Deployment automation updates the target environment.
- Observation: Logs, metrics, traces, health checks, and release markers help the team confirm the outcome.
Keep the first version narrow. A pipeline that reliably builds, tests, and deploys one service is more valuable than an ambitious workflow that is difficult to understand or repair.
Step-by-step workflow
1. Define the change and branching rules
Decide which events start which jobs. Pull requests commonly run fast validation without deploying. Merges to a protected branch may build a release candidate, while an explicit tag or approved promotion may deploy to production. The important point is to make these rules visible and consistent.
Use branch protection, required reviews, and status checks where they match the team’s risk level. Avoid making every change wait for a manual step if a lower-risk environment can be updated automatically. Conversely, do not treat production as an ordinary test environment when the consequences of an error are higher.
2. Run fast feedback first
Order checks from quick and inexpensive to slower and more specialized. A practical sequence is formatting and linting, unit tests, compilation or packaging, integration tests, and then broader end-to-end checks. Fast failures should appear early so developers do not wait for unrelated jobs.
Make test output easy to find. Store reports as pipeline artifacts when the platform supports it, and include enough context to reproduce a failure locally. Flaky tests should be tracked as reliability problems rather than silently retried forever. A limited retry may reduce noise, but it should not hide a recurring defect.
3. Build a reproducible artifact
Build once and promote the resulting artifact through the delivery process. Give it a traceable version based on a commit identifier, release tag, or another documented convention. Record the source revision, build inputs, and relevant dependency information so the team can connect a running release to the code that produced it.
For containerized applications, keep the image definition deterministic where practical and scan the image before promotion. For infrastructure changes, separate plan and apply operations when the platform and risk profile warrant it. The same principle applies to application and infrastructure delivery: review the proposed change before applying it to a sensitive environment.
4. Protect configuration and secrets
Do not place passwords, private keys, access tokens, or production configuration directly in repository files or pipeline logs. Use the CI/CD platform’s protected variables or an external secrets manager, with access limited by environment and job. Mask sensitive values in output, and inspect scripts for commands that might print them indirectly.
Prefer short-lived credentials and narrowly scoped permissions when the platform supports them. Separate credentials for testing, staging, and production. Treat a pipeline definition as production code: review changes to it, restrict who can alter deployment permissions, and log meaningful administrative actions.
For a broader checklist, see DevSecOps Best Practices for CI/CD Pipelines.
5. Deploy progressively
Choose a deployment strategy that fits the service and its ability to recover. A rolling deployment updates instances in groups. A blue-green deployment keeps two environments so traffic can move between them. A canary deployment exposes a small portion of traffic or users to the new version before broader promotion. A feature flag can separate code deployment from feature activation.
Each approach has trade-offs involving infrastructure cost, state management, database compatibility, and operational complexity. For database changes, use backward-compatible migrations where possible. A new application version may need to run briefly alongside the old version, so schema changes should not assume an immediate all-at-once cutover.
6. Verify health and define rollback
Deployment completion is not the same as release success. Add readiness checks, smoke tests, and service-level signals appropriate to the application. Compare error rates, latency, saturation, queue depth, and other relevant indicators with the pre-release baseline.
A deployment rollback strategy should answer four questions before production promotion:
- What signal indicates that the release is unhealthy?
- Who or what can stop promotion?
- Can the previous artifact be restored quickly?
- What happens to data or schema changes during rollback?
Automated rollback can be useful when the failure condition is clear and the previous version is known to be safe. Otherwise, use an automated pause with a documented human decision. Link the release to the team’s incident response checklist so deployment failure has an established path for communication, mitigation, and review.
Tools and handoffs
GitHub Actions, GitLab CI, and Jenkins can all implement the workflow described above. Their configuration syntax, runner model, permissions, and integrations differ, but the design questions are the same: what triggers the pipeline, which jobs may run in parallel, where artifacts are stored, how environments are protected, and how deployment status is reported?
In GitHub Actions, reusable workflows can centralize common build or security checks across repositories. A matrix can test supported language or operating-system versions, but use it selectively because every added combination increases execution time and maintenance. Environment protection and deployment approvals can create a controlled production handoff. See GitHub Actions examples that scale for patterns involving reusable workflows, matrices, and deployment guards.
A GitLab CI configuration commonly expresses stages, jobs, rules, artifacts, caches, and environment actions in one pipeline file. Keep rules explicit, especially when a job can run on both merge requests and branch pushes. Use artifacts for outputs that must be passed between jobs, and use caches only for reusable dependencies that can safely be regenerated.
Jenkins offers broad flexibility through pipelines, agents, plugins, and shared libraries. That flexibility makes governance important. Pin or review important dependencies, limit credentials by job, and document ownership of shared pipeline code. When considering Jenkins versus GitHub Actions or GitLab CI, evaluate the team’s existing source-control platform, runner requirements, plugin maintenance, access model, and operational ownership rather than choosing on feature lists alone.
Handoffs should produce evidence. A build hands an artifact to deployment; deployment hands health information to release management; monitoring hands anomalies to incident response. Use consistent release identifiers in logs and monitoring so an operator can answer which version is running and when it changed.
Quality checks
Review the pipeline itself at regular intervals. A useful quality check covers the following areas:
- Correctness: Do failed checks stop promotion, and do successful checks represent meaningful risk reduction?
- Reproducibility: Can another engineer rebuild or redeploy the same version from recorded inputs?
- Security: Are secrets protected, permissions minimal, dependencies reviewed, and logs free from sensitive values?
- Reliability: Are runners, registries, deployment targets, and external services handled with clear failure behavior?
- Speed: Are jobs parallelized where safe, and are caches helping without causing stale or incorrect results?
- Traceability: Can a commit, artifact, deployment, approval, and production signal be connected?
- Recovery: Is rollback tested rather than merely described?
Measure more than pipeline duration. Track where failures occur, how often reruns are needed, how long releases remain paused, and whether teams can diagnose failures without pipeline maintainers. These observations help distinguish a genuinely efficient workflow from one that is merely fast when everything goes well.
When to revisit
Revisit the pipeline whenever the application architecture, deployment target, security requirements, or team ownership changes. A move to Kubernetes, a new cloud account, a monorepo, a change in database technology, or the introduction of an internal developer platform can invalidate previous assumptions. Infrastructure-heavy workflows should also be reviewed alongside Kubernetes cost optimization practices when build and deployment usage expands.
Schedule a lightweight review after a serious deployment incident, a near miss, or a recurring flaky test. Ask whether the pipeline detected the problem, whether the right person received the signal, and whether recovery depended on undocumented knowledge. Update job ownership, runbooks, environment permissions, and rollback instructions as part of the review.
To keep the workflow current, maintain a short checklist in the repository:
- Confirm triggers, branch rules, and approval paths.
- Remove obsolete jobs, actions, plugins, runners, and credentials.
- Review dependency, container, and infrastructure scanning coverage.
- Test artifact retrieval and rollback in a non-production environment.
- Verify that release identifiers appear in logs and monitoring.
- Record the next change that may require a pipeline update.
Tools will change, but these handoffs remain durable. Build a small, understandable workflow; secure its permissions; promote one traceable artifact; verify the service after deployment; and practice recovery. Those habits provide a stable foundation for deployment automation even as the surrounding CI/CD platform evolves.