Home / Deep Dive / DevSecOps Pipeline: Azure DevOps, GitHub Actions, Container Security, and IaC
Deep Dive

DevSecOps Pipeline: Azure DevOps, GitHub Actions, Container Security, and IaC

Build a comprehensive DevSecOps pipeline with Azure DevOps, GitHub Actions, container scanning, secret management, infrastructure as code security, and compl...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

branches: [main, develop]``` pull_request:

branches: [main]

env: REGISTRY: contosoacr.azurecr.io IMAGE_NAME: api-service

jobs: code-quality:

runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v4
  
  - name: Setup .NET
    uses: actions/setup-dotnet@v4
    with:
      dotnet-version: '8.0.x'
  
  - name: Restore dependencies
      Securing the delivery pipeline is a systemic discipline—not a collection of one‑off scanners. Effective DevSecOps establishes policy as code, verifiable provenance (SBOM + build attestation), least‑privilege automation (OIDC to Entra workload identities instead of static secrets), runtime guardrails (Policy, Defender), and continuous feedback through SIEM + metrics. This expansion introduces a pragmatic operating model with concrete controls at each stage: plan, code, build, package, deploy, run, respond.

      ### DevSecOps Operating Model
      - Governance: risk register per product, exception process time‑boxed with remediation plan.
      - Standards: minimum baseline (SAST, SCA, secret scan, IaC policy, container scan, supply chain attestations).
      - Separation of duties: CI and CD identities isolated; production deploy requires policy gate approval.
      - Evidence: audits draw from centralized logs, SARIF artifacts, SBOMs, release notes, and attestation bundles.

      ### Threat Scenarios Addressed
      - Dependency supply chain compromise, leaked secrets, image vulnerabilities, misconfigured cloud resources, malicious pull requests, tampered artifacts, runtime drift.

      ### Key Artifacts
      - SBOM (CycloneDX/SPDX), attestation (SLSA provenance), security test reports (SARIF), policy audit logs, exception register.
    run: dotnet restore
  

      ### Control Mapping
      | Stage | Control | Tooling |
      |-------|---------|---------|
      | Code | SAST, secret scan | CodeQL, TruffleHog |
      | Dependencies | SCA | Dependency Review, OWASP DC |
      | Build | Reproducible builds, attestation | GitHub OIDC, SLSA provenance |
      | Package | Image scan, SBOM attach | Trivy, Syft/CycloneDX |
      | Deploy | IaC policy gates | Azure Policy, Checkov |
      | Run | Runtime threats | Defender for Containers |
      | Respond | SIEM & playbooks | Microsoft Sentinel + Logic Apps |
  - name: Code linting
    run: dotnet format --verify-no-changes
  
  - name: Run tests
    run: dotnet test --configuration Release --collect:"XPlat Code Coverage"
      **Bicep Lint + Policy:**
      ```bash
      # Bicep build validation
      bicep build main.bicep

      # Azure Policy assignment for allowed SKUs / tags
      az policy assignment create \
        --name enforce-tags-skus \
        --policy "/providers/Microsoft.Authorization/policyDefinitions/allowed-skus-and-tags" \
        --scope "/subscriptions/<sub>/resourceGroups/rg-platform" \
        --params '{"allowedSkus": ["Standard_B2s","P1V3"], "requiredTags": ["Owner","CostCenter","Environment"]}'
      ```

      **OPA Gate in CI:**
      ```yaml
      jobs:
        policy-gate:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v4
            - name: Run Rego policies
              uses: open-policy-agent/opa-action@v1
              with:
                policy-path: policies/
                input-path: iac-plan.json
      ```
  
  - name: Upload coverage
    uses: codecov/codecov-action@v4
    with:
      files: ./coverage.cobertura.xml

sast-scan:

runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v4
      ### Supply Chain Security
      Attach SBOMs and attestations; verify on deploy.

      **Generate SBOM (Syft) & Attestation (SLSA):**
      ```bash
      syft $REGISTRY/$IMAGE_NAME:$GITHUB_SHA -o cyclonedx-json > sbom.json
      slsa-provenance --artifact $REGISTRY/$IMAGE_NAME:$GITHUB_SHA --out provenance.json
      ```

      **Verify at CD:**
      ```bash
      cosign verify-attestation --type slsaprovenance $REGISTRY/$IMAGE_NAME:$GITHUB_SHA \
        --key cosign.pub
      ```
  
  - name: Initialize CodeQL
    uses: github/codeql-action/init@v3
    with:
      languages: csharp
  
  - name: Autobuild
    uses: github/codeql-action/autobuild@v3
  
  - name: Perform CodeQL Analysis

      Replace static passwords with managed identities or federated credentials. Rotate remaining secrets via Key Vault; enable purge protection.
    uses: github/codeql-action/analyze@v3

secret-scan:

runs-on: ubuntu-latest
steps:

      Run containers with non‑root users, read‑only filesystems where feasible, drop NET_RAW, leverage seccomp profiles, and minimal images (Alpine or distroless) to shrink attack surface.
  - uses: actions/checkout@v4
    with:
      fetch-depth: 0
  

      Enable Defender for Containers (plan on subscription), Kubernetes policy packs (Pod Security Standards), and ACR image quarantine pattern (only signed images admitted to prod).
  - name: TruffleHog Secret Scan
    uses: trufflesecurity/trufflehog@main
    with:
      path: ./
      base: ${{ github.event.repository.default_branch }}

      Add playbooks: auto‑open GitHub issue on high severity finding; auto‑quarantine image; notify #secops channel.
      head: HEAD

dependency-check: Issue: SBOM verification fails during CD
Solution: Ensure generator and verifier formats match (CycloneDX vs SPDX); pin tool versions; validate artifact digest alignment with provenance.

runs-on: ubuntu-latest
steps:
      - Treat security results as code: store SARIF, SBOM, attestation in artifact store; link to release
      - Schedule chaos/security days: simulate secret leak, image tampering, policy misconfiguration and rehearse incident response
    uses: actions/dependency-review-action@v4
    with:
      - Add SLSA level target and roadmap; instrument SBOM coverage metric across services
  - name: OWASP Dependency Check
    uses: dependency-check/Dependency-Check_Action@main
    with:
      project: 'api-service'
      path: '.'
      format: 'HTML'
      args: >
        --enableRetired
        --enableExperimental

build-and-scan:

needs: [code-quality, sast-scan, secret-scan, dependency-check]
runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v4
  
  - name: Login to ACR
    uses: docker/login-action@v3
    with:
      registry: ${{ env.REGISTRY }}
      username: ${{ secrets.ACR_USERNAME }}
      password: ${{ secrets.ACR_PASSWORD }}
  
  - name: Build container image
    run: |
      docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
      docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest .
  
  - name: Scan image with Trivy
    uses: aquasecurity/trivy-action@master
    with:
      image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
      format: 'sarif'
      output: 'trivy-results.sarif'
      severity: 'CRITICAL,HIGH'
  
  - name: Upload Trivy results to GitHub Security
    uses: github/codeql-action/upload-sarif@v3
    with:
      sarif_file: 'trivy-results.sarif'
  
  - name: Push to ACR
    run: |
      docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
      docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest

### Phase 2: Infrastructure as Code Security

**Bicep Template with Security Controls:**

```bicep
@description('Location for all resources')
param location string = resourceGroup().location

@description('AKS cluster name')
param aksClusterName string = 'aks-devsecops'

@description('Enable RBAC')
param enableRBAC bool = true

@description('Network plugin')
@allowed(['azure', 'kubenet'])
param networkPlugin string = 'azure'

resource aks 'Microsoft.ContainerService/managedClusters@2024-01-01' = {
  name: aksClusterName
  location: location
  identity: {
```yaml
type: 'SystemAssigned'```
  }
  properties: {
```yaml
dnsPrefix: '${aksClusterName}-dns'
enableRBAC: enableRBAC

// Security features
aadProfile: {
  managed: true
  enableAzureRBAC: true
  adminGroupObjectIDs: [
    'admin-group-object-id'
  ]
}

// Defender for Containers
securityProfile: {
  defender: {
    securityMonitoring: {
      enabled: true
    }
  }
  imageCleaner: {
    enabled: true
    intervalHours: 24
  }
}

// Network security
networkProfile: {
  networkPlugin: networkPlugin
  networkPolicy: 'azure'
  serviceCidr: '10.0.0.0/16'
  dnsServiceIP: '10.0.0.10'
}

// Pod Security Standards
podIdentityProfile: {
  enabled: true
}

agentPoolProfiles: [
  {
    name: 'systempool'
    count: 3
    vmSize: 'Standard_D4s_v3'
    mode: 'System'
    enableAutoScaling: true
    minCount: 3
    maxCount: 10
    
    // Security hardening
    enableNodePublicIP: false
    enableEncryptionAtHost: true
    osDiskType: 'Ephemeral'
  }
]

// Azure Policy add-on
addonProfiles: {
  azurepolicy: {
    enabled: true
  }
  azureKeyvaultSecretsProvider: {
    enabled: true
  }
}```
  }
}

// Enable diagnostic logging
resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  name: 'aks-diagnostics'
  scope: aks
  properties: {
```yaml
workspaceId: logAnalyticsWorkspace.id
logs: [
  {
    category: 'kube-apiserver'
    enabled: true
  }
  {
    category: 'kube-audit'
    enabled: true
  }
  {
    category: 'kube-controller-manager'
    enabled: true
  }
]
metrics: [
  {
    category: 'AllMetrics'
    enabled: true
  }
]```
  }
}

resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
  name: 'law-devsecops'
  location: location
  properties: {
```yaml
retentionInDays: 90
sku: {
  name: 'PerGB2018'
}```
  }
}

IaC Security Scanning (Checkov):

# .github/workflows/iac-scan.yml
name: IaC Security Scan

on:
  pull_request:
```yaml
paths:
  - '**/*.bicep'
  - '**/*.tf'

jobs: checkov-scan:

runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v4
  
  - name: Run Checkov
    uses: bridgecrewio/checkov-action@master
    with:
      directory: ./infrastructure
      framework: bicep
      soft_fail: false
      output_format: sarif
      output_file_path: checkov-results.sarif
  
  - name: Upload results
    uses: github/codeql-action/upload-sarif@v3
    if: always()
    with:
      sarif_file: checkov-results.sarif

## Phase 3: Azure DevOps CD Pipeline

![Phase 3: Azure DevOps CD Pipeline](/images/articles/deep-dive/2025-05-19-devsecops-pipeline-azure-devops-github-actions-container-security-ctx-1.svg)

**Release Pipeline (azure-pipelines-cd.yml):**





```yaml
trigger: none

resources:
  pipelines:
```text
- pipeline: ci-pipeline
  source: 'DevSecOps-CI'
  trigger:
    branches:
      include:
        - main

variables:

  • group: devsecops-secrets
  • name: aksResourceGroup
value: 'rg-devsecops-prod'```
  - name: aksClusterName
```yaml
value: 'aks-devsecops-prod'

stages:

  • stage: SecurityValidation
displayName: 'Security Validation'
jobs:
  - job: IaCSecurityScan
    displayName: 'Infrastructure Security Scan'
    steps:
      - task: AzureCLI@2
        displayName: 'Deploy infrastructure (validate mode)'
        inputs:
          azureSubscription: 'Azure-Prod'
          scriptType: 'bash'
          scriptLocation: 'inlineScript'
          inlineScript: |
            az deployment group validate \
              --resource-group $(aksResourceGroup) \
              --template-file infrastructure/main.bicep \
              --parameters @infrastructure/prod.parameters.json
      
      - task: Bash@3
        displayName: 'Run Terraform compliance checks'
        inputs:
          targetType: 'inline'
          script: |
            terraform-compliance -f compliance/ -p terraform-plan.json

  - job: ContainerScan
    displayName: 'Container Vulnerability Scan'
    steps:
      - task: AzureCLI@2
        displayName: 'ACR vulnerability assessment'
        inputs:
          azureSubscription: 'Azure-Prod'
          scriptType: 'bash'
          scriptLocation: 'inlineScript'
          inlineScript: |
            IMAGE_DIGEST=$(az acr repository show-tags \
              --name contosoacr \
              --repository api-service \
              --orderby time_desc \
              --output tsv \
              --query "[0]")
            
            VULN_COUNT=$(az security assessment list \
              --resource-id "/subscriptions/$(SUBSCRIPTION_ID)/resourceGroups/$(aksResourceGroup)/providers/Microsoft.ContainerRegistry/registries/contosoacr" \
              --query "[?displayName=='Container registry images should have vulnerability findings resolved'].status.code" \
              -o tsv | grep -c "Unhealthy" || echo 0)
            
            if [ "$VULN_COUNT" -gt 0 ]; then
              echo "##vso[task.logissue type=error]Critical vulnerabilities found in container image"
              exit 1
            fi
  • stage: DeployDev
displayName: 'Deploy to Dev'
dependsOn: SecurityValidation
jobs:
  - deployment: DeployAKS
    displayName: 'Deploy to AKS Dev'
    environment: 'dev'
    strategy:
      runOnce:
        deploy:
          steps:
            - task: KubernetesManifest@0
              displayName: 'Deploy to Kubernetes'
              inputs:
                action: 'deploy'
                kubernetesServiceConnection: 'AKS-Dev'
                namespace: 'api-service'
                manifests: |
                  k8s/deployment.yaml
                  k8s/service.yaml
                containers: 'contosoacr.azurecr.io/api-service:$(Build.BuildId)'
  • stage: DastTesting
displayName: 'DAST Testing'
dependsOn: DeployDev
jobs:
  - job: RunDast
    displayName: 'OWASP ZAP Scan'
    steps:
      - task: Bash@3
        displayName: 'Run ZAP baseline scan'
        inputs:
          targetType: 'inline'
          script: |
            docker run -v $(pwd):/zap/wrk/:rw \
              -t ghcr.io/zaproxy/zaproxy:stable \
              zap-baseline.py \
              -t https://api-dev.contoso.com \
              -r zap-report.html \
              -J zap-report.json
  • stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: DastTesting
jobs:
  - deployment: DeployProd
    displayName: 'Deploy to AKS Prod'
    environment: 'production'
    strategy:
      canary:
        increments: [10, 25, 50, 100]
        preDeploy:
          steps:
            - task: AzureCLI@2
              displayName: 'Snapshot configuration'
              inputs:
                azureSubscription: 'Azure-Prod'
                scriptType: 'bash'
                scriptLocation: 'inlineScript'
                inlineScript: |
                  kubectl create configmap deployment-backup-$(Build.BuildId) \
                    --from-literal=timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
                    --namespace api-service
        deploy:
          steps:
            - task: KubernetesManifest@0
              displayName: 'Deploy canary'
              inputs:
                action: 'deploy'
                kubernetesServiceConnection: 'AKS-Prod'
                namespace: 'api-service'
                manifests: 'k8s/deployment.yaml'
                containers: 'contosoacr.azurecr.io/api-service:$(Build.BuildId)'
                strategy: 'canary'
                trafficSplitMethod: 'smi'
                percentage: $(strategy.increment)
        routeTraffic:
          steps:
            - task: Bash@3
              displayName: 'Monitor canary metrics'
              inputs:
                targetType: 'inline'
                script: |
                  ERROR_RATE=$(kubectl top pods -n api-service --containers | grep canary | awk '{print $3}')
                  if (( $(echo "$ERROR_RATE > 5.0" | bc -l) )); then
                    echo "##vso[task.logissue type=error]Canary error rate too high: $ERROR_RATE%"
                    exit 1
                  fi
        postRouteTraffic:
          steps:
            - task: Bash@3
              displayName: 'Verify deployment health'
              inputs:
                targetType: 'inline'
                script: |
                  kubectl rollout status deployment/api-service -n api-service --timeout=300s

### Phase 4: Secret Management with Key Vault

**Key Vault Setup:**

```bash
## Create Key Vault
az keyvault create \
  --name kv-devsecops-prod \
  --resource-group rg-devsecops-prod \
  --location eastus \
  --enable-rbac-authorization true





## Assign permissions to AKS managed identity

![Assign permissions to AKS managed identity](/images/articles/deep-dive/2025-05-19-devsecops-pipeline-azure-devops-github-actions-container-security-ctx-2.svg)
AKS_IDENTITY=$(az aks show \
  --resource-group rg-devsecops-prod \
  --name aks-devsecops-prod \
  --query identityProfile.kubeletidentity.clientId -o tsv)





az role assignment create \
  --assignee $AKS_IDENTITY \
  --role "Key Vault Secrets User" \
  --scope /subscriptions/<subscription-id>/resourceGroups/rg-devsecops-prod/providers/Microsoft.KeyVault/vaults/kv-devsecops-prod

## Store secrets
az keyvault secret set \
  --vault-name kv-devsecops-prod \
  --name database-connection-string \
  --value "Server=sqlserver.database.windows.net;Database=mydb;..."





Expected output:

{ "name": "kv-myapp", "properties": { "enableRbacAuthorization": true, "provisioningState": "Succeeded", "vaultUri": "https://kv-myapp.vault.azure.net/" } }

Terminal output for az keyvault create

Kubernetes SecretProviderClass:

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: azure-keyvault-secrets
  namespace: api-service
spec:
  provider: azure
  parameters:
```yaml
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: "<kubelet-identity-client-id>"
keyvaultName: "kv-devsecops-prod"
cloudName: ""
objects: |
  array:
    - |
      objectName: database-connection-string
      objectType: secret
      objectVersion: ""
tenantId: "<tenant-id>"```
  secretObjects:
```text
- secretName: app-secrets
  type: Opaque
  data:
    - objectName: database-connection-string
      key: connectionString

**Pod Configuration:**

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: api-service
  namespace: api-service
spec:
  containers:
```text
- name: api
  image: contosoacr.azurecr.io/api-service:latest
  env:
    - name: DATABASE_CONNECTION_STRING
      valueFrom:
        secretKeyRef:
          name: app-secrets
          key: connectionString
  volumeMounts:
    - name: secrets-store
      mountPath: "/mnt/secrets-store"
      readOnly: true```
  volumes:
```text
- name: secrets-store
  csi:
    driver: secrets-store.csi.k8s.io
    readOnly: true
    volumeAttributes:
      secretProviderClass: "azure-keyvault-secrets"

## Phase 5: Runtime Security with Defender for Containers

![Phase 5: Runtime Security with Defender for Containers](/images/articles/deep-dive/2025-05-19-devsecops-pipeline-azure-devops-github-actions-container-security-ctx-3.svg)

**Enable Defender:**





```bash
az security pricing create \
  --name Containers \
  --tier Standard

az aks update \
  --resource-group rg-devsecops-prod \
  --name aks-devsecops-prod \
  --enable-defender

Pod Security Policy (Gatekeeper Constraints):

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: psp-privileged-container
spec:
  match:
```yaml
kinds:
  - apiGroups: [""]
    kinds: ["Pod"]
excludedNamespaces:
  - kube-system```
  parameters: {}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPCapabilities
metadata:
  name: psp-capabilities
spec:
  match:
```yaml
kinds:
  - apiGroups: [""]
    kinds: ["Pod"]```
  parameters:
```yaml
allowedCapabilities: []
requiredDropCapabilities:
  - ALL

**Security Monitoring KQL:**

```kql
// Suspicious process execution in containers
ContainerLog
| where TimeGenerated > ago(1h)
| where LogEntry contains "chmod +x" or LogEntry contains "wget" or LogEntry contains "curl" 
| where ContainerName !in ("monitoring-agent", "log-collector")
| project TimeGenerated, Computer, ContainerName, LogEntry

// Failed authentication attempts
AzureDiagnostics
| where Category == "kube-audit"
| where log_s contains "authentication failed"
| summarize FailedAttempts = count() by user_username_s, bin(TimeGenerated, 5m)
| where FailedAttempts > 5
| order by TimeGenerated desc

// Privilege escalation detection
| where Category == "kube-audit"
| where verb_s in ("create", "update", "patch")
| where objectRef_resource_s in ("clusterroles", "clusterrolebindings", "roles", "rolebindings")
| project TimeGenerated, user_username_s, verb_s, objectRef_name_s, objectRef_resource_s

Phase 6: Compliance Automation

Azure Policy Assignments:

## Pod Security Baseline
az policy assignment create \
  --name 'aks-pod-security-baseline' \
  --policy '/providers/Microsoft.Authorization/policySetDefinitions/a8640138-9b0a-4a28-b8cb-1666c838647d' \
  --scope /subscriptions/<subscription-id>/resourceGroups/rg-devsecops-prod \
  --params '{
```text
"effect": {
  "value": "audit"
}```
  }'





## Required tags
az policy assignment create \
  --name 'require-resource-tags' \
  --policy '/providers/Microsoft.Authorization/policyDefinitions/96670d01-0a4d-4649-9c89-2d3abc0a5025' \
  --scope /subscriptions/<subscription-id> \
  --params '{
```text
"tagName": {
  "value": "Environment"
}```
  }'





Expected output:

{ "displayName": "Require tags on resources", "enforcementMode": "Default" }

Terminal output for az policy assignment create

Compliance Reporting:

// Policy compliance summary
PolicyEvent
| where TimeGenerated > ago(30d)
| summarize 
```text
TotalEvaluations = count(),
NonCompliant = countif(ComplianceState == "NonCompliant"),
Compliant = countif(ComplianceState == "Compliant")
by PolicyDefinitionName```
| extend ComplianceRate = (Compliant * 100.0) / TotalEvaluations
| order by ComplianceRate asc

// Security recommendations by severity
SecurityRecommendation
| where TimeGenerated > ago(7d)
| summarize Count = count() by RecommendationSeverity, RecommendationState
| order by RecommendationSeverity desc

Best Practices

  • Shift-Left Security: Integrate security checks early in CI pipeline (SAST before build)
  • Least Privilege: Use managed identities with minimal RBAC permissions
  • Secrets Rotation: Implement automated secret rotation with Key Vault
  • Image Signing: Sign container images with Notation and verify signatures at admission
  • Network Segmentation: Use Azure Policy to enforce network policies in AKS
  • Audit Logging: Enable comprehensive audit logs for all control plane operations
  • Vulnerability SLA: Define SLA for patching critical vulnerabilities (e.g., 24 hours)

Troubleshooting

Issue: Trivy scan fails with registry authentication error
Solution: Ensure ACR credentials are correctly configured in GitHub secrets; use managed identity where possible

Issue: Key Vault CSI driver pod crashes
Solution: Verify kubelet identity has "Key Vault Secrets User" role; check SecretProviderClass syntax

Issue: Azure Policy blocks legitimate deployments
Solution: Review policy parameters; add exemptions for specific namespaces/resources; use audit mode initially

Architecture Decision and Tradeoffs

When designing integrated solutions solutions with Azure + Power Platform, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/azure/architecture/
  • https://learn.microsoft.com/azure/well-architected/
  • https://learn.microsoft.com/power-platform/guidance/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/azure/well-architected/
  • Sample repositories: https://github.com/Azure/ArchitectureCenter
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • DevSecOps embeds security throughout the entire software lifecycle
  • GitHub Actions and Azure DevOps provide complementary CI/CD capabilities
  • Container scanning catches vulnerabilities before runtime deployment
  • Azure Policy automates compliance enforcement at scale
  • Defender for Containers provides runtime threat detection

Next Steps

  • Implement software bill of materials (SBOM) generation with Syft
  • Add supply chain security with Sigstore Cosign image signing
  • Integrate chaos engineering with Azure Chaos Studio
  • Deploy service mesh (Istio/Linkerd) for mTLS and fine-grained policies

Additional Resources

Conclusion

DevSecOps succeeds when security becomes a first‑class, automated quality attribute across every stage of delivery. By enforcing policy as code, verifying supply chain provenance, eliminating static secrets via federated identities, and instrumenting runtime with actionable telemetry, teams reduce risk while accelerating throughput. Treat findings and attestations as versioned artifacts, rehearse incident playbooks, and continuously refine gates based on real metrics—this turns compliance from a bottleneck into a paved path for safe, repeatable releases.


Ready to secure your cloud-native applications?

Discussion