A green CI grants no right to deploy to production. This module shows how GitHub Actions materialises that boundary: an environment carries its own secrets, requires a human approval, restricts the allowed branches, and logs every deployment. You will build a build once, promote afterwards chain, from the pull request to production, with a rollback that does not depend on a rebuild.
What you will learn
- Tell apart what CI covers from what CD covers, and what GitHub calls a deployment
- Create an environment and attach a job to it, with the deployment URL
- Apply protection rules: approvals, wait timer, allowed branches
- Promote a single artifact from staging to production, without rebuilding it
- Go back without replaying the whole pipeline
CI and CD do not ask the same question
Continuous integration answers a question of quality: does this commit break anything? It runs on every branch, every pull request, and its verdict is binary. Continuous deployment answers a question of trust: is this artifact allowed to reach the users? The answer no longer depends on the tests alone, but on who approves, from which branch, with which secrets, and towards which target.
Confusing the two produces the most widespread mistake: a single workflow that tests, builds and deploys in the same job, with every production secret loaded from the first line. One compromised test, one boobytrapped dependency, and the attacker inherits production access.
| Question | Answered by | GitHub mechanism |
|---|---|---|
| Does the code compile and pass the tests? | CI | Jobs, matrices, cache |
| Is the artifact the one we think it is? | Supply chain | SHA pinning, attestations |
| Who authorises the production release? | CD | Environments, protection rules |
| How do we go back? | CD | A kept artifact, a redeployment |
What GitHub calls a deployment
A deployment is not merely a job running a script. It is an object GitHub
records: it appears in the repository's Deployments tab, it carries a target
environment, a state (pending, in progress, succeeded, failed), and
possibly a browsable URL. That traceability is what separates an
ssh server "./deploy.sh" from an auditable delivery chain.
The trigger for that machinery fits in one workflow key:
jobs: deploy: runs-on: ubuntu-24.04 environment: production steps: - run: ./deploy.shFrom that line on, the job is no longer an ordinary job. It is subject to the
rules of the production environment: if an approval is required, GitHub
pauses the job and waits; if the current branch is not allowed, the job
fails before starting; and the environment secrets are delivered only
once those conditions are met.
The promotion chain
The mental model to install is a promotion line, not a series of independent deployments. You build one single artifact, and it is that same artifact that travels through the steps:
Pull request | v CI (tests, lint, scanners) | v Build -> a single artifact plus its attestation | v Staging (environment, staging secrets) | v Human approval | vProduction (protected environment, production secrets)The property that matters is this one: what is tested in staging is exactly what goes to production. Rebuilding between the two steps breaks the guarantee, because the second build may resolve different dependencies, carry an untested fix, or simply fail after the validation has already been granted.
It is also what makes going back trivial: the previous artifact is still there, you only have to redeploy it. A rollback that requires replaying the whole pipeline is not a rollback, it is a new deployment with the risks of a new deployment.
How this module is laid out
The following pages build that chain in the order you would set it up on a real repository. The progression goes from the container (the environment) to the content (the promoted artifact), then to what happens when things go wrong.
| Step | What it installs |
|---|---|
| Environments | The named target, with its own secrets and variables |
| Protection rules | Approvals, wait timer, allowed branches |
| Artifact promotion | A single build travelling through staging then production |
| Releases and packages | Public distribution: a GitHub release, a GHCR image |
| Rollback and concurrency | Going back, and forbidding simultaneous deployments |
The billing trap to know up front
Environments with protection rules and environment secrets are not available everywhere. On a Free plan, they only work on public repositories: a private repository requires at least GitHub Pro, or a Team or Enterprise plan for an organisation.
That limit has a direct teaching consequence: if you follow this module on a private repository under a free plan, the environment will indeed be created, but the approvals and the environment secrets will not apply. Work on a public repository dedicated to learning, or check your organisation's plan before building a governance on top of it.
Key points
- A green CI validates the quality of the code; it does not authorise a deployment. CD answers a different question, that of trust.
- A GitHub deployment is a tracked object: a target environment, a state, a URL, a history in the Deployments tab.
- The
environment:key subjects the job to the environment rules: a pause for approval, a refusal when the branch is not allowed, secrets delivered only afterwards. - Naming a non-existent environment creates it with no protection at all: configure it in Settings > Environments before writing the job.
- You build once and promote: the artifact tested in staging must be exactly the one going to production.
- Protected environments and their secrets require a public repository on a Free plan, or at least GitHub Pro on a private one.
Next steps
- Protection rules: the four guardrails that turn an environment into a real boundary, approvals included.
- Promoting an artifact: the build once, deploy twice chain that guarantees staging and production get the same binary.
- Rollback and concurrency: what has to be prepared before the incident to go back in minutes.