Understanding CI/CD Stages
What Is a Stage in Simple Terms
A stage is one chapter in the pipeline story. Build is chapter one. Test is chapter two. If chapter two fails, you do not proceed to chapter three. This gate behaviour is the entire point of stages: a failed test should prevent a deployment, not just be noted and ignored.
Think of stages as quality checkpoints on an assembly line. A car does not move to the paint shop until the body shop confirms the frame is correct. Code does not move to deployment until the test stage confirms it works.
How It Works
+------------------------------------------+| Stage 1: Build || Jobs: compile, docker-build, push-image |+------------------------------------------+ | all jobs pass? / \ yes no | | v v+------------------+ +------------------+| Stage 2: Test | | PIPELINE BLOCKED || Jobs: unit-test | | Deploy blocked || integration-test | | Notify engineer || coverage-check | +------------------++------------------+ | all jobs pass? | v+------------------------------------------+| Stage 3: Deploy || Jobs: deploy-staging, smoke-test |+------------------------------------------+Stage definition in GitHub Actions (jobs with needs):
jobs: build: ## Stage 1 runs-on: ubuntu-latest steps: - run: docker build . unit-test: ## Stage 2 (parallel with lint) needs: build runs-on: ubuntu-latest steps: - run: npm test lint: ## Stage 2 (parallel with unit-test) needs: build runs-on: ubuntu-latest steps: - run: npm run lint security-scan: ## Stage 3 (after both Stage 2 jobs) needs: [unit-test, lint] runs-on: ubuntu-latest steps: - run: trivy image payment-api:${{ github.sha }} deploy: ## Stage 4 needs: security-scan runs-on: ubuntu-latest steps: - run: ./deploy.shStage definition in GitLab CI:
stages: - build - test - scan - deploy build-image: stage: build script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . unit-tests: stage: test script: - npm test trivy-scan: stage: scan script: - trivy image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA deploy-staging: stage: deploy script: - ./deploy.sh stagingPractical Commands
## GitHub Actions -- rerun a failed stage onlygh run rerun RUN_ID --failed ## GitLab -- retry a specific failed jobglab ci retry JOB_ID ## Jenkins -- replay a failed build## In Jenkinsfile: use retry(3) step for transient failures## stage('Test') {## retry(3) { sh 'npm test' }## }Troubleshooting
| Symptom | Check | What to Look For |
|---|---|---|
| Stage skipped unexpectedly | Review job conditions | if/when conditions blocking execution |
| Stage hangs with no output | Check timeout settings | Job timeout too short or process waiting |
| Parallel jobs causing conflicts | Check shared state | Jobs writing to same files or ports |
PLACEMENT PRO TIP**Tip:** Name stages after their purpose, not their tools. Call it Test not jest. Call it Scan not trivy. Tools change; stages do not. A pipeline named after tools requires renaming when you switch from Jest to Vitest.
REMEMBER THIS**Remember:** Every stage is a gate. A gate only has value if it actually blocks. If your Test stage is allowed to fail without blocking deployment, it is not a gate — it is decoration. Every stage must be configured to block the pipeline on failure.
COMMON MISTAKE / WARNING**Security:** Stages that deploy to production must require explicit human approval via environment protection rules. An automated pipeline that deploys to production without a gate is a single point of failure — a bug in CI configuration can push broken code to production without anyone reviewing it.
COMMON MISTAKE / WARNING**Common Mistake:** Creating a single monolithic stage that runs build, test, scan, and deploy all at once. When that stage fails, you cannot tell which part failed without reading all the logs. Split concerns into distinct named stages so failures are immediately visible and targeted retries are possible.