Use when working on the deployment flow — image tagging strategy, Flux configuration, modifying deploy workflows, rollback procedures, or anything involving how code becomes a running container on the VPS
Code becomes a running container through a specific chain: push → test → secrel → deploy. The image tag is computed once and flows through the entire chain. Know which deploy model you're in. Don't break the chain. Don't create shortcuts.
secrel.yml or deploy-compose.yml workflowsThe tag sha-${GITHUB_SHA::7} is computed once in secrel.yml at the set-tag step and passed as a workflow output.
secrel.yml (compute)
└─ steps.set-tag: echo "tag=sha-${GITHUB_SHA::7}"
│
├─ Used for: docker build --tag (build-scan-push job)
├─ Used for: trivy scan target
├─ Used for: syft SBOM target
├─ Used for: docker push tags
│
└─ Output: jobs.build-scan-push.outputs.image-tag
│
└─ Consumed by: deploy-compose.yml (inputs.image-tag)
│
└─ Used for: docker pull + IMAGE_TAG env var on VPS
The tag is never recomputed. It originates in one place and is passed by reference everywhere else.
| Phase 1-2 (Current) | Phase 4 (Target) | |
|---|---|---|
| Mechanism | SSH to VPS via deploy-compose.yml | Commit tag to repo, Flux applies |
| Trigger | secrel job outputs tag → deploy job SSHs | secrel job outputs tag → workflow commits to infra repo |
| Rollback | export IMAGE_TAG=sha-<old> && docker compose up -d | git revert <commit> in infra repo |
| Secrets needed | VPS_HOST, VPS_USER, DEPLOY_SSH_KEY | GitHub token (to push to infra repo) |
| VPS access | Direct SSH from GitHub Actions | None — Flux pulls from repo |
Don't mix models. In Phase 1-2, deploys go through SSH. In Phase 4, deploys go through Git commits. The transition happens when Flux is configured and proven stable.
push → test → secrel → deploy
Every link depends on the previous:
needs: test)needs: secrel)if: github.ref == 'refs/heads/main')Never create shortcuts:
| Shortcut | Why It's Dangerous |
|---|---|
| Deploy without secrel | Unscanned image in production |
| Deploy from branch | Untested code in production |
Manual docker pull on VPS | Bypasses the entire chain, no audit trail |
| Recompute tag in deploy step | Risk of mismatch with what secrel scanned |
Use latest tag for deploy | No way to know what's actually running |
Before changing any workflow file:
set-tag to the running container without a gap?| Pattern | Problem | Correct Approach |
|---|---|---|
echo "tag=sha-${GITHUB_SHA::7}" in multiple places | Drift risk — if one changes, they mismatch | Compute once in secrel, pass as output |
Using latest for deploys | Can't tell what's running, can't rollback reliably | Always use sha-XXXXXXX |
| Hardcoding a tag | Breaks automation, gets stale | Use the pipeline output |
| Tagging with branch name | Branches are mutable, same tag points to different images over time | SHA-based tags are immutable |
| Recomputing tag in deploy workflow | May not match what secrel scanned | Use inputs.image-tag passed from secrel |
Phase 1-2 (Compose):
cd /opt/infra
export IMAGE_TAG=sha-<previous-good-sha>
docker compose -f compose/docker-compose.yml -f compose/enforcer/docker-compose.yml up -d enforcer-backend
Phase 4 (GitOps):
# Revert the commit that updated the image tag
git revert <commit-sha>
git push origin main
# Flux detects the change and rolls back automatically