[{"data":1,"prerenderedAt":410},["ShallowReactive",2],{"project-azure-e2e-devops":3},{"id":4,"title":5,"body":6,"description":389,"duration":390,"extension":391,"featured":392,"github":393,"image":394,"live":395,"meta":396,"navigation":304,"order":170,"path":397,"role":395,"seo":398,"status":399,"stem":400,"team_size":395,"tech":401,"type":407,"year":408,"__hash__":409},"projects\u002Fproject\u002Fazure-e2e-devops.md","Azure End-to-End DevOps Pipeline",{"type":7,"value":8,"toc":381},"minimark",[9,14,18,22,25,63,67,72,86,100,104,109,112,238,242,253,377],[10,11,13],"h2",{"id":12},"the-problem","The Problem",[15,16,17],"p",{},"Manual infrastructure provisioning and application deployment create bottlenecks, configuration drift, and severe security vulnerabilities. Storing hardcoded credentials (like container registry passwords) inside production servers is a massive security risk. Furthermore, pushing code without automated vulnerability scanning often leads to deploying compromised container images to production, while lack of centralized telemetry makes post-deployment troubleshooting nearly impossible.",[10,19,21],{"id":20},"my-solution","My Solution",[15,23,24],{},"I engineered a fully automated, secure, and observable End-to-End (E2E) DevOps pipeline combining GitHub Actions, Terraform, and Docker on Microsoft Azure.",[26,27,28,36,51,57],"ul",{},[29,30,31,35],"li",{},[32,33,34],"strong",{},"GitOps Workflows:"," Automated the entire lifecycle from code push to deployment. Terraform plan outputs are automatically generated and commented directly onto Pull Requests for peer review before infrastructure changes are applied.",[29,37,38,41,42,46,47,50],{},[32,39,40],{},"Shift-Left Security:"," Integrated Trivy vulnerability scanning into the CI\u002FCD pipeline, automatically failing the build if ",[43,44,45],"code",{},"CRITICAL"," or ",[43,48,49],{},"HIGH"," vulnerabilities are detected in the Docker image.",[29,52,53,56],{},[32,54,55],{},"Zero Hardcoded Secrets:"," Utilized Azure Managed Identities (System-Assigned). The production Virtual Machine authenticates to the Azure Container Registry (ACR) securely without needing any stored passwords or tokens.",[29,58,59,62],{},[32,60,61],{},"Centralized Observability:"," Configured Application Insights and Log Analytics Workspace via Terraform, capturing both host-level metrics and application telemetry.",[10,64,66],{"id":65},"technical-deep-dive","Technical Deep Dive",[68,69,71],"h3",{"id":70},"architecture-decisions","Architecture Decisions",[15,73,74,77,78,81,82,85],{},[32,75,76],{},"Why Azure Managed Identities?","\nTraditionally, pulling a private Docker image requires running ",[43,79,80],{},"docker login"," with a username and password, storing those secrets in the server's environment. By attaching a System-Assigned Managed Identity to the VM with ",[43,83,84],{},"AcrPull"," RBAC roles, the infrastructure securely authenticates to Azure's API natively. If the server is ever compromised, there are no credential files for an attacker to steal.",[15,87,88,91,92,95,96,99],{},[32,89,90],{},"Why Terraform with GitHub Actions?","\nI decoupled the infrastructure provisioning (",[43,93,94],{},"terraform apply",") from the application build process (",[43,97,98],{},"docker build","). This ensures that compute, network, and monitoring resources are guaranteed to be in the correct state before the latest application artifact is deployed.",[68,101,103],{"id":102},"key-features-i-built","Key Features I Built",[105,106,108],"h4",{"id":107},"_1-shift-left-container-security-scanning","1. Shift-Left Container Security Scanning",[15,110,111],{},"Before any image is pushed to the Azure Container Registry, the GitHub Actions pipeline builds a local test image and scans it using Trivy. If vulnerabilities are found, the deployment is hard-stopped, protecting the production environment.",[113,114,119],"pre",{"className":115,"code":116,"language":117,"meta":118,"style":118},"language-yaml shiki shiki-themes github-light github-dark","# .github\u002Fworkflows\u002Fmain.yml\n- name: Run Trivy vulnerability scanner\n  uses: aquasecurity\u002Ftrivy-action@master\n  with:\n    image-ref: \"local\u002Fmy-webapp:test\"\n    format: \"table\"\n    exit-code: \"1\" # Fails the pipeline on detection\n    ignore-unfixed: true\n    vuln-type: \"os,library\"\n    severity: \"CRITICAL,HIGH\"\n","yaml","",[43,120,121,130,148,159,168,179,190,204,216,227],{"__ignoreMap":118},[122,123,126],"span",{"class":124,"line":125},"line",1,[122,127,129],{"class":128},"sJ8bj","# .github\u002Fworkflows\u002Fmain.yml\n",[122,131,133,137,141,144],{"class":124,"line":132},2,[122,134,136],{"class":135},"sVt8B","- ",[122,138,140],{"class":139},"s9eBZ","name",[122,142,143],{"class":135},": ",[122,145,147],{"class":146},"sZZnC","Run Trivy vulnerability scanner\n",[122,149,151,154,156],{"class":124,"line":150},3,[122,152,153],{"class":139},"  uses",[122,155,143],{"class":135},[122,157,158],{"class":146},"aquasecurity\u002Ftrivy-action@master\n",[122,160,162,165],{"class":124,"line":161},4,[122,163,164],{"class":139},"  with",[122,166,167],{"class":135},":\n",[122,169,171,174,176],{"class":124,"line":170},5,[122,172,173],{"class":139},"    image-ref",[122,175,143],{"class":135},[122,177,178],{"class":146},"\"local\u002Fmy-webapp:test\"\n",[122,180,182,185,187],{"class":124,"line":181},6,[122,183,184],{"class":139},"    format",[122,186,143],{"class":135},[122,188,189],{"class":146},"\"table\"\n",[122,191,193,196,198,201],{"class":124,"line":192},7,[122,194,195],{"class":139},"    exit-code",[122,197,143],{"class":135},[122,199,200],{"class":146},"\"1\"",[122,202,203],{"class":128}," # Fails the pipeline on detection\n",[122,205,207,210,212],{"class":124,"line":206},8,[122,208,209],{"class":139},"    ignore-unfixed",[122,211,143],{"class":135},[122,213,215],{"class":214},"sj4cs","true\n",[122,217,219,222,224],{"class":124,"line":218},9,[122,220,221],{"class":139},"    vuln-type",[122,223,143],{"class":135},[122,225,226],{"class":146},"\"os,library\"\n",[122,228,230,233,235],{"class":124,"line":229},10,[122,231,232],{"class":139},"    severity",[122,234,143],{"class":135},[122,236,237],{"class":146},"\"CRITICAL,HIGH\"\n",[105,239,241],{"id":240},"_2-zero-touch-bootstrapping-dynamic-telemetry","2. Zero-Touch Bootstrapping & Dynamic Telemetry",[15,243,244,245,248,249,252],{},"I utilized ",[43,246,247],{},"cloud-init"," to automate the VM setup upon creation. The script installs Docker, configures Nginx as a reverse proxy, waits for the Managed Identity to initialize, pulls the image, and uses ",[43,250,251],{},"sed"," to dynamically inject the Application Insights connection string into the container at runtime—keeping configuration out of the source code.",[113,254,258],{"className":255,"code":256,"language":257,"meta":118,"style":118},"language-bash shiki shiki-themes github-light github-dark","# terraform\u002Fmodules\u002Fcompute\u002Fcloud-init.yml\necho \"Logging in to Azure with Managed Identity...\"\naz login --identity\naz acr login --name ${acr_name}\n\n# Pull and run the application\ndocker run -d -p 8080:80 --name production-app $IMAGE_TAG\n\n# Inject Telemetry connection string securely at runtime\ndocker exec production-app sed -i 's|CONNECTION_STRING|${app_insights_connection_string}|g' \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\u002Findex.html\ndocker restart production-app\n","bash",[43,259,260,265,273,285,300,306,311,336,340,345,366],{"__ignoreMap":118},[122,261,262],{"class":124,"line":125},[122,263,264],{"class":128},"# terraform\u002Fmodules\u002Fcompute\u002Fcloud-init.yml\n",[122,266,267,270],{"class":124,"line":132},[122,268,269],{"class":214},"echo",[122,271,272],{"class":146}," \"Logging in to Azure with Managed Identity...\"\n",[122,274,275,279,282],{"class":124,"line":150},[122,276,278],{"class":277},"sScJk","az",[122,280,281],{"class":146}," login",[122,283,284],{"class":214}," --identity\n",[122,286,287,289,292,294,297],{"class":124,"line":161},[122,288,278],{"class":277},[122,290,291],{"class":146}," acr",[122,293,281],{"class":146},[122,295,296],{"class":214}," --name",[122,298,299],{"class":135}," ${acr_name}\n",[122,301,302],{"class":124,"line":170},[122,303,305],{"emptyLinePlaceholder":304},true,"\n",[122,307,308],{"class":124,"line":181},[122,309,310],{"class":128},"# Pull and run the application\n",[122,312,313,316,319,322,325,328,330,333],{"class":124,"line":192},[122,314,315],{"class":277},"docker",[122,317,318],{"class":146}," run",[122,320,321],{"class":214}," -d",[122,323,324],{"class":214}," -p",[122,326,327],{"class":146}," 8080:80",[122,329,296],{"class":214},[122,331,332],{"class":146}," production-app",[122,334,335],{"class":135}," $IMAGE_TAG\n",[122,337,338],{"class":124,"line":206},[122,339,305],{"emptyLinePlaceholder":304},[122,341,342],{"class":124,"line":218},[122,343,344],{"class":128},"# Inject Telemetry connection string securely at runtime\n",[122,346,347,349,352,354,357,360,363],{"class":124,"line":229},[122,348,315],{"class":277},[122,350,351],{"class":146}," exec",[122,353,332],{"class":146},[122,355,356],{"class":146}," sed",[122,358,359],{"class":214}," -i",[122,361,362],{"class":146}," 's|CONNECTION_STRING|${app_insights_connection_string}|g'",[122,364,365],{"class":146}," \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\u002Findex.html\n",[122,367,369,371,374],{"class":124,"line":368},11,[122,370,315],{"class":277},[122,372,373],{"class":146}," restart",[122,375,376],{"class":146}," production-app\n",[378,379,380],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":118,"searchDepth":132,"depth":132,"links":382},[383,384,385],{"id":12,"depth":132,"text":13},{"id":20,"depth":132,"text":21},{"id":65,"depth":132,"text":66,"children":386},[387,388],{"id":70,"depth":150,"text":71},{"id":102,"depth":150,"text":103},"A production-ready CI\u002FCD pipeline and immutable infrastructure setup on Azure, featuring automated security scanning, zero-secret authentication, and dynamic telemetry injection.","2 weeks","md",false,"https:\u002F\u002Fgithub.com\u002Fszuryuu\u002Fazure-e2e-devops","\u002Fimages\u002Fprojects\u002Fazure.png",null,{},"\u002Fproject\u002Fazure-e2e-devops",{"title":5,"description":389},"Completed","project\u002Fazure-e2e-devops",[402,403,404,405,406],"GitHub Actions","Terraform","Docker","Azure","Bash","Solo Project","2026","kxqFPEJyOQv4AN1fPXFZk2gMi9O4cyIOrq-vEsqje-U",1776582962960]