Define what a reproducible release means
A CI/CD pipeline connects a code change to verification and deployment. In a small product, the first goal is to know which code is running, what was tested and how to run that same version again. A long chain of tools can obscure that link if no one clearly owns each stage.
Write down a short path: a change comes in, tests run, an artifact is built, the artifact is tested and an identifiable version is released. Next to each stage, note what stops the pipeline and what is kept as evidence. A pipeline that keeps going after an important failure does not earn trust just because it ends with an image.
Choose tests that protect behavior
Tests should match the risk. Input validation, permissions, duplicate actions and calls to external services can matter far more than dozens of tests on static UI text. Add a build check and a smoke test of the artifact in a runtime environment similar to the one you deploy to.
Dependency and secret scanning add information, but they do not replace behavioral tests. Set a policy for handling findings: what blocks a release, who can approve an exception and how it is documented. A temporary exception should be tied to a specific finding and a date for re-review.
Build once and identify the artifact
When you rebuild the artifact between testing and publishing, you can lose the link to what was actually tested. Design the pipeline so the same artifact that was tested is promoted to the publish stage. Record the commit, the version tag and, when using containers, the image digest.
A version tag is useful to people; a digest identifies the image's content. Decide which tags stay fixed and which serve as an update channel. Without that policy, a familiar name like latest can point to different content at different times, making diagnosis and rollback harder.
Limit what the build process is allowed to do
A test stage does not necessarily need publish permissions. Scope permissions per job, keep the handling of untrusted code separate from deployment secrets and review every third-party action. GitHub recommends pinning actions to a full-length commit SHA so you rely on an immutable reference.
Pinning is not the end of maintenance. You still need to review updates and manage upgrades in an orderly way. Also document what happens in a Pull Request build and what happens in a tagged release. Mixing the two paths can grant unapproved code authority it was never meant to have.
Treat deployment and rollback checks as part of delivery
After deploying, check a small user flow and system health, not just that the container started. Define stop conditions and owners for alerts. Database changes need a separate plan: rolling back to old code does not guarantee that the data or schema still fit it.
- Each version is linked to a commit and to the artifact that was tested.
- Required checks stop publishing when they fail.
- Secrets and permissions are granted only to the stage that needs them.
- There is a smoke test and a rollback plan that has been rehearsed in a realistic scenario.