Lesson 1 of 10 · Principles and GitHub Actions
Continuous integration and deployment: principles, pipeline, environments
Objective
Understand why teams automate their tests and their production releases, what the acronyms CI and CD cover exactly, how a pipeline is structured, and what environments are for. You will then write your first pipeline… in plain shell, to see clearly what a tool such as GitHub Actions or GitLab CI automates.
Key points
- Continuous integration (CI): every pushed change is built and tested automatically, several times a day, to detect problems immediately.
- Continuous delivery: every version that passes CI is ready to go to production; the production release remains a human click.
- Continuous deployment: every version that passes all the checks goes to production automatically.
- A pipeline chains steps (lint, tests, build, deployment); the first one that fails stops everything.
- Environments (development, staging, production) let you validate a version before it reaches customers.
- Core principle: you build only once and deploy the same artifact everywhere.
1. The problem: "it works on my machine"
At the agency Atlas Web (Casablanca), before automation, a production release looked like this:
- on Friday evening, Youssef merges three weeks of work by four people;
- nobody has run all the tests: some have been failing for ten days;
- he builds the site on his workstation (with his version of Node.js), then copies the files to the server over FTP;
- a client's website goes down on Saturday; nobody knows which change caused it.
Continuous integration and deployment solve these problems with three ideas:
| Idea | In practice |
|---|---|
| Integrate often | short branches merged every day: conflicts stay small |
| Check everything automatically | a neutral machine runs lint, tests and build on every push |
| Deploy the way you test | deployment is a versioned script, identical every time, and reversible |
2. CI, continuous delivery, continuous deployment
Continuous integration (CI)
┌──────────────────────────────────────────────┐
push → │ fetch the code → lint → tests → build │ → artifact
└──────────────────────────────────────────────┘
│
Continuous delivery: automatic deployment to staging, │
to production on a human decision (button) ─────────────┤
│
Continuous deployment: everything is automatic up to │
production, if all checks are green ────────────────────┘
| Continuous integration | Continuous delivery | Continuous deployment | |
|---|---|---|---|
| Build and tests on every push | yes | yes | yes |
| Artifact ready to deploy | sometimes | always | always |
| Production release | manual, outside the pipeline | one click in the pipeline | automatic |
| Prerequisites | automated tests | + scripted deployments | + strong confidence in tests and monitoring |
In practice, people often say "CI/CD" for the whole thing. An agency like Atlas Web typically does continuous delivery: every merge into main goes automatically to staging, and the project manager triggers the production release after the client's approval.
3. Anatomy of a pipeline
A pipeline is split into stages that contain jobs. Jobs in the same stage can run in parallel; stages run in order.
"check" stage "test" stage "build" stage "deploy" stage
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ lint │ │ tests Node 20│ │ Docker image │ │ staging │
│ format │ ───→ │ tests Node 22│ ───→ │ archive │ ───→ │ production │
└──────────┘ └──────────────┘ └──────────────┘ │ (manual) │
└──────────────┘
| Step | Role | Examples |
|---|---|---|
| Lint / format | check style and obvious errors, in a few seconds | ESLint, Prettier, shellcheck, php -l |
| Tests | prove that the code does what it should | unit, integration and end-to-end tests |
| Security | find vulnerable dependencies and secrets | npm audit, code analysis, secret detection |
| Build | produce the artifact to deploy | archive, Docker image, binary, mobile app |
| Deployment | install the artifact in an environment | SSH to a VPS, Kubernetes, hosting provider |
The pipeline runs on a runner (or executor): a machine, often a fresh virtual machine or container, provided by GitHub or GitLab, or installed on your own servers. Because it starts from scratch every time, "it works on my machine" disappears.
The good properties of a pipeline
- Fast: less than 10 minutes for the main feedback; beyond that, developers stop waiting for the result.
- Reliable: a test that fails at random (flaky) destroys trust; you fix it or remove it.
- Versioned: the pipeline is a file in the repository (
.github/workflows/*.yml,.gitlab-ci.yml), reviewed in pull requests like the code. - Red = priority: when
mainis broken, the team fixes it before moving on.
4. Environments
| Environment | Role | Who uses it | Data |
|---|---|---|---|
| Development (local) | write and try out | the developer | fictitious |
| Review (review app) | see a pull request online | reviewers, designers | fictitious |
| Staging | validate a version under conditions close to production | project manager, client | anonymized copy or test data set |
| Production | serve real customers | everyone | real |
Rules that prevent disasters:
- The same artifact moves from staging to production: you do not rebuild between the two, otherwise what you validated is not what you deploy.
- Only the configuration changes from one environment to another (database address, API keys), through variables and secrets specific to each environment.
- Production is protected: deployment restricted to certain branches, mandatory approval, secrets that other jobs cannot access.
- You can always roll back to the previous version (lesson 3.2).
5. The tool landscape
| Tool | Where the pipeline lives | Specific feature |
|---|---|---|
| GitHub Actions | .github/workflows/*.yml |
built into GitHub, huge catalog of reusable actions |
| GitLab CI/CD | .gitlab-ci.yml |
built into GitLab, runners you can install anywhere, widely used in companies |
| Jenkins | Jenkinsfile |
historical, self-hosted, extensible with plugins |
| Others | Azure Pipelines, Bitbucket Pipelines, CircleCI, Drone/Woodpecker… | same logic: a YAML file, steps, runners |
Levels 1 and 2 of this course cover GitHub Actions, then GitLab CI. Once you understand the principles, moving from one to the other is mostly a matter of vocabulary.
6. A pipeline by hand, to understand
Before handing the work to a platform, let's write the same sequence in shell. This is exactly what a runner will do: commands, in order, stopping at the first error.
#!/bin/sh
# scripts/pipeline.sh — local pipeline for the Atlas storefront
set -e # stop at the first command that fails
echo "== lint"
npm run lint
echo "== tests"
npm test
echo "== build"
mkdir -p dist
tar -czf dist/vitrine.tar.gz package.json src
echo "Artefact prêt : dist/vitrine.tar.gz"
chmod +x scripts/pipeline.sh
./scripts/pipeline.sh
echo "code : $?" # 0 = green pipeline; anything else = red
set -e is the key: without it, the script would continue after a failed test and produce a broken artifact. CI platforms apply the same rule: a command that returns a non-zero code makes the job fail.
You can even connect this pipeline to Git with a pre-push hook (Git course, lesson 3.2): the push is rejected if the pipeline fails. This is useful, but it stays on your workstation and can be bypassed with --no-verify. Real CI runs on the server, for everyone, on every push.
7. Measuring: the four metrics
To know whether your delivery chain is improving, teams often track four metrics (called "DORA" metrics):
| Metric | Question |
|---|---|
| Deployment frequency | how many production releases per week? |
| Lead time for changes | how long between a commit and its release? |
| Change failure rate | what share of deployments causes an incident? |
| Time to restore | how long to get back to normal service after an incident? |
Good CI/CD raises the first two and lowers the last two at the same time: you deliver more often and more safely, because each delivery is small.
8. Where to start in an existing team
There is no need to automate everything at once. Here is the plan Atlas Web followed, over about a month:
| Week | Goal | Visible result |
|---|---|---|
| 1 | lint + existing tests run on every pull request | a green check or a red cross on every PR |
| 2 | main protected: green CI and one review required |
no more direct pushes, no more "it worked on my machine" |
| 3 | artifact built by CI, automatic deployment to staging | the client sees every merge online the same day |
| 4 | one-click production deployment, with a documented rollback | no more Friday-evening FTP copies |
Only then come optimization (cache, parallelism), advanced security (OIDC, signatures, level 4) and, if the team wants it, full continuous deployment.
Two tips to make this transition succeed:
- Measure before and after: the average duration of a production release, the number of incidents per month. These figures convince better than a speech.
- Keep the pipeline simple and readable: a YAML file that the whole team understands is better than an overcomplicated machine that only one person can repair.
Common pitfalls
- Automating chaos: without automated tests, a pipeline only deploys bugs faster. Start with a few tests on the critical functions (price calculation, payment).
- Rebuilding for production what was validated in staging: it is no longer the same artifact.
- A 40-minute pipeline: developers push less often and work around CI. Parallelize and use caching (lesson 1.3).
- Ignoring a red pipeline "because this test always fails": fix it or delete it, but do not get used to red.
Lab
In the Node.js project /root/vitrine (already in Git, with a test and a lint), you build a local pipeline:
scripts/pipeline.sh: lint then tests, stopping at the first error;- a build step that produces the artifact
dist/vitrine.tar.gz, only if everything is green; - a commit of the script, without the artifact;
- a
pre-pushhook that runs the pipeline before every push.
The checks deliberately break a test in a copy of the project to make sure that your pipeline really turns red.
Quiz
- What is the difference between continuous delivery and continuous deployment?
- Why does a pipeline stop at the first failed step?
- What is a runner, and why does it start from a fresh machine?
- Why must you deploy to production the same artifact as the one validated in staging?
- What does
set -edo in a pipeline script?
Answers
- In continuous delivery, every validated version is ready and the production release is triggered by a human decision; in continuous deployment, it goes to production automatically if all the checks pass.
- So that you never build or deploy code that you know is defective, and to give quick feedback on the cause of the failure.
- It is the machine (or container) that runs the pipeline jobs. It starts from a clean state so that the result does not depend on whatever is left on a particular workstation.
- Because rebuilding can produce a different result (updated dependencies, another environment): what you would deploy would no longer be what you tested.
- It stops the script as soon as a command returns a non-zero code, and the script itself then ends in failure.
Reference sources: GitHub Docs (CC BY 4.0) and GitLab Docs (CC BY-SA 4.0). Original MasterLinux text.