Interest in CI/CD automation has grown consistently over the past three years, and search volume for “CI/CD pipeline setup” rose 34% in 2025 alone. Despite that, the majority of UK development agencies still deploy via manual SSH sessions or ad-hoc scripts. That gap represents a significant competitive disadvantage: teams with mature CI/CD pipelines ship roughly five times more frequently and catch bugs at a stage where fixes are ten times cheaper than post-deployment remediation.

This guide covers the practical reality of setting up a CI/CD pipeline for a UK development team in 2026: choosing a platform, structuring pipeline stages correctly, integrating security scanning, and handling the deployment strategies that actually work in production.

TL;DR

  • CI builds and tests every commit automatically; CD delivers validated code to staging or production without manual intervention
  • GitHub Actions is the right default for most UK teams in 2026; GitLab CI is the better choice if you need self-hosted runners or stronger audit controls
  • A production-ready pipeline has eleven stages from build through to post-deploy monitoring, not just build and test
  • Security scanning belongs inside the pipeline, not bolted on later: SAST, dependency auditing, and secret scanning should run on every pull request

What CI/CD Actually Is

The term is used loosely, so it is worth being precise. Continuous Integration (CI) means every commit to a shared branch triggers an automated sequence: the code is built, linted, and tested before the change is accepted. The goal is to find integration problems immediately, while context is fresh, rather than at the end of a sprint when merging a week of diverged branches.

Continuous Delivery (CD) extends that automation so validated code can be released to staging or production with minimal or no manual steps. Continuous Deployment (the stronger version) means every passing pipeline automatically deploys to production. Most teams start with Continuous Delivery, adding a manual approval gate before the production deploy, and move toward full Continuous Deployment once they have enough confidence in their test coverage and rollback mechanisms.

The practical outcome is that CI/CD converts deployment from a high-anxiety, multi-hour event into a routine background process that happens dozens of times a week.

Choosing a Platform

The platform choice is consequential but not permanent. Migrate later if you need to, but choose the right default for your context now:

GitHub Actions is the right choice for most UK development teams starting a new project in 2026. It is deeply integrated with GitHub, has the largest ecosystem of reusable actions, generous free minutes for public repositories, and a well-documented configuration format. The hosted runners cover Linux, Windows, and macOS. For teams already on GitHub, the path-of-least-resistance advantage is real.

GitLab CI is the better choice if you need self-hosted runners (for regulatory reasons or to access private network resources), stronger audit logging, or if your team prefers a single platform for source control, CI, container registry, and deployment. GitLab’s .gitlab-ci.yml format is mature and well-understood.

CircleCI has strong parallelism and Docker support, and its config is readable. It was the market leader before GitHub Actions and many UK agencies still run it. There is no urgent reason to migrate an existing CircleCI setup if it is working.

Jenkins is legacy. It works, and many large organisations run it successfully, but for a new project in 2026 the operational overhead of maintaining a Jenkins server is not worth it. If you are inheriting Jenkins, evaluate the migration cost before committing to it long-term.

Bitbucket Pipelines is acceptable if your team is embedded in the Atlassian stack and cannot change source control. The feature set lags GitHub Actions but it covers the fundamentals.

Pipeline Stages: What to Include and Why

A complete, production-ready pipeline has more stages than most tutorials cover. Here is the full sequence with the reasoning behind each stage:

Stage 1: Build

Compile or transpile the source, install dependencies, and produce the build artefact. For a Node.js project, this means npm ci (not npm install, because ci uses the lockfile exactly) and your build step. For a Python service, it means creating a virtual environment and installing from requirements.txt. The output is a versioned artefact: a compiled binary, a transpiled JS bundle, or a wheel file.

Cache your dependencies aggressively here. npm ci with a cache keyed to package-lock.json means subsequent pipeline runs that have not changed dependencies skip the download entirely.

Stage 2: Lint and Static Analysis

ESLint, flake8, mypy, Pylance in strict mode, or your language-appropriate equivalent. This stage is fast, typically under 30 seconds, and catches a wide class of problems before tests run. Static type checking (mypy for Python, TypeScript’s tsc --noEmit for JS/TS) is particularly high-value for catching interface mismatches that unit tests often miss.

Fail fast here. There is no point running a ten-minute test suite if the code does not pass lint.

Stage 3: Unit Tests

Run your unit test suite. Unit tests should be fast. If your full unit test run takes more than five minutes, split the suite or investigate why. Parallelise across test files where your test runner supports it (Jest’s --runInBand off, pytest-xdist for Python).

A critical point: unit tests use mocks for external services. The CI environment should not have real database credentials or external API keys at this stage.

Stage 4: Integration Tests

Integration tests run against real services: a test database, a Redis instance, a local message queue. Most CI platforms support service containers for this purpose. In GitHub Actions, you declare a services block alongside the job to spin up a PostgreSQL or MySQL container for the duration of the test run.

This is the stage that catches the bugs unit tests miss: ORM queries that are semantically wrong, transaction isolation problems, foreign key constraint failures. Run integration tests against a real database, not a mock.

Stage 5: Security Scanning

Security scanning belongs in the pipeline, not in a quarterly scheduled scan. This stage has three components:

SAST (Static Application Security Testing). Semgrep is the most practical choice for a polyglot team; it has rules for Python, JavaScript, TypeScript, Go, Java, and more. For Python specifically, Bandit catches common security antipatterns. GitHub’s CodeQL is available free for public repositories and catches a different class of vulnerability than pattern-matching tools. Run at least one of these on every pull request.

Dependency auditing. npm audit --audit-level=high for Node.js projects and pip-audit for Python. These check your installed dependencies against the OSV (Open Source Vulnerabilities) database. Block the pipeline on high and critical findings; flag medium findings for human review rather than blocking.

Secret scanning. Gitleaks scans commit history for accidentally committed credentials, API keys, and tokens. This is your last line of defence before secrets reach the remote. GitHub’s built-in secret scanning is also worth enabling, but Gitleaks in the pipeline catches problems before the push rather than after.

Stage 6: Build and Push Docker Image

If your deployment target is containers, build the Docker image and push it to your registry here. Tag with the commit SHA, not just latest. Immutable tags mean you can always identify exactly which commit is running in production. Push to GitHub Container Registry (ghcr.io), AWS ECR, or your registry of choice.

Run a container image scan (Trivy or Grype) against the built image before pushing. This catches OS-level CVEs in your base image that dependency auditing misses.

Stage 7: Deploy to Staging

Deploy the validated artefact to a staging environment that mirrors production. Infrastructure-as-code (Terraform, Pulumi) means your staging environment is defined identically to production. The deployment step should be idempotent: running it twice should not cause problems.

Stage 8: Smoke Tests Against Staging

After deploying to staging, run a minimal set of end-to-end checks: can the application start, does the health endpoint return 200, does a basic login flow work? These tests should run in under two minutes. The goal is not comprehensive coverage but catching deployment failures that tests in isolation would not catch.

Stage 9: Manual Approval Gate

Before deploying to production, pause for a human to approve. In GitHub Actions, this is an environment protection rule with required reviewers. In GitLab CI, it is a manual job trigger. This gate should be lightweight: one reviewer confirming the staging environment looks correct, not a lengthy sign-off process.

For teams moving toward Continuous Deployment, this gate can be automated away once deployment frequency and test coverage are high enough, but starting with the gate is the safer default.

Stage 10: Deploy to Production

The same deployment step as staging, pointed at the production environment. With an immutable artefact tagged by commit SHA, the production deploy is deterministic.

Stage 11: Post-Deploy Monitoring Check

After the production deploy, the pipeline should verify that monitoring is healthy: error rate is not elevated, the health endpoint is responding, and there are no spikes in latency. A simple check against your observability platform (Datadog, Grafana, or even a basic HTTP health check) gives you an automated signal that the deploy succeeded or an alert to trigger a rollback.

A Realistic GitHub Actions Configuration

Below is an abbreviated but realistic pipeline structure for a Node.js application:

 1name: CI/CD Pipeline
 2
 3on:
 4  push:
 5    branches: [main, develop]
 6  pull_request:
 7    branches: [main]
 8
 9jobs:
10  build-and-lint:
11    runs-on: ubuntu-latest
12    steps:
13      - uses: actions/checkout@v4
14      - uses: actions/setup-node@v4
15        with:
16          node-version: '20'
17          cache: 'npm'
18      - run: npm ci
19      - run: npm run lint
20      - run: npx tsc --noEmit
21
22  unit-tests:
23    needs: build-and-lint
24    runs-on: ubuntu-latest
25    steps:
26      - uses: actions/checkout@v4
27      - uses: actions/setup-node@v4
28        with:
29          node-version: '20'
30          cache: 'npm'
31      - run: npm ci
32      - run: npm test -- --coverage
33
34  integration-tests:
35    needs: build-and-lint
36    runs-on: ubuntu-latest
37    services:
38      postgres:
39        image: postgres:16
40        env:
41          POSTGRES_PASSWORD: testpass
42          POSTGRES_DB: testdb
43        options: >-
44          --health-cmd pg_isready
45          --health-interval 10s
46          --health-timeout 5s
47          --health-retries 5          
48    steps:
49      - uses: actions/checkout@v4
50      - uses: actions/setup-node@v4
51        with:
52          node-version: '20'
53          cache: 'npm'
54      - run: npm ci
55      - run: npm run test:integration
56        env:
57          DATABASE_URL: postgres://postgres:testpass@localhost:5432/testdb
58
59  security-scan:
60    needs: build-and-lint
61    runs-on: ubuntu-latest
62    steps:
63      - uses: actions/checkout@v4
64        with:
65          fetch-depth: 0
66      - uses: actions/setup-node@v4
67        with:
68          node-version: '20'
69          cache: 'npm'
70      - run: npm ci
71      - run: npm audit --audit-level=high
72      - uses: semgrep/semgrep-action@v1
73      - name: Run Gitleaks
74        uses: gitleaks/gitleaks-action@v2
75
76  deploy-staging:
77    needs: [unit-tests, integration-tests, security-scan]
78    runs-on: ubuntu-latest
79    environment: staging
80    if: github.ref == 'refs/heads/main'
81    steps:
82      - uses: actions/checkout@v4
83      - name: Deploy to staging
84        run: ./scripts/deploy.sh staging
85
86  deploy-production:
87    needs: deploy-staging
88    runs-on: ubuntu-latest
89    environment: production
90    steps:
91      - uses: actions/checkout@v4
92      - name: Deploy to production
93        run: ./scripts/deploy.sh production

The unit-tests, integration-tests, and security-scan jobs run in parallel after the build-and-lint job passes. This structure reduces total pipeline time without sacrificing coverage.

Deployment Strategies

How you deploy matters as much as whether you deploy. Three strategies cover most UK team needs:

Rolling deploy. Replace instances one at a time. Simple to implement with most orchestrators (Kubernetes, ECS). Risk: if the new version has a bug, some users hit old instances and some hit new ones during the rollout window. Good default for low-traffic applications.

Blue/green deployment. You maintain two identical production environments. Traffic switches from blue to green atomically. If the new version is faulty, you switch back in seconds. The cost is running two environments simultaneously, which is non-trivial for databases. Best for applications where downtime is unacceptable and rollback speed is critical.

Canary release. Route a small percentage of traffic (5% or 10%) to the new version, observe error rates and performance for a defined period, then promote the canary to 100%. Excellent for high-traffic applications where you want real production feedback before full rollout. Requires your load balancer or service mesh to support traffic splitting.

Most UK teams should start with rolling deploys, add blue/green when their traffic and criticality justify it, and consider canary releases when they are shipping multiple times per day and need validated production feedback at each step.

Secrets Management

Secrets do not belong in code, configuration files, or environment-specific .env files committed to source control. The correct approach:

GitHub Secrets / GitLab CI variables for secrets that pipeline jobs need. These are encrypted at rest, masked in logs, and available to jobs via environment variables.

Environment-specific secrets configured at the deployment target level: AWS Parameter Store, Azure Key Vault, Google Secret Manager, or HashiCorp Vault for teams with more complex needs. The application reads secrets at startup from the secret store, not from environment variables passed through the pipeline.

Rotation. API keys, database passwords, and service credentials should be rotated on a schedule. CI/CD pipelines make rotation easier because updating the secret in one place (the secret store or the CI environment) propagates to the next deployment automatically.

Gitleaks in your pipeline is the safety net for when a developer accidentally commits a secret. The remediation for a committed secret is to invalidate it immediately and then clean the git history; the Gitleaks alert tells you that process needs to start now rather than when someone exploits it.

Pipeline Performance

A slow pipeline is an ignored pipeline. If developers wait fifteen minutes for feedback on every push, they stop pushing small commits and start batching work, which defeats the purpose of CI.

Practical steps to keep pipelines fast:

Cache dependency installs. For npm, key the cache on package-lock.json. For pip, use the pip cache action keyed on requirements.txt. A warm cache turns a two-minute install into a five-second restore.

Run independent jobs in parallel. Build, lint, unit tests, integration tests, and security scanning do not all depend on each other. A well-structured pipeline runs them as parallel jobs after a common build step, reducing total wall-clock time significantly.

Use path filters. If you have a monorepo with multiple services, only trigger the relevant pipeline stages when files in a service directory change. GitHub Actions supports paths filters on push triggers.

Set timeouts. A hung job should not occupy a runner for the full default timeout of six hours. Set job-level timeouts appropriate to each stage: five minutes for lint, fifteen minutes for unit tests, thirty minutes for integration tests.

Key Takeaways

  • CI/CD converts deployment from a high-risk event into a routine, automated process; the biggest bottleneck for most UK teams is not tooling but the absence of a structured pipeline at all.
  • GitHub Actions is the right starting point for new projects; GitLab CI for teams needing self-hosted runners or a single integrated platform.
  • A production-grade pipeline has eleven stages. Most tutorials stop at three. The stages you skip are where production incidents originate.
  • Security scanning (SAST, dependency audit, secret scanning) belongs in the pipeline on every pull request, not in a scheduled scan that runs monthly.
  • Deployment strategies are not interchangeable: rolling deploy for simplicity, blue/green for zero-downtime criticality, canary for high-frequency releases needing production validation.
  • A slow pipeline is as damaging as no pipeline. Cache aggressively, parallelise independent jobs, and set hard timeouts on every job.

Frequently Asked Questions (FAQ)

How long should a CI/CD pipeline take to run? Target under ten minutes for the critical path from push to staging deploy. Lint and unit tests should complete in under five minutes. If your pipeline regularly takes longer, investigate caching, parallelisation, and whether your test suite has grown slow tests that belong in a separate, less frequent run.

Should I run the full test suite on every commit or only on pull requests to main? Run fast checks (lint, unit tests, security scan) on every push. Run integration tests and staging deploys on pull requests to main and on merges to main. This balances feedback speed against resource cost and runner minutes.

What is the difference between CI and CD? CI (Continuous Integration) means every commit triggers an automated build and test sequence. CD (Continuous Delivery) extends that automation to deploy validated code to staging or production. They are typically implemented together but represent distinct practices.

Do I need a staging environment? Yes, for any application with real users. Staging is where you catch deployment-specific failures, configuration differences between environments, and smoke-test issues that do not appear in unit or integration tests. Testing directly in production is a risk that CI/CD exists to eliminate.

What is the best way to handle database migrations in a CI/CD pipeline? Run migrations as part of the deployment step, before the new application version starts receiving traffic. Ensure migrations are backward-compatible with the previous application version so a rollback does not break the schema. Avoid destructive schema changes (column drops, type changes) in the same migration as the feature that uses them.

How do I convince a UK agency or client that CI/CD investment is worth it? The strongest argument is financial: bugs caught in CI cost roughly ten times less to fix than bugs found post-deployment. A well-run pipeline also reduces the risk and stress of each release, which directly improves team retention and velocity. Most UK agencies see a measurable reduction in after-hours emergency deployments within the first quarter of adopting CI/CD.