ci(deploy): deploy pre-built images tagged by commit SHA - #2131
alanpeixinho wants to merge 4 commits into
Conversation
* Publish GHCR Images: optional ref on manual dispatch; tag images with resolved commit SHA; push `latest` only on push to main * Staging: required `image_tag`; pull GHCR images with `--no-build`; manual `workflow_dispatch`; CI passes `github.sha` after tests * Production: required `image_tag` (replaces unused `tag` default); export `IMAGE_TAG` on deploy; checkout deploy SHA on server * Align staging `docker-compose.yml` image paths with `IMAGE_OWNER` / `IMAGE_REPOSITORY` from `.env.example` * Update DEPLOYMENT.md and README for SHA-based deploy flow Closes kernelci#2087
e9a1955 to
bfb9c73
Compare
felipebergamin
left a comment
There was a problem hiding this comment.
Two things that will bite us on the actual deploy path — left them inline.
| uses: ./.github/workflows/deploy-staging.yaml | ||
| secrets: inherit | ||
| with: | ||
| image_tag: ${{ github.sha }} |
There was a problem hiding this comment.
This is going to race with Publish GHCR Images. Both still start on their own when something lands on main, and CI never waits for the images to actually be in the registry.
That used to be fine because staging built on the box. Now it pulls this SHA with --no-build, so if tests finish before all three images are pushed, deploy just fails. A retry/wait on pull would keep build and deploy as separate workflows and still make this reliable.
There was a problem hiding this comment.
Good catch — addressed in 7c53d27.
Staging deploy now retries docker compose pull (up to ~10 min) so it waits for the SHA tags in GHCR instead of racing Publish. Build and Deploy stay separate.
| cp ~/.env-staging dashboard-staging/.env && | ||
| cd dashboard-staging && | ||
| git checkout ${GITHUB_SHA} && | ||
| git checkout ${IMAGE_TAG} && |
There was a problem hiding this comment.
This clone only has main, so git checkout will fail for a SHA that isn't on that branch. That's the hotfix path from #2087 — publish from a branch, then deploy that SHA. Actions checkout can fetch it; the server can't. Same thing in the production workflow.
Fetching the SHA first (git fetch origin "$IMAGE_TAG" then checkout) would cover that and also save us from cloning the whole history on every deploy.
There was a problem hiding this comment.
Also fixed in 7c53d27 (staging + production).
We now git fetch origin "$IMAGE_TAG" before checkout, and clone with --depth 1 so hotfix SHAs not on main still resolve without pulling full history.
Retry staging pulls until SHA tags exist so CI does not race Publish. Fetch the image_tag on the host before checkout so hotfix commits work.
| outputs: | ||
| digest: ${{ steps.build.outputs.digest }} | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
since we are here I think we can update the action version.
| - uses: actions/checkout@v4 | |
| - uses: actions/checkout@v7 |
There was a problem hiding this comment.
Done in efdad26. Publish now uses actions/checkout@v7.
| resolve-ref: | ||
| if: github.repository == 'kernelci/dashboard' || github.event_name == 'workflow_dispatch' | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| sha: ${{ steps.git.outputs.sha }} | ||
| steps: | ||
| - name: Resolve commit SHA | ||
| id: git | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| REF: ${{ github.event.inputs.ref || github.sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| sha=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${REF}" --jq .sha) | ||
| echo "sha=${sha}" >> "$GITHUB_OUTPUT" | ||
|
|
There was a problem hiding this comment.
do we need this? I'm not sure but I think we can pass the ref directly to the checkout action.
There was a problem hiding this comment.
Done in efdad26. We check out inputs.ref (or github.sha on push) and take the image tag from git rev-parse HEAD, so a branch or tag still becomes one commit SHA. The job stays so a bad ref fails once before the three image builds.
| inputs: | ||
| ref: | ||
| description: Git ref to build (branch, tag, or commit SHA) | ||
| required: true | ||
| type: string | ||
|
|
There was a problem hiding this comment.
Dont we loose the power to go to any commit with that? I believe it only brings tags and branches.
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ needs.resolve-ref.outputs.sha }} |
There was a problem hiding this comment.
if I'm not mistaken we can use ${{ github.sha }}.
maybe we don't even need to provide this arg, it defaults to github.ref
There was a problem hiding this comment.
The image we want is inputs.ref, which can be a different SHA than the one from github.sha.
| tags: | | ||
| ${{ steps.meta.outputs.prefix }}/dashboard-frontend:latest | ||
| ${{ steps.meta.outputs.prefix }}/dashboard-frontend:${{ github.sha }} | ||
| tags: ${{ steps.meta.outputs.prefix }}/dashboard-frontend:${{ needs.resolve-ref.outputs.sha }}${{ github.event_name == 'push' && format(',{0}/dashboard-frontend:latest', steps.meta.outputs.prefix) || '' }} |
There was a problem hiding this comment.
I think we can use a short sha to make easier to read the image tags.
- name: Get short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
- name: Show short SHA
run: echo "${{ env.SHORT_SHA }}"
There was a problem hiding this comment.
I might be too much conservative (especially because we are a small repo), but I still prefer long hashes due to reduced collision chance.
Do you consider too problematic to keep long hash here?
| image_tag: | ||
| description: GHCR image tag (Git commit SHA) to deploy | ||
| required: true | ||
| default: 'main' | ||
| type: string |
There was a problem hiding this comment.
I'd replace this by github.ref_name
There was a problem hiding this comment.
github.ref_name is the branch we are running the workflow from image_tag is the one we are using to build the image.
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.inputs.image_tag }} |
There was a problem hiding this comment.
Same as above: checkout image_tag, not github.ref_name.
| SSH_HOST: ${{ secrets.STAGING_HOST }} | ||
| SSH_KEY: ${{ secrets.STAGING_KEY }} | ||
| DASHBOARD_VERSION: ${{ github.event.inputs.tag }} | ||
| IMAGE_TAG: ${{ github.event.inputs.image_tag }} |
There was a problem hiding this comment.
again, I'd use github.(ref | ref_name | sha) instead of relying in the input exactly as provided
There was a problem hiding this comment.
The host pulls image_tag. github.sha and github.ref_name are the workflow run, not that image.
| inputs: | ||
| image_tag: | ||
| description: GHCR image tag (Git commit SHA) to deploy | ||
| required: true | ||
| type: string |
There was a problem hiding this comment.
Same as production. Staging can deploy a SHA other than the branch that started the workflow.
Checkout the requested ref and tag images with git rev-parse HEAD. Bump actions/checkout to v7. Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>

What it is
Staging and production deploy pre-built GHCR images by commit SHA instead of
:latestor on-server builds.ref; images tagged with resolved SHA;:latestonly on push tomain.github.sha; pull +up --no-build; optional manual deploy withimage_tag.image_tag(SHA); pulls that tag;DASHBOARD_VERSIONfrom deployed commit..env.example; docs updated.Do not run Deploy production Dashboard for this PR.
How to test
1. Publish (no staging, no prod)
Commit SHA: <sha>is the commit you built.https://github.com/orgs/kernelci/packages):dashboard-backend,dashboard-frontend,dashboard-proxyeach have a tag equal to that full SHA.latesttag digest should be unchanged (compare digest before vs after).2. Negative staging (no host change)
gh workflow run "Deploy Staging and E2E" \ --repo kernelci/dashboard \ --ref chore/deploy-sha-images \ -f image_tag=<commit tag from the published image>).actions/checkout(refnot found).3. Manual staging (optional — live staging)
Same workflow as (2), image_tag = SHA from (1). This updates staging.
Deploying staging with IMAGE_TAG=<sha>.ghcr.io/kernelci/dashboard/dashboard-backend:<sha>dashboard-frontendanddashboard-proxy.git describe, not the image tag.4. After merge to
main(live staging; existing CI behavior)Two runs, same merge commit. Actions at
https://github.com/kernelci/dashboard/actions.CI
pull_request). Title is the merge commit.github.sha).Deploying staging with IMAGE_TAG=<sha>equals the header SHA.…/dashboard-*: <same sha>, not:latest.Publish
Commit SHA: <sha>matchesIMAGE_TAGfrom the CI staging job.Discord only fires if staging fails; success is Actions Summary only.
Closes #2087.