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.
Before any code is merged, enforce security policies at the developer’s local machine and in the pull request workflow.
pre-commitUse 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.
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.
Ensure that every build produces a secure, reproducible artifact.
# 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.
trivytrivy 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.
cosignSign 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.
Deploy the image and validate security in the target environment.
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.
kubectl and curlAfter 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.
opa and gatekeeperUse 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.
Close the loop: integrate security findings into the developer workflow.
github-actionsUse 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.
git and prAfter 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.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy