Continuous deployment means that every change that passes all automated tests is automatically released to production.

Let’s watch a commit flow through the system.

Imagine git push origin main. This triggers a webhook to our CI/CD orchestrator, a tool like GitLab CI or GitHub Actions.

# Example .gitlab-ci.yml snippet
deploy_staging:
  stage: deploy
  script:
    - echo "Deploying to staging environment..."
    - kubectl apply -f k8s/staging/deployment.yaml
    - kubectl apply -f k8s/staging/service.yaml
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - main

This pipeline snippet shows a deploy_staging job. When a commit lands on the main branch, this job executes. It first runs kubectl apply commands to update the Kubernetes deployment and service definitions for our staging environment. The environment block tells GitLab where to find the deployed application.

Once deployed to staging, a separate set of automated tests kicks off. These are often end-to-end tests, perhaps using tools like Cypress or Playwright.

// Example Cypress test snippet
describe('Homepage loads', () => {
  it('should display welcome message', () => {
    cy.visit('https://staging.example.com');
    cy.contains('Welcome to our awesome app!');
  });
});

If these tests pass, the pipeline proceeds. The next stage is typically a manual approval gate. This is a crucial safety net. A human reviewer checks that everything looks good in staging before authorizing the production deployment.

# Example .gitlab-ci.yml snippet for production deploy
deploy_production:
  stage: deploy
  script:
    - echo "Deploying to production environment..."
    - kubectl apply -f k8s/production/deployment.yaml
    - kubectl apply -f k8s/production/service.yaml
  environment:
    name: production
    url: https://example.com
  when: manual # Requires manual trigger
  only:
    - main

The deploy_production job is configured with when: manual. This means it won’t run automatically. A developer or QA engineer clicks a "play" button in the CI/CD interface to start the production deployment after verifying the staging environment.

The actual production deployment in Kubernetes might use a rolling update strategy. This ensures zero downtime. Kubernetes gradually replaces old pods with new ones, keeping a minimum number of healthy pods running throughout the process.

# Example kubectl command for rolling update (implicit in deployment definition)
# The deployment.yaml would define strategy: { type: RollingUpdate, rollingUpdate: { maxUnavailable: 1, maxSurge: 1 } }
kubectl rollout status deployment/my-app-deployment -n production

This command, kubectl rollout status, monitors the progress of the deployment. It will report success once all new pods are ready and serving traffic, and old pods have been terminated.

The real magic here is the feedback loop. If any test fails at any stage – unit, integration, or end-to-end on staging – the pipeline halts. No bad code makes it to production. Developers get immediate feedback and can fix issues before they impact users.

The key to enabling this is robust automated testing. Without a comprehensive suite of tests that accurately reflect desired behavior, continuous deployment becomes a dangerous liability. You need tests that cover not just functionality but also performance, security, and user experience.

Many teams struggle with the perceived risk of automatic production deployments. The solution isn’t to avoid automation, but to build confidence through highly reliable testing and well-defined rollback strategies. This means having automated tests that can be run against production traffic (carefully!) or a production-like canary environment, and a clear, automated process to revert to a previous known-good version if something goes wrong.

The most challenging part for many organizations is moving from a culture of fear around production changes to one of trust in automated processes. This shift requires significant investment in testing infrastructure, developer education, and a willingness to embrace rapid iteration. The goal is to make deployments so safe and frequent that they become a non-event, freeing up engineering time for more impactful work.

The next hurdle is often managing feature flags to decouple deployment from release, allowing you to deploy code that isn’t yet visible to users.

Want structured learning?

Take the full DevOps & Platform Engineering course →