Skip to content

Jenkins

Purpose

The full build → scan → image → deploy pipeline - the only path that ever pushes an image to a registry or deploys to dev/staging/production.

File

Jenkinsfile (declarative pipeline, one agent labeled docker).

Prerequisites for the Jenkins agent

Docker, .NET 8 SDK, Node 20+, Helm, kubectl.

Configuration (environment block)

REGISTRY            = 'ghcr.io'
REGISTRY_NAMESPACE  = 'thirunk'
BACKEND_IMAGE        = 'ghcr.io/thirunk/akshayabazaar-backend'
FRONTEND_IMAGE       = 'ghcr.io/thirunk/akshayabazaar-frontend'
HELM_RELEASE         = 'akshayabazaar'
HELM_CHART_PATH      = 'helm/akshayabazaar'
SCAN_FAIL_SEVERITY   = 'CRITICAL'

No secret value ever appears in this file - every credential is a Jenkins Credentials-store ID, resolved only at runtime via withCredentials:

Credentials ID What it is
container-registry-creds Registry username/token (GHCR)
k3s-kubeconfig-dev kubeconfig for the dev deploy
k3s-kubeconfig-staging kubeconfig for the staging deploy
k3s-kubeconfig-production kubeconfig for the production deploy

Pipeline parameters

Parameter Choices Purpose
ACTION Deploy (default) / Rollback Deploy the current commit, or roll an already-deployed environment back
ROLLBACK_ENV staging / production / dev ACTION=Rollback only
ROLLBACK_REVISION (blank = previous revision) ACTION=Rollback only - helm history <release> -n <namespace> shows what's available

Stages (Deploy path)

Checkout           → resolves IMAGE_TAG (immutable short commit SHA), BUILD_VERSION, branch/tag flags
Backend             → restore, build, test (xUnit, non-zero exit stops here), dependency scan
                       (dotnet list package --vulnerable - fails only on SCAN_FAIL_SEVERITY findings)
Frontend            → npm ci, test (vitest), dependency scan (npm audit --audit-level), production build
Docker image build   → both images, VCS_REF/BUILD_VERSION/BUILD_DATE/VCS_URL build args
Security/image scan  → Trivy, fails on CRITICAL (fixable) findings; full HIGH+ report always archived
Tag images            → immutable commit-SHA tag (always) + a floating <env>-latest pointer
                        (human browsing only, never what Helm deploys with) + semver tag on a release
Push images            → only if SHOULD_DEPLOY (master or a release tag)
Deploy to dev           → only on master - helm upgrade --install ... values-dev.yaml --atomic
Helm deploy to staging   → only on a release tag - helm upgrade --install ... values-staging.yaml --atomic
Smoke tests               → scripts/smoke-test.sh against staging - failure stops here, before promotion
Promote to production      → manual approval gate (submitter: release-managers, 24h timeout),
                              then helm upgrade --install ... values-production.yaml --atomic
Production smoke tests      → scripts/smoke-test.sh against production

"Promote, don't rebuild": the exact same image tag that passed staging's smoke test is what gets deployed to production - never a second build. This is what makes the manual approval gate meaningful (approving this specific, already-tested artifact, not a promise to rebuild it correctly later).

Rollback path (ACTION=Rollback)

Short-circuits the rest of the pipeline entirely:

helm history akshayabazaar -n akshayabazaar-<env>
helm rollback akshayabazaar <revision> -n akshayabazaar-<env> --wait --timeout 5m
followed by a smoke test against that environment. Helm's own release history (kept automatically, last 10 revisions by default) is the rollback mechanism - nothing here deletes or overwrites a prior revision's recorded image tag, and the registry never deletes a SHA-tagged image either.

--atomic: the first line of rollback defense

Every helm upgrade --install in this pipeline passes --atomic - if the rollout doesn't reach Ready within --timeout, Helm automatically rolls the release back to its previous revision before the pipeline stage even returns. The explicit ACTION=Rollback path above is for a deploy that succeeded but is behaviorally wrong, not for a deploy that simply timed out.

Security/dependency/image scanning

  • Backend: dotnet list package --vulnerable --include-transitive - fails the build only on a CRITICAL finding (text-matched, since this command always exits 0 regardless of findings); High/Moderate findings are logged, archived, and do not block.
  • Frontend: npm audit --omit=dev --audit-level=critical - uses npm's own severity-threshold exit code natively.
  • Images: Trivy (aquasec/trivy image), --severity CRITICAL --exit-code 1 --ignore-unfixed - fails only on a fixable CRITICAL vulnerability; a full HIGH+CRITICAL report is always archived regardless of pass/fail.

Validation

# Linux/Bash - the exact commands this pipeline runs, for local reproduction
cd backend && dotnet restore ECommerce.sln && dotnet build ECommerce.sln --configuration Release --no-restore
dotnet test tests/ECommerce.Tests/ECommerce.Tests.csproj --configuration Release --no-build
cd ../frontend && npm ci && npm test && VITE_API_URL=/api npm run build

Troubleshooting

  • Pipeline failed at a gated stage - nothing later ran; the post { failure } block always points at which check stopped it (tests / scan / smoke test / rollout timeout) - check that specific stage's log, not the overall job summary.
  • Smoke test failure blocking promotion - scripts/smoke-test.sh is intentionally environment-agnostic HTTP-only checks; see Operations → Verification for the fuller Kubernetes-aware equivalent (verify-staging.ps1) to narrow down why.
  • Promote to production timed out (24h) - re-running the job re-enters the same approval gate; nothing was deployed while it waited.

Rollback / recovery

See "Rollback path" above, and Operations → Rollback for the Helm-level detail of what a rollback does and does not undo.

Security considerations

  • Images are promoted, never rebuilt per environment - eliminates "it worked in staging because it was a different build" as a failure mode.
  • Production deploys require a named-group human approval (submitter: release-managers) - never automatic, regardless of how green every earlier stage was.
  • No secret is ever written to a log, an archived artifact, or this file - only Credentials-store IDs, resolved at runtime and scoped to the single stage that needs them (withCredentials blocks).