How to Implement DevSecOps: Pipeline Security Step by Step

Integrate security into your CI/CD pipeline. Covers SAST, DAST, dependency scanning, container scanning, secrets detection, and compliance gates.

DevSecOps integrates security into every phase of the CI/CD pipeline, ensuring that code is not only built and deployed, but also verified, hardened, and monitored for security risks at every stage. This guide walks through the step-by-step implementation of a secure, automated pipeline, from code commit to production deployment, with a focus on actionable, concrete configurations and real-world failure modes.

Stage 1: Secure Code Commit and Pre-Commit Checks

Before any code is merged, enforce security policies at the developer’s local machine and in the pull request workflow.

Set up pre-commit hooks with Git and pre-commit

Use a pre-commit configuration file to run checks on every git commit.


repos:
  - repo: https://github.com/psf/black
    rev: 23.1.0
    hooks:
      - id: black
        language_version: python3
        args: [--line-length=88]

  - repo: https://github.com/PyCQA/flake8
    rev: 6.0.0
    hooks:
      - id: flake8
        args: [--max-line-length=88, --extend-ignore=E203,W503]

  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.52.0
    hooks:
      - id: eslint
        files: \.js$
        args: [--ext, .js, --fix]

  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.27.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_lint

  - repo: https://github.com/securego/gosec
    rev: v2.10.1
    hooks:
      - id: gosec
        args: [-c, gosec.yaml]

Ensure the gosec.yaml file includes critical security rules:

# gosec.yaml
include:
  - G101    # Sensitive data in code
  - G102    # Hardcoded credentials
  - G103    # SQL injection
  - G104    # Insecure cryptographic usage
  - G105    # Insecure random number generation
  - G106    # Path traversal
  - G201    # Unquoted strings in templates
  - G202    # Use of unsafe functions
  - G301    # Use of reflect.Value
exclude:
  - G101
  - G102
  - G103
  - G104
  - G105
  - G106
  - G201
  - G202
  - G301
  - G302
  - G303
  - G401
  - G402
  - G403
  - G404
  - G405
  - G406
  - G407
  - G408
  - G409
  - G410
  - G411
  - G412
  - G413
  - G414
  - G415
  - G416
  - G417
  - G418
  - G419
  - G420
  - G421
  - G422
  - G423
  - G424
  - G425
  - G426
  - G427
  - G428
  - G429
  - G430
  - G431
  - G432
  - G433
  - G434
  - G435
  - G436
  - G437
  - G438
  - G439
  - G440
  - G441
  - G442
  - G443
  - G444
  - G445
  - G446
  - G447
  - G448
  - G449
  - G450
  - G451
  - G452
  - G453
  - G454
  - G455
  - G456
  - G457
  - G458
  - G459
  - G460
  - G461
  - G462
  - G463
  - G464
  - G465
  - G466
  - G467
  - G468
  - G469
  - G470
  - G471
  - G472
  - G473
  - G474
  - G475
  - G476
  - G477
  - G478
  - G479
  - G480
  - G481
  - G482
  - G483
  - G484
  - G485
  - G486
  - G487
  - G488
  - G489
  - G490
  - G491
  - G492
  - G493
  - G494
  - G495
  - G496
  - G497
  - G498
  - G499
  - G500
  - G501
  - G502
  - G503
  - G504
  - G505
  - G506
  - G507
  - G508
  - G509
  - G510
  - G511
  - G512
  - G513
  - G514
  - G515
  - G516
  - G517
  - G518
  - G519
  - G520
  - G521
  - G522
  - G523
  - G524
  - G525
  - G526
  - G527
  - G528
  - G529
  - G530
  - G531
  - G532
  - G533
  - G534
  - G535
  - G536
  - G537
  - G538
  - G539
  - G540
  - G541
  - G542
  - G543
  - G544
  - G545
  - G546
  - G547
  - G548
  - G549
  - G550
  - G551
  - G552
  - G553
  - G554
  - G555
  - G556
  - G557
  - G558
  - G559
  - G560
  - G561
  - G562
  - G563
  - G564
  - G565
  - G566
  - G567
  - G568
  - G569
  - G570
  - G571
  - G572
  - G573
  - G574
  - G575
  - G576
  - G577
  - G578
  - G579
  - G580
  - G581
  - G582
  - G583
  - G584
  - G585
  - G586
  - G587
  - G588
  - G589
  - G590
  - G591
  - G592
  - G593
  - G594
  - G595
  - G596
  - G597
  - G598
  - G599
  - G600
  - G601
  - G602
  - G603
  - G604
  - G605
  - G606
  - G607
  - G608
  - G609
  - G610
  - G611
  - G612
  - G613
  - G614
  - G615
  - G616
  - G617
  - G618
  - G619
  - G620
  - G621
  - G622
  - G623
  - G624
  - G625
  - G626
  - G627
  - G628
  - G629
  - G630
  - G631
  - G632
  - G633
  - G634
  - G635
  - G636
  - G637
  - G638
  - G639
  - G640
  - G641
  - G642
  - G643
  - G644
  - G645
  - G646
  - G647
  - G648
  - G649
  - G650
  - G651
  - G652
  - G653
  - G654
  - G655
  - G656
  - G657
  - G658
  - G659
  - G660
  - G661
  - G662
  - G663
  - G664
  - G665
  - G666
  - G667
  - G668
  - G669
  - G670
  - G671
  - G672
  - G673
  - G674
  - G675
  - G676
  - G677
  - G678
  - G679
  - G680
  - G681
  - G682
  - G683
  - G684
  - G685
  - G686
  - G687
  - G688
  - G689
  - G690
  - G691
  - G692
  - G693
  - G694
  - G695
  - G696
  - G697
  - G698
  - G699
  - G700
  - G701
  - G702
  - G703
  - G704
  - G705
  - G706
  - G707
  - G708
  - G709
  - G710
  - G711
  - G712
  - G713
  - G714
  - G715
  - G716
  - G717
  - G718
  - G719
  - G720
  - G721
  - G722
  - G723
  - G724
  - G725
  - G726
  - G727
  - G728
  - G729
  - G730
  - G731
  - G732
  - G733
  - G734
  - G735
  - G736
  - G737
  - G738
  - G739
  - G740
  - G741
  - G742
  - G743
  - G744
  - G745
  - G746
  - G747
  - G748
  - G749
  - G750
  - G751
  - G752
  - G753
  - G754
  - G755
  - G756
  - G757
  - G758
  - G759
  - G760
  - G761
  - G762
  - G763
  - G764
  - G765
  - G766
  - G767
  - G768
  - G769
  - G770
  - G771
  - G772
  - G773
  - G774
  - G775
  - G776
  - G777
  - G778
  - G779
  - G780
  - G781
  - G782
  - G783
  - G784
  - G785
  - G786
  - G787
  - G788
  - G789
  - G790
  - G791
  - G792
  - G793
  - G794
  - G795
  - G796
  - G797
  - G798
  - G799
  - G800
  - G801
  - G802
  - G803
  - G804
  - G805
  - G806
  - G807
  - G808
  - G809
  - G810
  - G811
  - G812
  - G813
  - G814
  - G815
  - G816
  - G817
  - G818
  - G819
  - G820
  - G821
  - G822
  - G823
  - G824
  - G825
  - G826
  - G827
  - G828
  - G829
  - G830
  - G831
  - G832
  - G833
  - G834
  - G835
  - G836
  - G837
  - G838
  - G839
  - G840
  - G841
  - G842
  - G843
  - G844
  - G845
  - G846
  - G847
  - G848
  - G849
  - G850
  - G851
  - G852
  - G853
  - G854
  - G855
  - G856
  - G857
  - G858
  - G859
  - G860
  - G861
  - G862
  - G863
  - G864
  - G865
  - G866
  - G867
  - G868
  - G869
  - G870
  - G871
  - G872
  - G873
  - G874
  - G875
  - G876
  - G877
  - G878
  - G879
  - G880
  - G881
  - G882
  - G883
  - G884
  - G885
  - G886
  - G887
  - G888
  - G889
  - G890
  - G891
  - G892
  - G893
  - G894
  - G895
  - G896
  - G897
  - G898
  - G899
  - G900
  - G901
  - G902
  - G903
  - G904
  - G905
  - G906
  - G907
  - G908
  - G909
  - G910
  - G911
  - G912
  - G913
  - G914
  - G915
  - G916
  - G917
  - G918
  - G919
  - G920
  - G921
  - G922
  - G923
  - G924
  - G925
  - G926
  - G927
  - G928
  - G929
  - G930
  - G931
  - G932
  - G933
  - G934
  - G935
  - G936
  - G937
  - G938
  - G939
  - G940
  - G941
  - G942
  - G943
  - G944
  - G945
  - G946
  - G947
  - G948
  - G949
  - G950
  - G951
  - G952
  - G953
  - G954
  - G955
  - G956
  - G957
  - G958
  - G959
  - G960
  - G961
  - G962
  - G963
  - G964
  - G965
  - G966
  - G967
  - G968
  - G969
  - G970
  - G971
  - G972
  - G973
  - G974
  - G975
  - G976
  - G977
  - G978
  - G979
  - G980
  - G981
  - G982
  - G983
  - G984
  - G985
  - G986
  - G987
  - G988
  - G989
  - G990
  - G991
  - G992
  - G993
  - G994
  - G995
  - G996
  - G997
  - G998
  - G999
  - G1000

Run pre-commit install --hook-type commit-msg to integrate hooks into the Git workflow. A commit fails silently if gosec finds a G102 (hardcoded credentials) without a G101 (sensitive data), and the CI pipeline will fail if any hook exits non-zero.

Enforce PR checks with GitHub Actions

In .github/workflows/ci.yml, define a job that runs pre-commit and other checks:

name: CI
on: [pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install pre-commit
      - run: pre-commit run --all-files
      - run: |
          if ! git diff --exit-code; then
            echo "Staged changes are not committed"
            exit 1
          fi
      - run: |
          if ! git diff --cached --exit-code; then
            echo "Staged changes are not committed"
            exit 1
          fi

The pre-commit run --all-files command must be run with --show-diff-on-failure to help reviewers see what changed.

Stage 2: Build and Image Security

Ensure that every build produces a secure, reproducible artifact.

Use BuildKit with Docker for reproducible builds

# Dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main cmd/main.go

FROM alpine:latest AS runtime

RUN apk --no-cache add ca-certificates

WORKDIR /root/
COPY --from=builder /app/main .

EXPOSE 8080
CMD ["/root/main"]

Build with BuildKit:

docker build --progress=plain \
  --no-cache \
  --target=runtime \
  --build-arg BUILDKIT_PROGRESS=plain \
  -t myapp:latest .

The --target=runtime ensures only the final minimal image is produced, and --no-cache guarantees no layer caching from previous builds. Use --progress=plain to see real-time output during CI.

Scan container images with trivy

trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest

This command fails if any high or critical CVE is found. It returns exit code 1 if any vulnerability exists. Run this in CI after building.

Add metadata to the image with cosign

Sign and verify images with cosign:

# Generate key pair
cosign generate-key-pair

# Sign image
cosign sign myapp:latest

# Verify signature
cosign verify myapp:latest

The cosign verify command fails if the image is not signed, the signature is expired, or the key is not trusted. Use --certificate-oidc-issuer to bind the signature to a CI system.

Stage 3: Deployment and Runtime Verification

Deploy the image and validate security in the target environment.

Deploy with kubectl and helm

# values.yaml
image:
  repository: myapp
  tag: latest
  pullPolicy: Always

securityContext:
  runAsNonRoot: true
  runAsUser: 1001
  fsGroup: 2001

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3

Apply with Helm:

helm upgrade --install app ./chart --values values.yaml --set image.tag=2024-03-14-12345

The --set image.tag=2024-03-14-12345 ensures a specific tag is used, and --install creates the release if it does not exist.

Validate deployment with kubectl and curl

After deployment, run a health check:

kubectl rollout status deployment/app --timeout=300s

This command waits up to 300 seconds for the rollout to complete. If any pod fails readiness, it exits with a non-zero status.

Verify the service is accessible:

curl -f -H "Host: app.example.com" http://app.example.com/healthz

The -f flag makes curl fail on HTTP error codes (4xx, 5xx), and the Host header ensures virtual host routing.

Monitor and enforce security with opa and gatekeeper

Use Open Policy Agent (OPA) to define policies that validate the cluster state.

# policies/app-security.rego
package app.security

import data.kubernetes

default allow = false

allow {
  input.kind == "Deployment"
  input.spec.template.spec.securityContext.runAsNonRoot
  input.spec.template.spec.securityContext.fsGroup == 1001
  count(input.spec.template.spec.containers) > 0
  container := input.spec.template.spec.containers[_]
  container.securityContext.runAsUser == 1001
  container.resources.limits.cpu > "200m"
}

Apply policy with gatekeeper:

kubectl apply -f - <<EOF
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: appsecurity
spec:
  crd:
    spec:
      names:
        kind: AppSecurity
  targets:
    - target: admission.gatekeeper.sh
      rego: |
        package app.security
        # ... policy content above
EOF

A deployment that omits fsGroup fails validation, and the gatekeeper controller logs Violation: AppSecurity with details of the missing field.

Stage 4: Continuous Security Feedback

Close the loop: integrate security findings into the developer workflow.

Write findings to PRs with github-actions

Use github-actions to post security report as a comment:

- name: Post Security Report
  run: |
    cat report.json | jq -c . > report.json
    gh pr comment ${{ github.event.pull_request.number }} --body-file report.json

The report.json contains a structured report with title, body, and annotations fields. Use gh CLI to create a comment with --body-file.

Automate remediation with git and pr

After a security check fails, create a PR to fix it:

git checkout -b fix/security-cve-123
git add .
git commit -m "Fix: CVE-123: Upgrade golang to 1.21.1"
git push origin fix/security-cve-123
gh pr create --title "Fix: CVE-123" --body "This PR addresses a critical CVE found in the CI pipeline." --base main --head fix/security-cve-123

The gh pr create command opens a PR with the specified title and body, and sets up the base and head branches. The PR is automatically linked to the original PR and the CI job.

This end-to-end pipeline ensures that every change is secured, verified, and continuously improved. The combination of concrete commands, precise configuration keys, and real failure modes creates a robust, repeatable DevSecOps implementation.

This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.