Lesson 06 Push-to-Deploy
Theme:

Homework — Push-to-Deploy

Lesson 06 · Cloud Infrastructure & DevOps · 3 tasks

Task 1 — Add a staging environment

Create a second GitHub Environment named staging (no required reviewer — staging deploys should be fast). Modify deploy.yml to add a deploy-staging job that runs before deploy-production. The production job should only start if the staging deploy succeeds and its healthcheck passes.

The two-job structure should look like this (fill in the steps):

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.shortener.example.com
    steps:
      # 1. SSH to staging VM, pull, up -d
      # 2. Smoke-test: curl -fsS https://staging.shortener.example.com/healthz

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://shortener.example.com
    steps:
      # same as lab deploy steps

Create a corresponding staging environment in GitHub Settings → Environments. Add STAGING_HOST, STAGING_USER, and STAGING_KEY secrets to the staging environment (you can reuse the same VM from the lab, or create a second one).

Self-check: push to main. The Deploy workflow runs two jobs. deploy-staging starts immediately (no approval required). deploy-production starts only after staging passes and only after the production reviewer approves. The staging URL returns 200 on /healthz before the production approval prompt fires.

Task 2 — Pin deploys to the digest, not the tag

Modify deploy.yml so the SSH step receives the image digest rather than the commit SHA tag. The deploy target’s docker-compose.yml should reference the image by digest (ghcr.io/.../api@sha256:...), making the deployed version immutable and traceable.

You will need to pass the digest from the Security & Sign workflow to the deploy job. One reliable approach: in security.yml, upload the digests as a workflow artifact at the end of the sign job. In deploy.yml, download that artifact using actions/download-artifact@v4 with the run-id set to github.event.workflow_run.id.

A sketch of the artifact approach:

# In security.yml, at the end of the sign job:
- name: Save digests
  run: |
    echo "api=${{ steps.api.outputs.digest }}" > digests.env
    echo "worker=${{ steps.worker.outputs.digest }}" >> digests.env
- uses: actions/upload-artifact@v4
  with:
    name: image-digests
    path: digests.env

# In deploy.yml, before the SSH step:
- uses: actions/download-artifact@v4
  with:
    name: image-digests
    run-id: ${{ github.event.workflow_run.id }}
    github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Load digests
  run: cat digests.env >> $GITHUB_ENV

After loading, $api and $worker are available as environment variables in subsequent steps. Pass them to the SSH script and use them to set IMAGE_DIGEST_API and IMAGE_DIGEST_WORKER on the target, then reference them in docker-compose.yml.

Self-check: after a deploy, SSH to the target and run docker compose config. The image: fields for api and worker should end with @sha256: rather than a tag name. Confirm that deploying the same digest twice is a no-op (no container restart).

Task 3 — Concurrent-deploy safety experiment

Push two commits to main in quick succession (e.g., merge two pull requests within 30 seconds of each other). Observe what happens in the Actions tab under the two Deploy workflow runs.

With cancel-in-progress: false (the current setting): both Deploy runs are queued in the deploy-production concurrency group. The second run waits for the first to finish before starting. Neither is cancelled.

Now temporarily change cancel-in-progress: true in deploy.yml, commit, and repeat the two-commit experiment. Observe: the first deploy run is cancelled mid-flight when the second one starts.

Write a short paragraph (3–5 sentences) in your homework writeup documenting both behaviors and the trade-off:

Self-check: your writeup explains the correctness-vs-speed trade-off in your own words. You have a screenshot from the Actions UI showing the queued behavior (false) and the cancellation behavior (true). Revert to cancel-in-progress: false before submitting.

View solutions →