Continuous integration answers is this change safe? Delivery answers how does it reach users, and who decides when?
The second question is mostly not technical. The mechanics of deploying are usually a handful of steps; the engineering is in what must be true first, who can approve, what happens when it goes wrong, and how the thing being deployed relates to the thing that was tested.
Start with Deploy to AWSThree terms, kept distinct
Section titled “Three terms, kept distinct”Continuous integration — every change is built and tested on integration. Output: a verdict.
Continuous delivery — every validated change could be released at any time. A deployable artifact exists and is proven deployable. Releasing is a decision someone makes.
Continuous deployment — every validated change is released, automatically, with no human gate.
A successful build should not automatically imply an uncontrolled production deployment.
Continuous deployment is a legitimate and sometimes excellent choice. It is a choice, made with knowledge of your blast radius, your monitoring and your ability to reverse. Arriving at it because nobody configured a gate is different.
Build once, promote
Section titled “Build once, promote”The principle that shapes every lesson here:
A sequence: source is built once into an artifact identified by digest; that same artifact is tested, deployed to staging, validated, and then promoted unchanged to production.
Rebuilding per environment means production runs something that was never tested. The source may be identical while dependency resolution, base images and build tooling have moved. Promoting one immutable artifact — identified by a digest, not a mutable tag — is what makes staging validation mean anything at all.
Container images make this concrete: myapp:latest is a moving pointer, while
myapp@sha256:a3f8c2… is one specific image forever. The lessons here deploy by digest.
No long-lived cloud credentials
Section titled “No long-lived cloud credentials”Every cloud lesson in this cluster authenticates with OIDC, not stored access keys.
The short version: the workflow requests a short-lived signed token from GitHub describing who it is — repository, branch, environment. Your cloud provider is configured to trust tokens matching specific claims and exchanges one for temporary credentials. Nothing long-lived is ever stored in GitHub.
permissions: contents: read id-token: write # allows requesting an OIDC tokenThe Security cluster covers each cloud’s federation setup in depth. This cluster covers the deployment itself.
Prerequisites
Section titled “Prerequisites”| You should be able to | Covered in |
|---|---|
| Build and test a project in CI | Continuous Integration |
| Use job dependencies and conditions | Jobs |
| Explain the OIDC trust model | GitHub Actions OIDC |
| Configure an environment | Environments |
| Understand artifact versus cache | Artifacts |
You also need an account with the cloud provider you intend to deploy to, and enough access to create an identity provider and a role. Every example uses placeholders — no real account identifiers appear.
Learning objectives
Section titled “Learning objectives”- Deploy to AWS, Azure and Google Cloud without storing a single long-lived credential.
- Structure a deployment as a separate job that depends on a successful build.
- Use environments to attach secrets, protection rules and deployment history to a target.
- Prevent two production deployments running simultaneously with concurrency control.
- Promote an immutable artifact rather than rebuilding, and identify it by digest.
- Verify a deployment succeeded rather than assuming a green job means a working system.
- Describe rollback accurately for the system you are deploying — which differs enormously.
The learning path
Section titled “The learning path”- Lesson 1: 01. Deploy to AWSDeploy to AWS from GitHub Actions using OIDC instead of stored access keys — S3 and CloudFront, ECS, Lambda, environments, concurrency and honest rollback.
- Lesson 2: 02. Deploy to AzureDeploy to Azure from GitHub Actions using federated credentials instead of a service principal secret — App Service, Functions, Container Apps, slots and rollback.
- Lesson 3: 03. Deploy to Google CloudDeploy to Google Cloud from GitHub Actions using Workload Identity Federation — Cloud Run, Artifact Registry, traffic splitting, revisions and rollback.
- Lesson 4: 04. Deploy Docker ApplicationsDeploy container images from GitHub Actions — publishing to GHCR, deploying by digest, registry authentication, rolling updates and rollback that actually works.
- Lesson 5: 05. Deploy to KubernetesDeploy to Kubernetes from GitHub Actions — cluster authentication without stored kubeconfigs, manifest templating, rollout status gating, undo, and push versus GitOps.
- Lesson 6: 06. Deploy TerraformApply Terraform from GitHub Actions after merge — saved plans, environments and approvals, state locking, drift detection, and what infrastructure rollback actually means.
- Lesson 7: 07. Deploy GitHub PagesDeploy a static site to GitHub Pages with GitHub Actions — the Pages deployment API, split build and deploy jobs, custom domains, concurrency and rollback.
Concurrency is not optional here
Section titled “Concurrency is not optional here”For CI, cancelling a superseded run saves money. For deployment, concurrency control prevents corruption.
concurrency: group: deploy-production cancel-in-progress: falseTwo production deployments running simultaneously can interleave in ways that leave a system in a state neither intended. For Terraform it can mean competing state operations; for Kubernetes, conflicting rollouts; for anything with migrations, genuine data risk.
cancel-in-progress: false is the critical difference from the CI pattern. Cancelling a half-finished
deployment is usually worse than queueing behind it — the in-flight one is left partially applied.
Every deployment lesson in this cluster sets concurrency, and says why.
Environments tie it together
Section titled “Environments tie it together”Every deployment lesson here uses a GitHub environment, which is the feature that connects five otherwise-separate concerns:
Secrets and variables scoped to one target, so staging and production configuration cannot be confused.
Protection rules — required reviewers, wait timers, branch restrictions — that gate whether the job may proceed at all.
Deployment history, visible on the repository, recording what went where and when.
OIDC claims, so a cloud trust policy can require that a token came from a job targeting
production rather than merely from the repository.
A URL, surfaced in the interface, linking to what was deployed.
jobs: deploy: runs-on: ubuntu-latest environment: name: production url: https://example.com steps: - run: ./deploy.shDeclaring environment: is what makes the environment’s secrets reachable and its protection rules
apply. A deployment job without one is using repository secrets and no gate — which is a choice, and
should be a deliberate one.
Verification is part of deploying
Section titled “Verification is part of deploying”A deployment job that exits zero means the deployment command succeeded. It does not mean the system works.
- name: Deploy run: ./deploy.sh
- name: Verify run: | for attempt in $(seq 1 30); do if curl -fsS --max-time 5 "$HEALTH_URL" >/dev/null; then echo "healthy after ${attempt} attempt(s)" exit 0 fi sleep 10 done echo "::error::service did not become healthy within 5 minutes" exit 1 env: HEALTH_URL: https://example.com/healthzWithout that, a deployment that succeeded in shipping a broken build reports success, and the first signal is a user. With it, the pipeline knows — which is also what makes automated rollback possible, since something must decide the deployment failed.
Rollback, honestly
Section titled “Rollback, honestly”Rollback semantics differ so much between systems that a single description would be wrong for most of them.
| System | What rollback actually means |
|---|---|
| Stateless application | Redeploy the previous artifact — usually clean |
| Kubernetes | kubectl rollout undo, or redeploy the previous digest |
| Container platform | Point the service at the previous image digest |
| Terraform | No generic rollback. Applying an older configuration is a new change |
| Database migration | Migration-specific; often not reversible at all |
| Static site | Redeploy the previous build — genuinely simple |
Never write “click rollback and everything returns to normal.”
For anything with state, forward-fixing is often safer than reversing. Each lesson states what rollback means for its target, including where the honest answer is “you cannot, and here is what to do instead.”
A deployment job checklist
Section titled “A deployment job checklist”Before a deployment workflow runs against anything real:
- Does it declare an environment? That is what attaches secrets, protection rules and history.
- Is concurrency set with
cancel-in-progress: false? Two simultaneous production deployments is the failure this prevents. - Does it authenticate with OIDC rather than stored credentials?
- Are
permissionsminimal, withid-token: writeonly where OIDC is used? - Does it deploy an artifact built earlier, identified by digest, rather than rebuilding?
- Does it verify the deployment succeeded, rather than assuming a zero exit means a working system?
- Is the trigger filtered so it cannot fire from an arbitrary branch?
- Is rollback documented for this specific system, honestly?
Items 2 and 5 are the ones most often missing, and both fail silently until the day they do not.
Then what?
Section titled “Then what?”The Advanced cluster covers the machinery these deployments use — environments, approvals, artifacts and reusable workflows — in the depth this cluster assumes.
The Security cluster covers per-cloud OIDC federation setup, and the supply-chain question of proving that the artifact you deployed is the one your pipeline built.## One artifact, many targets
The single idea to carry through this cluster: the thing you deploy to production should be the exact thing you tested, identified by a digest rather than a tag, promoted rather than rebuilt.
Everything else — environments, approvals, OIDC, concurrency — exists to make that promotion safe.
What “deployment” means here
Section titled “What “deployment” means here”The lessons use the word for anything that makes a change to a running system: pushing an image, applying a manifest, updating infrastructure, publishing a site.
That breadth is deliberate, because the engineering concerns are the same regardless of target — identity, concurrency, verification, and an honest account of reversal. What differs is the mechanism, and the mechanism is usually the least interesting part.
Begin: Deploy to AWS with GitHub Actions