Files
detections/.cursor/skills/deploy/templates/ci_cd_pipeline.md
T
Oleksandr Bezdieniezhnykh 3482173e7d
ci/woodpecker/push/02-build-push Pipeline was successful
chore: sync .cursor from suite
Made-with: Cursor
2026-04-25 19:44:40 +03:00

9.1 KiB

CI/CD Pipeline Template

Save as _docs/04_deploy/ci_cd_pipeline.md.


# [System Name] — CI/CD Pipeline

## Pipeline Overview

| Stage | Trigger | Quality Gate |
|-------|---------|-------------|
| Lint | Every push | Zero lint errors |
| Test | Every push | 75%+ coverage, all tests pass |
| Security | Every push | Zero critical/high CVEs |
| Build | PR merge to dev | Docker build succeeds |
| Push | After build | Images pushed to registry |
| Deploy Staging | After push | Health checks pass |
| Smoke Tests | After staging deploy | Critical paths pass |
| Deploy Production | Manual approval | Health checks pass |

## Stage Details

### Lint
- [Language-specific linters and formatters]
- Runs in parallel per language

### Test
- Unit tests: [framework and command]
- Blackbox tests: [framework and command, uses docker-compose.test.yml]
- Coverage threshold: 75% overall, 90% critical paths
- Coverage report published as pipeline artifact

### Security
- Dependency audit: [tool, e.g., npm audit / pip-audit / dotnet list package --vulnerable]
- SAST scan: [tool, e.g., Semgrep / SonarQube]
- Image scan: Trivy on built Docker images
- Block on: critical or high severity findings

### Build
- Docker images built using multi-stage Dockerfiles
- Tagged with git SHA: `<registry>/<component>:<sha>`
- Build cache: Docker layer cache via CI cache action

### Push
- Registry: [container registry URL]
- Authentication: [method]

### Deploy Staging
- Deployment method: [docker compose / Kubernetes / cloud service]
- Pre-deploy: run database migrations
- Post-deploy: verify health check endpoints
- Automated rollback on health check failure

### Smoke Tests
- Subset of blackbox tests targeting staging environment
- Validates critical user flows
- Timeout: [maximum duration]

### Deploy Production
- Requires manual approval via [mechanism]
- Deployment strategy: [blue-green / rolling / canary]
- Pre-deploy: database migration review
- Post-deploy: health checks + monitoring for 15 min

## Caching Strategy

| Cache | Key | Restore Keys |
|-------|-----|-------------|
| Dependencies | [lockfile hash] | [partial match] |
| Docker layers | [Dockerfile hash] | [partial match] |
| Build artifacts | [source hash] | [partial match] |

## Parallelization

[Diagram or description of which stages run concurrently]

## Notifications

| Event | Channel | Recipients |
|-------|---------|-----------|
| Build failure | [Slack/email] | [team] |
| Security alert | [Slack/email] | [team + security] |
| Deploy success | [Slack] | [team] |
| Deploy failure | [Slack/email + PagerDuty] | [on-call] |

Reference Implementation: Woodpecker CI two-workflow contract

Use this when the project's CI is Woodpecker and the test layout follows the autodev e2e contract from ../../decompose/templates/test-infrastructure-task.md (an e2e/ folder containing Dockerfile, docker-compose.test.yml, conftest.py, requirements.txt, mocks/, fixtures/, tests/).

The contract is two workflows in .woodpecker/, scheduled on the same agent label, with the build workflow gated on a successful test run:

  • .woodpecker/01-test.yml — runs the e2e contract, publishes results/report.csv as an artifact, fails the pipeline on any test failure.
  • .woodpecker/02-build-push.ymldepends_on: [01-test]. Builds the image, tags it ${CI_COMMIT_BRANCH}-${TAG_SUFFIX}, pushes it to the registry. Skipped automatically if test failed.

The agent label is parameterized via matrix: so a single workflow file fans out across architectures: labels: platform: ${PLATFORM} routes each matrix entry to the matching agent. Both workflows for a repo must use the same matrix so test and build run on the same machine and share Docker layer cache. New architectures = new matrix entries; never new files.

Multi-arch matrix conventions

Variable Meaning Typical values
PLATFORM Woodpecker agent label — selects which physical machine runs the entry. arm64, amd64
TAG_SUFFIX Image tag suffix appended after the branch name. arm, amd
DOCKERFILE (only when arches need different Dockerfiles) Path to the Dockerfile for this entry. Dockerfile, Dockerfile.jetson

Most repos use the same Dockerfile for both arches (multi-arch base images handle the rest), so DOCKERFILE can be omitted from the matrix and hardcoded in the build command. Repos with split per-arch Dockerfiles (e.g., detections uses Dockerfile.jetson on Jetson with TensorRT/CUDA-on-L4T) declare DOCKERFILE as a matrix var.

When only one architecture is currently in use, keep the matrix block with a single entry and the second entry commented out — adding a new arch is then a one-line uncomment, not a structural change.

.woodpecker/01-test.yml

when:
  event: [push, pull_request, manual]
  branch: [dev, stage, main]

matrix:
  include:
    - PLATFORM: arm64
      TAG_SUFFIX: arm
    # - PLATFORM: amd64
    #   TAG_SUFFIX: amd

labels:
  platform: ${PLATFORM}

steps:
  - name: e2e
    image: docker
    commands:
      - cd e2e
      - docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from e2e-runner --build
      - docker compose -f docker-compose.test.yml down -v
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  - name: report
    image: docker
    when:
      status: [success, failure]
    commands:
      - test -f e2e/results/report.csv && cat e2e/results/report.csv || echo "no report"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Notes:

  • --abort-on-container-exit shuts the whole compose down as soon as ANY service exits, so a crashed dependency surfaces immediately instead of hanging the runner.
  • --exit-code-from e2e-runner ensures the pipeline's exit code reflects the test runner's, not the SUT's.
  • The report step runs on [success, failure] so the report is always published; without this the CSV is lost on red builds.
  • down -v between runs drops mock state and DB volumes — every test run starts clean.

.woodpecker/02-build-push.yml

when:
  event: [push, manual]
  branch: [dev, stage, main]

depends_on:
  - 01-test

matrix:
  include:
    - PLATFORM: arm64
      TAG_SUFFIX: arm
    # - PLATFORM: amd64
    #   TAG_SUFFIX: amd

labels:
  platform: ${PLATFORM}

steps:
  - name: build-push
    image: docker
    environment:
      REGISTRY_HOST:
        from_secret: registry_host
      REGISTRY_USER:
        from_secret: registry_user
      REGISTRY_TOKEN:
        from_secret: registry_token
    commands:
      - echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
      - export TAG=${CI_COMMIT_BRANCH}-${TAG_SUFFIX}
      - export BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
      - |
        docker build -f Dockerfile \
          --build-arg CI_COMMIT_SHA=$CI_COMMIT_SHA \
          --label org.opencontainers.image.revision=$CI_COMMIT_SHA \
          --label org.opencontainers.image.created=$BUILD_DATE \
          --label org.opencontainers.image.source=$CI_REPO_URL \
          -t $REGISTRY_HOST/azaion/<service>:$TAG .
      - docker push $REGISTRY_HOST/azaion/<service>:$TAG
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

Notes:

  • depends_on: [01-test] is enforced by Woodpecker — a failed 01-test (any matrix entry) skips this workflow.
  • The build workflow does NOT trigger on pull_request events: PRs get test signal only; pushes to dev/stage/main produce images. Avoids polluting the registry with PR images.
  • Replace <service> with the actual service name (matches the registry namespace pattern azaion/<service>).
  • For repos with split per-arch Dockerfiles, add DOCKERFILE: Dockerfile.jetson (or similar) to the matrix entry and substitute ${DOCKERFILE} for Dockerfile in the docker build -f line.

Variations by stack

The contract is language-agnostic because the runner is docker compose. The Dockerfile inside e2e/ selects the test framework:

Stack e2e/Dockerfile runs
Python pytest --csv=/results/report.csv -v
.NET dotnet test --logger:"trx;LogFileName=/results/report.trx" (convert to CSV in a final step if needed)
Node/UI npm test -- --reporters=default --reporters=jest-junit --outputDirectory=/results
Rust cargo test --no-fail-fast -- --format json > /results/report.json

When the repo has only unit tests (no e2e/docker-compose.test.yml), drop the compose orchestration and run the native test command directly inside a stack-appropriate image. Keep the same two-workflow split — 01-test.yml runs unit tests, 02-build-push.yml is unchanged.

Manual-trigger override (test infrastructure not yet validated)

If a repo ships a complete e2e/ layout but the test fixtures are not yet validated end-to-end (e.g., expected-results data is still being authored), gate 01-test.yml on event: [manual] only and add a TODO comment pointing to the unblocking task. The 02-build-push.yml workflow drops its depends_on clause for the manual-only window — an explicit and reversible exception, not a permanent split.