Production-Grade DevSecOps: Build Enterprise-Ready CI/CD Pipelines on AWS EKS with GitHub Actions, Secretless Auth, and Full Observability + Video

Listen to this Post

Featured Image

Introduction

The shift from traditional DevOps to DevSecOps isn’t just about adding security scans to your pipeline—it’s about embedding security into every stage of the software delivery lifecycle without sacrificing velocity. As organizations increasingly adopt multi-cloud strategies and Kubernetes-based infrastructures, the demand for professionals who can design, deploy, and secure production-grade environments has skyrocketed. Aditya Jaiswal’s course, “The Production-Grade End-to-End DevSecOps Project,” recently accepted into the Udemy Business Collection, addresses this exact gap by providing a hands-on, project-based approach to building real-world DevSecOps pipelines that mirror how actual production teams operate.

Learning Objectives

  • Build end-to-end CI/CD pipelines using GitHub Actions with branch-based promotion strategies from feature branches through QA to production
  • Implement DevSecOps security tooling including Gitleaks, Trivy, Checkov, SBOM scanning, and SonarQube directly within CI/CD pipelines
  • Configure secretless authentication to AWS using GitHub OIDC and securely access Amazon EKS without static credentials
  • Deploy and manage stateful applications (MySQL) on Kubernetes using StatefulSets, persistent storage, and AWS Secrets Manager integrated with External Secrets Operator (ESO)
  • Design a production-grade AWS EKS architecture with Application Load Balancer (ALB), Route 53, and AWS Certificate Manager (ACM) for secure HTTPS traffic routing
  • Implement a complete observability stack with Prometheus, Loki, Tempo, and Grafana for metrics, logs, and distributed traces
  1. Building the CI/CD Pipeline Foundation with GitHub Actions

The heart of any DevSecOps implementation is a robust CI/CD pipeline that automates building, testing, and deployment while enforcing security gates at every stage. GitHub Actions provides a powerful, YAML-based workflow engine that integrates seamlessly with your repository and supports branch-based promotion strategies.

Step-by-Step Implementation:

  1. Define your branching strategy: Create a Git workflow with `feature/` branches for development, a `qa` branch for integration testing, and a `main` (or production) branch for production deployments.

2. Create the GitHub Actions workflow file (`.github/workflows/devsecops-pipeline.yml`):

name: DevSecOps CI/CD Pipeline

on:
push:
branches: [ feature/, qa, main ]
pull_request:
branches: [ qa, main ]

env:
AWS_REGION: us-east-1
EKS_CLUSTER: production-cluster

jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

<ul>
<li>name: Run Gitleaks (Secrets Detection)
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}</p></li>
<li><p>name: Run Trivy (Container & IaC Scanning)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'</p></li>
<li><p>name: Run Checkov (IaC Security)
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
output_format: sarif</p></li>
<li><p>name: Generate SBOM
uses: anchore/sbom-action@v0
with:
path: ./
format: spdx-json
output-file: sbom.spdx.json</p></li>
</ul>

<p>build-and-push:
needs: security-scan
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/qa' || github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4

<ul>
<li>name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE }}
aws-region: ${{ env.AWS_REGION }}</p></li>
<li><p>name: Build and Push Docker Image
run: |
docker build -t ${{ secrets.ECR_REPO }}:${{ github.sha }} .
docker push ${{ secrets.ECR_REPO }}:${{ github.sha }}</p></li>
</ul>

<p>deploy-qa:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/qa'
steps:
- uses: actions/checkout@v4

<ul>
<li>name: Deploy to QA EKS
run: |
kubectl set image deployment/app app=${{ secrets.ECR_REPO }}:${{ github.sha }} -1 qa
kubectl rollout status deployment/app -1 qa</li>
</ul>

deploy-production:
needs: deploy-qa
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4

<ul>
<li>name: Deploy to Production EKS
run: |
kubectl set image deployment/app app=${{ secrets.ECR_REPO }}:${{ github.sha }} -1 production
kubectl rollout status deployment/app -1 production

What this does: This workflow implements a complete DevSecOps pipeline with security scanning as a mandatory gate before any build or deployment occurs. The branch-based promotion strategy ensures that code progresses through feature → QA → production only after passing all security checks. The OIDC authentication eliminates the need for static AWS credentials, following security best practices.

  1. Implementing Secretless Authentication with GitHub OIDC and AWS

One of the most critical security improvements in modern cloud-1ative architectures is eliminating long-lived static credentials. GitHub OIDC (OpenID Connect) allows your workflows to authenticate directly to AWS without storing any secrets, reducing the risk of credential leakage.

Step-by-Step Implementation:

  1. Create an OIDC provider in AWS IAM for your GitHub organization:
 Create the OIDC provider (AWS CLI)
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1" \
--client-id-list "sts.amazonaws.com"
  1. Create an IAM role that GitHub Actions can assume:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:YOUR_ORG/YOUR_REPO:"
}
}
}
]
}
  1. Attach the necessary policies to this role for EKS access, ECR push/pull, and Secrets Manager access.

  2. In your GitHub workflow, use the `configure-aws-credentials` action with the role ARN:

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions-role
aws-region: us-east-1
  1. Update your kubeconfig to use the OIDC-authenticated AWS session:
aws eks update-kubeconfig --region us-east-1 --1ame production-cluster

What this does: This configuration enables GitHub Actions to authenticate to AWS using OIDC tokens instead of static access keys. The IAM role trusts the OIDC provider and conditionally allows access based on the repository and branch, providing granular, temporary credentials that automatically expire. This approach aligns with the principle of least privilege and eliminates the security risks associated with storing AWS credentials in GitHub secrets.

  1. Integrating AWS Secrets Manager with Kubernetes Using External Secrets Operator (ESO)

Managing secrets in Kubernetes has traditionally been challenging, with many teams resorting to storing secrets in plain text or using base64-encoded values in YAML files. The External Secrets Operator (ESO) bridges the gap between AWS Secrets Manager and Kubernetes, automatically syncing secrets from AWS into your cluster.

Step-by-Step Implementation:

1. Install External Secrets Operator using Helm:

helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
--1amespace external-secrets \
--create-1amespace
  1. Configure IRSA (IAM Roles for Service Accounts) to allow ESO to access AWS Secrets Manager:
 Create IAM policy for Secrets Manager access
aws iam create-policy \
--policy-1ame ESOSecretsManagerPolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": ""
}
]
}'

Associate IAM role with Kubernetes service account
eksctl create iamserviceaccount \
--1ame external-secrets \
--1amespace external-secrets \
--cluster production-cluster \
--attach-policy-arn arn:aws:iam::ACCOUNT_ID:policy/ESOSecretsManagerPolicy \
--approve
  1. Create a SecretStore resource that defines how ESO connects to AWS:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secretstore
namespace: default
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
  1. Create an ExternalSecret that syncs a secret from AWS Secrets Manager:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: default
spec:
secretStoreRef:
name: aws-secretstore
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: production/mysql/credentials
property: username
- secretKey: password
remoteRef:
key: production/mysql/credentials
property: password

What this does: ESO continuously watches for changes in AWS Secrets Manager and automatically updates Kubernetes secrets in real-time. This eliminates manual secret rotation and ensures that your applications always have the latest credentials. The IRSA configuration ensures that ESO can authenticate to AWS without storing any static credentials, maintaining a zero-trust security posture.

  1. Deploying Stateful Applications on Kubernetes with StatefulSets and Persistent Storage

Stateful applications like databases require special handling in Kubernetes. StatefulSets provide unique identities, stable network identifiers, and ordered deployment/scale operations—essential for running production databases like MySQL.

Step-by-Step Implementation:

  1. Create a StorageClass for persistent volumes (using EBS on AWS):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-encrypted
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
fsType: ext4
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

2. Deploy a MySQL StatefulSet with persistent storage:

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: production
spec:
serviceName: mysql
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3-encrypted
resources:
requests:
storage: 20Gi
  1. Create a headless Service for stable network identity:
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: production
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
  1. Initialize the database with a custom script using an init container:
initContainers:
- name: init-mysql
image: mysql:8.0
command:
- bash
- -c
- |
mysql -h mysql-0.mysql.production.svc.cluster.local -u root -p$MYSQL_ROOT_PASSWORD <<EOF
CREATE DATABASE IF NOT EXISTS appdb;
CREATE USER IF NOT EXISTS 'appuser'@'%' IDENTIFIED BY '$APP_PASSWORD';
GRANT ALL PRIVILEGES ON appdb. TO 'appuser'@'%';
FLUSH PRIVILEGES;
EOF

What this does: This configuration deploys MySQL as a stateful application with persistent storage that survives pod restarts and rescheduling. The StatefulSet provides a stable network identity (mysql-0.mysql.production.svc.cluster.local), while the PersistentVolumeClaim template automatically provisions encrypted EBS volumes for data persistence. The init container ensures database initialization happens before the main application starts.

  1. Designing a Production-Grade AWS EKS Architecture with ALB, Route 53, and ACM

A production-grade Kubernetes cluster requires proper ingress management, DNS configuration, and TLS termination. AWS provides a comprehensive stack: Application Load Balancer (ALB) for HTTP/HTTPS traffic, Route 53 for DNS, and ACM for SSL/TLS certificates.

Step-by-Step Implementation:

1. Install the AWS Load Balancer Controller:

helm repo add eks https://aws.github.io/eks-charts
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
--1amespace kube-system \
--set clusterName=production-cluster \
--set serviceAccount.create=true \
--set serviceAccount.name=aws-load-balancer-controller

2. Request an SSL/TLS certificate from ACM:

aws acm request-certificate \
--domain-1ame ".yourdomain.com" \
--validation-method DNS \
--subject-alternative-1ames "yourdomain.com"
  1. Create an Ingress resource that uses the ALB and ACM certificate:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:ACCOUNT_ID:certificate/CERT_ID
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/healthcheck-path: /health
alb.ingress.kubernetes.io/success-codes: 200
spec:
rules:
- host: app.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
  1. Configure Route 53 to point your domain to the ALB:
 Get the ALB DNS name
ALB_DNS=$(kubectl get ingress app-ingress -1 production -o jsonpath='{.status.loadBalancer.ingress[bash].hostname}')

Create Route 53 record
aws route53 change-resource-record-sets \
--hosted-zone-id ZONE_ID \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.yourdomain.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z35SXDOTRQ7X7K",
"DNSName": "'$ALB_DNS'",
"EvaluateTargetHealth": false
}
}
}]
}'

What this does: This architecture provisions a fully managed Application Load Balancer that terminates TLS connections using ACM-issued certificates and routes HTTPS traffic to your Kubernetes services. The ALB controller automatically updates the load balancer configuration as services and ingresses change. Route 53 provides DNS resolution with alias records that point directly to the ALB, eliminating additional latency and cost.

  1. Implementing Full Observability with Prometheus, Loki, Tempo, and Grafana

Observability is the cornerstone of production-grade systems. The Grafana stack (Prometheus for metrics, Loki for logs, and Tempo for traces) provides a unified observability platform that gives you complete visibility into your application’s health and performance.

Step-by-Step Implementation:

  1. Install the Grafana Helm repository and deploy the full stack:
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

Install Prometheus (metrics)
helm install prometheus prometheus-community/kube-prometheus-stack \
--1amespace observability \
--create-1amespace \
--set prometheus.prometheusSpec.retention=15d

Install Loki (logs)
helm install loki grafana/loki-stack \
--1amespace observability \
--set grafana.enabled=false \
--set promtail.enabled=true

Install Tempo (traces)
helm install tempo grafana/tempo \
--1amespace observability \
--set storage.trace.backend=s3 \
--set storage.trace.s3.bucket=tempo-traces \
--set storage.trace.s3.endpoint=s3.amazonaws.com

2. Configure Grafana data sources for unified dashboards:

apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
namespace: observability
data:
datasources.yaml: |
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-operated.observability:9090
access: proxy
isDefault: true
- name: Loki
type: loki
url: http://loki.observability:3100
access: proxy
- name: Tempo
type: tempo
url: http://tempo.observability:3200
access: proxy
  1. Deploy the OpenTelemetry Collector in your application to send traces:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-telemetry
spec:
template:
spec:
containers:
- name: app
image: your-app:latest
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://tempo.observability:4318"
- name: OTEL_SERVICE_NAME
value: "my-app"

4. Create alerts in Prometheus for critical conditions:

groups:
- name: app-alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[bash]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected"

<ul>
<li>alert: PodCrashLooping
expr: kube_pod_container_status_restarts_total > 5
for: 10m
labels:
severity: warning

What this does: This observability stack provides end-to-end visibility into your production environment. Prometheus collects and stores metrics from Kubernetes and your application; Loki aggregates logs from all pods; Tempo captures distributed traces showing request flow across services; Grafana unifies all three data sources into a single dashboard interface. This enables you to correlate metrics, logs, and traces for rapid troubleshooting and proactive performance optimization.

  1. Hardening Your Kubernetes Cluster with Security Policies and Admission Controllers

Production-grade security requires more than just pipeline scanning—it demands runtime security controls that enforce policies at the cluster level. Kubernetes admission controllers like OPA/Gatekeeper and Kyverno allow you to implement policy-as-code for your cluster.

Step-by-Step Implementation:

1. Install OPA Gatekeeper for policy enforcement:

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper gatekeeper/gatekeeper \
--1amespace gatekeeper-system \
--create-1amespace \
--set enableExternalData=true

2. Create a ConstraintTemplate for disallowing privileged containers:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: disallow-privileged
spec:
crd:
spec:
names:
kind: DisallowPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package disallow_privileged
violation[{"msg": msg}] {
c := input.review.object
privileged := c.spec.containers[bash].securityContext.privileged
privileged == true
msg := "Privileged containers are not allowed"
}

3. Apply the constraint to enforce the policy:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: DisallowPrivileged
metadata:
name: disallow-privileged
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "production"
- "qa"

4. Implement network policies for zero-trust networking:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-1ginx
ports:
- protocol: TCP
port: 8080

5. Enable Pod Security Standards (Pod Security Admission):

 Label namespace to enforce restricted Pod Security Standard
kubectl label namespace production pod-security.kubernetes.io/enforce=restricted
kubectl label namespace production pod-security.kubernetes.io/audit=restricted
kubectl label namespace production pod-security.kubernetes.io/warn=restricted

What this does: These policies enforce security best practices at the cluster level, preventing misconfigurations from reaching production. OPA/Gatekeeper provides policy-as-code enforcement for custom security rules, Network Policies implement micro-segmentation (zero-trust networking), and Pod Security Standards enforce baseline security configurations. Together, these controls create a defense-in-depth security posture that protects your production workloads from both external threats and internal misconfigurations.

What Undercode Say

  • Secretless authentication is non-1egotiable: The shift from static credentials to OIDC-based authentication represents a fundamental security improvement. Organizations still storing AWS keys in GitHub secrets are exposed to unnecessary risk—GitHub OIDC with IRSA provides temporary, scoped credentials that automatically rotate, eliminating the blast radius of compromised secrets.

  • Observability is the foundation of production reliability: Metrics, logs, and traces are not optional—they are essential for understanding system behavior, diagnosing issues, and proving compliance. The integration of Prometheus, Loki, and Tempo provides a unified view that enables teams to correlate events across all three pillars, dramatically reducing mean time to resolution (MTTR).

  • Security must be embedded, not bolted on: The DevSecOps approach demonstrated in this pipeline—scanning secrets, containers, and infrastructure-as-code at every commit—catches vulnerabilities before they reach production. This shift-left strategy reduces remediation costs by orders of magnitude compared to finding issues in production.

  • Stateful workloads require special handling: Running databases like MySQL on Kubernetes requires understanding StatefulSets, persistent volumes, and proper backup strategies. The approach of using ESO with AWS Secrets Manager ensures credentials are never stored in plain text and are automatically rotated, addressing a common security gap in Kubernetes deployments.

  • Policy-as-code enables consistent security: OPA/Gatekeeper and Pod Security Standards provide the guardrails that prevent teams from accidentally deploying insecure configurations. This is particularly valuable in organizations with multiple teams where consistent security enforcement is challenging.

Prediction

  • +1 The acceptance of hands-on DevSecOps courses into enterprise learning platforms like Udemy Business signals a maturation of the DevOps discipline. Organizations are recognizing that theoretical knowledge alone is insufficient—they need practitioners who can build and secure real production systems. This trend will accelerate as cloud-1ative adoption continues to grow.

  • +1 The integration of AI/ML capabilities into DevSecOps pipelines (as hinted in Aditya’s broader curriculum) will become standard within 12-18 months. AI-driven anomaly detection, automated remediation, and intelligent observability will augment human operators, enabling faster incident response and proactive security.

  • -1 The complexity of modern DevSecOps stacks presents a significant barrier to entry. With tools like GitHub Actions, Terraform, Kubernetes, Helm, Prometheus, Loki, Tempo, Grafana, OPA, and ESO, the learning curve is steep. Organizations without dedicated platform engineering teams may struggle to implement these best practices, potentially widening the gap between security leaders and laggards.

  • -1 The rapid evolution of cloud-1ative security tools creates fragmentation and integration challenges. Teams must constantly evaluate and update their toolchains, risking alert fatigue and configuration drift. Standardization efforts (like the CNCF landscape) will help, but consolidation of the observability and security tooling space is inevitable.

  • +1 The demand for professionals with demonstrable project-based experience will continue to outstrip supply. Courses like “The Production-Grade End-to-End DevSecOps Project” that produce portfolio-ready artifacts will become the primary pathway for career transition into DevOps and cloud engineering roles, as employers increasingly prioritize practical skills over certifications alone.

▶️ Related Video (74% Match):

https://www.youtube.com/watch?v=1gplOodHgPo

🎯Let’s Practice For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

IT/Security Reporter URL:

Reported By: Adityajaiswal7 Devops – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow UndercodeTesting & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky