Reuse, release, and deploy
Build a release from one artifact
Promote the exact tested artifact instead of rebuilding independently in the deployment job.
Tests exercise one build. Production should ship those same bytes, not a fresh build after approval with a different dependency tree.
Build once, promote many is the pattern. The artifact is the contract between CI and deploy. If staging and production each rebuild, you lose that contract.
I have seen teams pass review on commit abc123 while production silently built from abc123 plus whatever npm resolved that afternoon. The diff was one patch version of a transitive dependency. No human tested that combination.
Upload after the authoritative build
jobs:
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- run: shasum -a 256 dist/index.html
- uses: actions/upload-artifact@v4
with:
name: site-${{ github.sha }}
path: dist/
Record the digest in the log. That hash is your receipt.
Download the same artifact to deploy
deploy-staging:
needs: package
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: site-${{ github.sha }}
path: dist/
- run: ./scripts/deploy.sh dist/ staging
deploy-production:
needs: deploy-staging
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: site-${{ github.sha }}
path: dist/
- run: ./scripts/deploy.sh dist/ production
Staging and production deploy the tarball that passed tests. No second npm run build hidden after approval.
Compare hashes across environments
After a release, compare artifact hashes in build logs, staging deploy records, and production deploy records. They should match.
A realistic bug: production runs npm install again because someone thought it was faster. A patch release of a dependency slips in untested.
Tag releases should upload the same artifact GitHub attaches to the release, not rebuild inside the release job. Container images follow the same idea: build and scan once, promote the digest.
If you must rebuild for a platform-specific binary, document why and keep a separate artifact per platform. Do not pretend one generic rebuild equals the tested one.
Try this on your own project: remove any npm run build step from deploy jobs and rely on the artifact alone.
Lesson completed