skills/cicd/SKILL.md
CI/CD pipeline design with Harness Drone. Use when: creating .drone.yml pipelines, designing build/test/deploy stages, adding code coverage, publishing artifacts, configuring scheduled runs, applying CI/CD best practices, auditing existing pipelines for gaps, or improving pipeline efficiency and reliability. Covers linting gates, test parallelism, coverage thresholds, Docker image builds, secret management, and deployment strategies.
npx skillsauth add michaelsvanbeek/personal-agent-skills cicdInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
.drone.yml pipelines.drone.yml at the repo root.kind: pipeline, type: docker..drone.yml is version-controlled, reviewed, and tested like application code.from_secret). No env var values in pipeline definitions.Separate concerns into distinct pipelines (Drone --- document separators):
| Pipeline | Trigger | Purpose | |----------|---------|---------| | lint-and-test | push, pull_request on main | Quality gate — must pass before merge | | build-and-publish | push to main (post-merge) | Build artifacts, push images | | deploy | push to main OR manual promote | Ship to staging/production | | scheduled | cron | Recurring tasks (backups, scans, syncs) |
# Pipeline 1: Gate — blocks merge if it fails
kind: pipeline
name: lint-and-test
trigger:
branch: [main]
event: [push, pull_request]
# Pipeline 2: Build — only on merged code
kind: pipeline
name: build-and-publish
trigger:
branch: [main]
event: [push]
depends_on: [lint-and-test]
# Pipeline 3: Deploy — after build succeeds
kind: pipeline
name: deploy
trigger:
branch: [main]
event: [push]
depends_on: [build-and-publish]
Linting is the fastest, cheapest check. It runs first and blocks everything else.
steps:
- name: lint
image: python:3.12-slim
commands:
- pip install ruff
- ruff check .
- ruff format --check .
Rules:
|| true, no allow_failure.| Language | Lint step |
|----------|-----------|
| Python | ruff check . && ruff format --check . |
| TypeScript | npx prettier --check . && npx eslint . |
| YAML / Markdown | npx prettier --check "**/*.{yml,yaml,md}" |
| Dockerfile | hadolint docker/*.Dockerfile |
Tests run after lint passes. Use parallel steps per test target to maximize throughput.
- name: test-shared
image: python:3.12-slim
commands:
- pip install pytest pytest-cov python-dotenv
- pytest shared/ -v --tb=short
- name: test-my-script
image: python:3.12-slim
commands:
- pip install pytest pytest-cov -r scripts/my_script/requirements.txt
- >-
pytest scripts/my_script/ -v --tb=short
--cov=scripts/my_script
--cov-report=term-missing
--cov-fail-under=80
Rules:
requirements.txt.--tb=short for concise failure output in CI.--cov-fail-under=80 to enforce minimum coverage (see coverage section).@pytest.mark.skip unless there's a tracked issue.Drone runs steps within a pipeline in parallel by default. Steps that depend on each other need explicit depends_on:
steps:
- name: lint
...
- name: test-unit
depends_on: [lint]
...
- name: test-integration
depends_on: [lint]
...
Lint → then unit + integration in parallel → then build (depends on both).
- name: test-with-coverage
image: python:3.12-slim
commands:
- pip install pytest pytest-cov
- >-
pytest
--cov=src
--cov-report=term-missing
--cov-report=xml:coverage.xml
--cov-fail-under=80
| Rule | Value | Rationale |
|------|-------|-----------|
| Minimum threshold | 80% | Floor — not a target. New code should aim higher. |
| Fail the build | Yes | --cov-fail-under=80 makes the step exit non-zero |
| Report format | term-missing + xml | Terminal for quick review, XML for tooling integration |
| What counts | Tested lines / total lines | Branch coverage is ideal but line coverage is the practical minimum |
.coveragerc or pyproject.toml.[tool.coverage.run]
source = ["src", "scripts"]
omit = ["*/test_*", "*/__pycache__/*"]
[tool.coverage.report]
fail_under = 80
show_missing = true
Use the Drone Docker plugin for building and pushing images:
- name: build-and-push
image: plugins/docker
settings:
dockerfile: docker/my-service.Dockerfile
repo: ${DRONE_REPO_OWNER}/my-service
tags:
- latest
- ${DRONE_COMMIT_SHA:0:8}
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
branch: [main]
event: [push]
Rules:
latest and a commit-specific tag (short SHA or semver).For internal Python packages:
- name: build-package
image: python:3.12-slim
commands:
- pip install build
- python -m build
when:
branch: [main]
event: [push]
| Strategy | When to use | Risk | |----------|-------------|------| | Direct deploy | Homelab scripts, internal tools | Low — rollback is a git revert + re-deploy | | Blue-green | Services with zero-downtime requirement | Medium — need two environments | | Canary | User-facing APIs with high traffic | Low — gradual rollout, auto-rollback | | Manual promote | Production deployments requiring approval | Lowest — human gate |
For homelab scripts running on a schedule, deployment means the cron pipeline pulls the latest image:
---
kind: pipeline
name: deploy-to-homelab
trigger:
branch: [main]
event: [push]
depends_on: [build-and-publish]
steps:
- name: deploy
image: appleboy/drone-ssh
settings:
host:
from_secret: deploy_host
username:
from_secret: deploy_user
key:
from_secret: deploy_ssh_key
script:
- cd /opt/homelab && docker compose pull && docker compose up -d
For AWS Lambda services:
- name: deploy-staging
image: node:20-slim
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
commands:
- npm ci
- npx serverless deploy --stage dev
when:
branch: [main]
event: [push]
- name: deploy-production
image: node:20-slim
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
commands:
- npm ci
- npx serverless deploy --stage prod
when:
event: [promote]
target: [production]
Promote to production manually: drone build promote <repo> <build> production
from_secret: key_name.drone secret add CLI or Drone UI, never in .drone.yml.environment:
API_KEY:
from_secret: api_key # correct
# API_KEY: "sk-1234..." # NEVER — exposed in version control
For recurring tasks (backups, health checks, cleanup):
---
kind: pipeline
name: backup-daily
trigger:
event: [cron]
cron: [backup-daily]
steps:
- name: run-backup
image: python:3.12-slim
environment:
BACKUP_SOURCE:
from_secret: backup_source
commands:
- pip install -r scripts/backup/requirements.txt
- python -m scripts.backup.backup
Rules:
--- documents.| Language | Image | Notes |
|----------|-------|-------|
| Python | python:3.12-slim | Minimal, fast pull |
| Node.js | node:20-slim | For Serverless, frontend builds |
| Docker builds | plugins/docker | Drone's Docker build plugin |
| SSH deploy | appleboy/drone-ssh | Remote execution via SSH |
| General CLI | alpine:3.19 | When you just need curl, jq, etc. |
Drone Docker pipelines don't have built-in caching. Options:
/tmp/cache mounted) — works for single-node setups.plugins/docker with cache_from. - name: notify-failure
image: plugins/slack
settings:
webhook:
from_secret: slack_webhook
channel: ci-alerts
template: >
{{repo.name}} build {{build.number}} failed on {{build.branch}}.
{{build.link}}
when:
status: [failure]
Integrate performance testing into CI with a tiered approach (see performance-testing and ui-performance skills for full details):
| Trigger | Test type | Purpose | |---------|-----------|----------| | Every PR | Smoke (k6, 1 min, 5 VUs) | Catch gross regressions cheaply | | Release tag | Load + Stress (k6, 15 min) | Validate capacity before shipping | | Release tag | Lighthouse audit | Enforce frontend performance score ≥ 80 | | Weekly cron | Soak (k6, 1-4 hours) | Detect memory leaks and connection exhaustion |
- name: perf-smoke
image: grafana/k6:latest
commands:
- k6 run --duration=1m --vus=5 tests/perf/smoke.js
depends_on: [test]
Add a dedicated release pipeline that triggers only on semver tags. This separates release builds from regular CI (see release-management skill for full versioning conventions):
---
kind: pipeline
name: release
trigger:
event: [tag]
ref: ["refs/tags/v*"]
steps:
- name: validate-tag
image: alpine:3.19
commands:
- |
TAG="${DRONE_TAG}"
if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
echo "ERROR: Tag '$TAG' does not match semver format"
exit 1
fi
echo "Valid semver tag: $TAG"
- name: lint
image: python:3.12-slim
commands:
- pip install ruff
- ruff check .
- ruff format --check .
depends_on: [validate-tag]
- name: test
image: python:3.12-slim
commands:
- pip install pytest pytest-cov
- pytest -v --tb=short --cov=src --cov-fail-under=80
depends_on: [validate-tag]
- name: build-and-publish
image: plugins/docker
settings:
repo: ${DRONE_REPO_OWNER}/my-service
tags:
- latest
- ${DRONE_TAG}
- ${DRONE_TAG##v}
username:
from_secret: docker_username
password:
from_secret: docker_password
depends_on: [lint, test]
| Rule | Rationale |
|------|-----------|
| Validate tag format | Reject accidental or malformed tags before building |
| Re-run lint + test | Tag may point to a commit that wasn't the latest CI run |
| Tag with semver | v1.5.0 and 1.5.0 alongside latest for flexibility |
| Skip latest for pre-releases | v2.0.0-beta.1 should not be tagged latest |
| Version-file validation | Ensure pyproject.toml / package.json version matches the tag |
If the project stores a version in a file, add a validation step to prevent drift:
- name: validate-version
image: python:3.12-slim
commands:
- |
FILE_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
TAG_VERSION="${DRONE_TAG#v}"
if [ "$FILE_VERSION" != "$TAG_VERSION" ]; then
echo "ERROR: pyproject.toml version ($FILE_VERSION) != tag ($TAG_VERSION)"
exit 1
fi
depends_on: [validate-tag]
Use this checklist when reviewing any .drone.yml:
| Check | What to verify |
|-------|---------------|
| Fail fast | Lint/format steps run before test steps |
| No secrets in YAML | All credentials use from_secret |
| Pinned images | Step images use specific tags, not :latest |
| Test coverage | --cov-fail-under is set and enforced |
| PR safety | Build/deploy steps have when: event: [push] (not on PRs) |
| Cron isolation | Scheduled pipelines use separate --- documents |
| Dependencies | depends_on ensures correct ordering between pipelines |
| Notifications | Failure notifications are configured |
| Minimal installs | Each step installs only what it needs |
| Short SHA tags | Docker images tagged with commit SHA, not just latest |
| Release pipeline | Tag-triggered pipeline exists for semver releases |
| Tag validation | Release pipeline validates semver format before building |
development
TypeScript coding standards and type safety conventions. Use when: creating TypeScript files, defining interfaces and types, writing type-safe code, reviewing TypeScript for type correctness, auditing a codebase for type safety gaps, eliminating any or ts-ignore usage, or improving strict-mode compliance. Covers strict typing, avoiding any and ts-ignore, discriminated unions, Zod runtime validation, immutability patterns, and proper type definitions.
testing
Writing clear, actionable tickets in any issue tracker (Jira, Linear, GitHub Issues, ServiceNow, etc.). Use when: creating epics, stories, tasks, bugs, or spikes; writing acceptance criteria; decomposing work for a sprint; linking dependencies between tickets; auditing backlog items for clarity; or coaching a team on ticket quality. Covers title conventions, description templates, acceptance criteria, decomposition rules, dependency linking, and org-specific pluggable configuration.
development
Testing strategy, patterns, and evaluation for software and LLM/AI systems. Use when: writing tests, choosing test boundaries, designing test data, structuring test suites, evaluating LLM outputs, building evaluation pipelines, setting coverage thresholds, auditing test coverage gaps in existing projects, or improving test quality and structure.
development
Writing effective status updates for different audiences and cadences. Use when: writing a weekly status update, preparing a monthly summary, drafting a quarterly review, sending updates to leadership, sharing progress with stakeholders, or improving the clarity and impact of team communications. Covers weekly, monthly, and quarterly formats tailored for upward, lateral, and downward communication.