From CI to CD
CI verifies that the code is correct. Continuous delivery/deployment (CD) takes the next step: automatically bringing that verified code to an environment where it runs. We distinguish:
- Continuous Delivery: the artifact is left ready to deploy; the final deployment is approved manually.
- Continuous Deployment: every change that passes CI is deployed to production with no manual intervention.
The deployment flow with containers
The usual pattern when you use Docker is:
- Build the image from the Dockerfile.
- Publish (push) the image to a registry (Docker Hub, GitHub Container Registry, etc.), tagged with a version.
- Deploy: the environment downloads that image and starts new containers.
A CD job that continues the CI one:
deploy:
needs: build # only if the CI job finished successfully
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to the registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build and publish image
run: |
docker build -t ghcr.io/my-org/my-app:${{ github.sha }} .
docker push ghcr.io/my-org/my-app:${{ github.sha }}
- name: Deploy
run: ./deploy.sh ghcr.io/my-org/my-app:${{ github.sha }}
Notice needs: build: the deployment only happens if CI passed. The
credentials live in secrets, never in the code.
Environments and manual approvals
You rarely deploy straight to production. The usual thing is a chain of
environments: staging (pre-production, to validate) and then production. The
move to production usually requires a manual approval from a responsible
person, who acts as a control gate.
Rollback
If a deployment introduces a failure, you need to revert fast. Since each version is an immutable tagged image, the rollback consists of re-deploying the previous tag that did work. Having versioned images makes going back a matter of seconds, not hours.
Small, frequent deployments + versioned images = low risk and fast recovery.