100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
YAML

Deploying Containers from CI/CD

Learn the common patterns pipelines use to deploy containerized applications, from direct kubectl updates to GitOps-driven reconciliation.

Containers in CI/CDIntermediate10 min readJul 8, 2026
Analogies

Deploying Containers from CI/CD

Once a container image is built, scanned, and pushed to a registry, the pipeline's final job is to get that image running in a target environment — a Kubernetes cluster, a serverless container platform like AWS Fargate or Cloud Run, or a simpler VM-based Docker host. How this happens varies enormously across organizations, ranging from a pipeline directly issuing imperative commands against an API, to a fully declarative GitOps model where the pipeline's only job is to update a manifest and a separate controller handles the actual rollout. Understanding these patterns, and their tradeoffs around auditability, blast radius, and rollback speed, is central to designing dependable container deployments.

🏏

Cricket analogy: Like the final step after a player is selected and cleared — actually getting him onto the field, whether a captain personally inserts him (imperative) or team management updates the official team sheet for match officials to process (GitOps-style), with different implications for accountability and how fast a mistake reverses.

Push-Based Deployment

In a push-based model, the CI pipeline itself has credentials to the target environment and directly issues the update — running kubectl set image, calling a cloud provider's deploy API, or executing a Helm upgrade. This is simple to set up and gives immediate feedback within the pipeline about whether the deployment succeeded. The tradeoff is that the pipeline now holds powerful, often broad, credentials to production infrastructure, and any pipeline misconfiguration, malicious code injection, or compromised CI runner has a direct path to modifying production. Push-based deployment also means the actual state of the cluster can drift from what's declared in git if anyone runs manual kubectl commands outside the pipeline.

🏏

Cricket analogy: Like giving the assistant coach the master key to change the batting order directly on the official scoresheet mid-match — fast and instant, but if that key is misused or someone else scribbles changes on the scoresheet later, the official record no longer matches what's happening on the field.

GitOps: Pull-Based Deployment

In the GitOps model, the CI pipeline's only responsibility after pushing an image is to update a manifest in a separate 'deploy' git repository (or a designated path in the same repo) to reference the new image tag, then open or auto-merge a commit. A cluster-resident controller — Argo CD or Flux are the dominant tools — continuously watches that repository and reconciles the live cluster state to match it, pulling the change rather than the pipeline pushing it. This inverts the trust model: the CI pipeline never holds cluster credentials at all, only git write access, while the in-cluster controller holds the deployment credentials and is the only thing that can modify the cluster. This significantly shrinks the blast radius of a compromised CI pipeline and gives a full audit trail of every deployment as git commits.

🏏

Cricket analogy: Like a captain only submitting the team sheet to the match referee's office (git write access) rather than substituting players himself, while match officials (controller) actually enforce the lineup on the pitch, so a rogue captain can only alter paperwork, never play directly, and every change is logged.

yaml
# .github/workflows/deploy.yml — GitOps-style: pipeline only updates the manifest
name: promote-to-staging
on:
  workflow_run:
    workflows: ["docker-build"]
    types: [completed]
jobs:
  update-manifest:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout deploy repo
        uses: actions/checkout@v4
        with:
          repository: acme/k8s-manifests
          token: ${{ secrets.DEPLOY_REPO_TOKEN }}

      - name: Update image tag with yq
        run: |
          yq -i '.spec.template.spec.containers[0].image = "ghcr.io/acme/api:${{ github.sha }}"' \
            environments/staging/deployment.yaml

      - name: Commit and push
        run: |
          git config user.name "ci-bot"
          git config user.email "ci-bot@acme.dev"
          git commit -am "deploy: api image ${{ github.sha }} to staging"
          git push
      # Argo CD, watching this repo, detects the commit and syncs the cluster.

GitOps flips the deployment analogy from a delivery truck driving into your warehouse (push) to a warehouse worker continuously checking a shared order sheet and restocking shelves to match it (pull). The worker inside never needs to trust or admit anyone from outside.

A subtle failure mode in push-based deployments is 'kubectl apply drift' — if the pipeline applies a partial manifest or a manual hotfix is applied directly to the cluster, the git-declared state and the live state silently diverge, and the next pipeline run may unexpectedly revert an emergency fix nobody remembered was manual.

Deployment Verification and Health Gates

Regardless of push or pull model, a responsible deployment pipeline does not consider the job done the moment a new image reference is applied — it must verify the new version is actually healthy before declaring success. This typically means polling readiness/liveness probes, checking that the expected number of replicas reached Ready state within a timeout, and optionally running smoke tests against the newly deployed endpoint. Pipelines that skip this step can report a false 'deployment successful' status while pods are actually crash-looping in production.

🏏

Cricket analogy: Like not declaring a returning injured bowler match-fit the moment he steps onto the field — the physio must watch him bowl a full spell at pace within a set window and run him through drills before certifying him, otherwise a team risks fielding someone who breaks down mid-over.

  • Push-based deployment has the CI pipeline directly apply changes using credentials to the target environment.
  • GitOps (pull-based) has the pipeline only update a manifest; an in-cluster controller reconciles the live state to match it.
  • GitOps shrinks the CI pipeline's blast radius since it never holds direct cluster credentials.
  • Manual out-of-band changes cause drift between declared and live state in push-based setups.
  • Deployment pipelines should verify rollout health (readiness, replica counts, smoke tests) before declaring success.
  • Argo CD and Flux are the dominant GitOps controllers used to reconcile Kubernetes clusters against git.

Practice what you learned

Was this page helpful?

Topics covered

#YAML#CICDToolsPipelinesStudyNotes#DevOps#DeployingContainersFromCICD#Deploying#Containers#Push#Based#Docker#StudyNotes#SkillVeris