CI/CD pipelines: GitHub Actions, GitLab CI
CI/CD pipelines: GitHub Actions, GitLab CI
1) CI/CD Task and Platform Space
CI/CD is the continuous assembly, testing, and delivery of changes from the repository to production environments. Objectives:- Speed and predictability of releases (short lead time).
- Quality (auto tests, static/dynamic analysis).
- Supply chain security (artifact signing, access control).
- Reliability (canary deposits, fast rollback).
- Observability (trace and metrics at each stage).
Key principles: "pipeline as code," immutable artifacts, "build once - run many," "shift-left security," "left privilege," deterministic assemblies.
2) Architectural patterns of conveyors
Stage-gate: build → test → security → package → deploy → post-deploy checks.
Fan-out/Fan-in: parallel matrix assemblies (languages/platforms) with concatenation of results.
Promotion: the same artifact is promoted through the environment (dev → stage → prod), and not reassembled.
Trunk-based + short branches: drift minimization, automated checks on PR/MR.
Reusable: reusable workflows GitLab: includes/child-pipelines).
GitOps (optional): separation of "assembly" and "delivery" (Argo CD/Flux monitor declarative repo environments).
3) Supply chain security
Identification: OIDC federation from runner to cloud (without long-lived keys).
Secrets: centralized storage, context restriction, prohibition on logging.
Signature of artifacts/containers (cosign/Sigstore), verification of signature in admission control.
SBOM (CycloneDX/SPDX) and SCA, SAST/DAST/Container Scan - "mandatory gate."
Politicians: OPA/Conftest for IaC/manifestos, "no latest," banning privileged containers.
Isolation of runners: prod-runners in a private network, separate outgoing access from the public Internet.
4) GitHub Actions - Structure and Practices
4. 1 workflows structure
`.github/workflows/.yml` — триггеры (`on: push, pull_request, schedule, workflow_call`).
Reusable workflows for standardization (linter, SCA, container assembly, deployment).
4. 2 Example: multi-stage pipeline with OIDC and image signature
yaml name: ci-cd
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
id-token: write # для OIDC contents: read packages: write
jobs:
build_test_matrix:
runs-on: ubuntu-latest strategy:
matrix:
node: [18, 20]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4 with: { node-version: ${{ matrix. node }} }
- name: Cache npm uses: actions/cache@v4 with:
path: ~/.npm key: npm-${{ runner. os }}-${{ matrix. node }}-${{ hashFiles('/package-lock. json') }}
- run: npm ci
- run: npm run lint && npm test -- --ci
docker_build_sign:
runs-on: ubuntu-latest needs: build_test_matrix steps:
- uses: actions/checkout@v4
- name: Login to GHCR uses: docker/login-action@v3 with:
registry: ghcr. io username: ${{ github. actor }}
password: ${{ secrets. GITHUB_TOKEN }}
- name: Build image run:
docker build --pull --no-cache -t ghcr. io/org/app:${{ github. sha }}.
docker push ghcr. io/org/app:${{ github. sha }}
- name: Generate SBOM uses: anchore/syft-action@v0 with:
image: ghcr. io/org/app:${{ github. sha }}
format: cyclonedx-json output-file: sbom. json
- name: Cosign sign (OIDC)
uses: sigstore/cosign-installer@v3
- name: Sign image run:
cosign sign ghcr. io/org/app:${{ github. sha }} \
--yes \
--identity-token $ACTIONS_ID_TOKEN_REQUEST_TOKEN \
--rekor-url https://rekor. sigstore. dev
deploy_stage:
runs-on: ubuntu-latest needs: docker_build_sign environment:
name: stage url: https://stage. example. com steps:
- uses: actions/checkout@v4
- name: Assume cloud role via OIDC uses: aws-actions/configure-aws-credentials@v4 with:
role-to-assume: arn:aws:iam::123456789012:role/github-deployer aws-region: eu-central-1
- name: Helm deploy (canary 10%)
run:
helm upgrade --install app charts/app \
--set image. tag=${{ github. sha }} \
--set canary. enabled=true --set canary. traffic=10
- name: Smoke checks run:./scripts/smoke. sh
promote_prod:
runs-on: ubuntu-latest needs: deploy_stage environment:
name: production url: https://app. example. com concurrency: prod-release steps:
- name: Manual approval gate run: echo "Requires environment approvers in repo settings"
- name: Promote canary → 100% (blue-green)
run:
helm upgrade --install app charts/app \
--set image. tag=${{ github. sha }} \
--set canary. enabled=false
- name: Post-deploy checks & rollback on SLO breach run:./scripts/verify_or_rollback. sh
Keys:
- 'permissions' are minimized, 'id-token: write'is enabled for OIDC.
- Environments with approvers and URL, 'concurrency' protects against racing.
- Canary inclusion of traffic and automatic rollback over SLO.
4. 3 Reusable workflow (call example)
yaml jobs:
security_suite:
uses: org/.github/.github/workflows/security. yml@v1 with:
severity_threshold: high
5) GitLab CI - Structure and Practices
5. 1 Basic structure
`.gitlab-ci. yml 'at the root; key entities: 'stages', 'jobs', 'rules', 'needs', 'artifacts', 'environments', 'manual'.
Reuse: 'include:' (local/remote patterns), child/parent pipelines for complex monorepos.
5. 2 Example: matrix, cache, signature, environments and approvals
yaml stages: [lint, test, build, security, deploy]
variables:
DOCKER_TLS_CERTDIR: "" # docker: dind acceleration
IMAGE_TAG: $CI_COMMIT_SHA
lint:
stage: lint image: node:20 script:
- npm ci
- npm run lint cache:
key: "npm-${CI_COMMIT_REF_SLUG}"
paths: [node_modules/]
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
test:
stage: test image: node:20 parallel:
matrix:
- NODE_VERSION: ["18", "20"]
script:
- nvm install $NODE_VERSION true
- npm ci
- npm test -- --ci artifacts:
when: always reports:
junit: report. xml
build_image:
stage: build image: docker:26. 1 services: [ "docker:26. 1-dind" ]
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$IMAGE_TAG.
- docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG artifacts:
expire_in: 1 week paths: [ "sbom. json" ]
after_script:
- syft $CI_REGISTRY_IMAGE:$IMAGE_TAG -o cyclonedx-json > sbom. json
security_scans:
stage: security image: alpine:3. 20 script:
- trivy image --exit-code 0 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$IMAGE_TAG rules:
- if: '$CI_COMMIT_BRANCH == "main"'
deploy_stage:
stage: deploy image: bitnami/kubectl:1. 30 environment:
name: stage url: https://stage. example. com on_stop: stop_stage script:
- kubectl set image deploy/app app=$CI_REGISTRY_IMAGE:$IMAGE_TAG -n stage
-./scripts/smoke. sh needs: [build_image, security_scans]
when: manual allow_failure: false
stop_stage:
stage: deploy image: bitnami/kubectl:1. 30 environment:
name: stage action: stop script:
- kubectl rollout undo deploy/app -n stage
deploy_prod:
stage: deploy image: alpine/k8s:1. 30. 2 environment:
name: production url: https://app. example. com rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual allow_failure: false script:
-./scripts/canary_traffic. sh 10
-./scripts/verify_or_rollback. sh
Keys:
- `parallel. matrix 'simulates matrix assemblies.
- 'artifacts' + test reports.
- Environments with 'on _ stop', manual 'when: manual' for approvals.
- DIND for building an image (better - Kaniko/BuildKit in the k8s runner).
5. 3 Child pipelines and include for monorepos
yaml include:
- local:.gitlab/ci/includes/security. yml
- project: org/platform/pipelines file: /k8s/deploy. yml ref: v1
stages: [prepare, component_a, component_b, deploy]
component_a:
stage: component_a trigger:
include:.gitlab/ci/component_a. yml strategy: depend
component_b:
stage: component_b trigger:
include:.gitlab/ci/component_b. yml strategy: depend
6) Monorepository and multiservice
Directory-based ownership: CODEOWNERS and scoped tests by path.
Incremental builds: identify affected packages/charts; cache by path keys and lock files.
Dynamic pipelines: child-pipelines/' workflow _ call 'run only for changed components.
Versioning: semver for each module, changelog at the release stage.
7) Caching and acceleration
Content address caches (hashFiles/lockfile).
Separate cache for dependencies and artifacts.
Pre-warm runner images (toolchains, SDK).
Local packet mirrors (npm/pip/maven) and container registry cache.
8) Release strategies and rollback
Canary: gradually increasing the percentage of traffic; auto-stop during SLO degradation.
Blue-Green: parallel stacks, instant switching.
Shadow: duplicate requests without affecting the client.
Feature flags: rollout at flag level, not release level.
Rollback: clear one-button scripts, the artifact version is stored in the release metadata.
9) Infrastructure and GitOps
IaC: Terraform/Ansible/Helm are managed in separate repos; policy-as-code as a gate.
GitOps-contour: Argo CD/Flux observe repo with manifestos of environments; the pipeline only creates an artifact and updates versions in Git.
Advantages: clear history of environment changes, idempotency, standard rollbacks via Git.
10) Observability of CI/CD
DORA metrics: depletion rate, time from commit to production, failure rate, MTTR.
Telemetry: time of job queues, duration of stages, hit-rate of the cache, frequency of flaky tests.
Security logs: who initiated the release, which gates were passed, which exceptions were issued.
11) Access control and approvals
Branch protection and mandatory checks.
Environment-approvals: separate approvers lists on stage/prod.
JIT access for manual steps, session logging.
Separation of duties: different roles for "writes code," "approves," "releases."
12) Frequent errors (anti-patterns)
Long-lived cloud keys in repo secrets instead of OIDC roles.
Assembling different artifacts for stage and prod (violation of "build once").
'latest'tags and mutable images.
Publishing secrets in step logs (non-disabled masking).
One common public-runner for production deployments.
Lack of security "gates" (SAST/SCA/Policy) and post-deploy checks.
13) Implementation checklist (0-60 days)
0-15 days
Configure trunk-based, PR/MR rules, mandatory static checks.
Enable OIDC federation to the cloud; minimal'permissions'.
Post runners: public - for CI, private - for CD.
16-30 days
Add SBOM, image signature; Cluster - Signature Verification.
Enter canary/blue-green; auto-rollback by SLO.
Cache of dependencies and artifacts, pre-warm images.
31-60 days
Separate assembly and delivery (GitOps), policy-as-code gate.
Establish DORA metrics and alerts for the degradation of pipelines.
Template pipelines (reusable/child) for all services.
14) Practical reliability tips
Support small, fast pipelines (10-12 minutes before the PR signal).
Kill flaky tests: quarantine tags + parallel fix.
Do not mix CI artifacts and release artifacts; store metadata (commit, time, SBOM, signatures).
Give developers local scripts identical to pipeline steps (dev-prod parity).
15) Templates for reuse
15. 1 GitHub Actions - security reusable workflow (simplified)
yaml name: security-suite on:
workflow_call:
inputs:
severity_threshold:
type: string required: false default: high jobs:
sast_sca:
runs-on: ubuntu-latest steps:
- uses: actions/checkout@v4
- run:./sec/sast. sh --threshold ${{ inputs. severity_threshold }}
- run:./sec/sca. sh --format cyclonedx-json --out sbom. json artifacts: # if using actions/upload-artifact
- sbom. json
15. 2 GitLab - include template deploy (simplified)
yaml
.deployment_template:
image: alpine/k8s:1. 30 script:
- helm upgrade --install $APP charts/$APP --set image. tag=$IMAGE_TAG rules:
- if: '$CI_COMMIT_BRANCH == "main"'
16) Conclusion
GitHub Actions and GitLab CI provide mature mechanisms for a fast and secure code → prod loop. The key to success is standardization and security: OIDC instead of keys, signature and SBOM, quality gates, a single artifact with promotion, GitOps delivery and observability through DORA. Build pipelines as a product: Measure, simplify, speed up - and releases will become a chore, not an event.