Creating Automated Microsoft Bicep Application Infrastructure from GitHub in the Azure Cloud
Abstract
1. Introduction
2. Background and Related Work
2.1. Infrastructure as Code Concepts
2.2. Azure Bicep Overview
{ “$schema”: “https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#”, “contentVersion”: “1.0.0.0”, “resources”: [ { “type”: “Microsoft.Storage/storageAccounts”, “apiVersion”: “2022-09-01”, “name”: “[parameters(‘storageAccountName’)]”, “location”: “[resourceGroup().location]”, “sku”: { “name”: “Standard_LRS” }, “kind”: “StorageV2”, “properties”: {} } ], “parameters”: { “storageAccountName”: { “type": “string" } } } |
param storageAccountName string resource storageAccount ‘Microsoft.Storage/storageAccounts@2022-09-01’ = { name: storageAccountName location: resourceGroup().location sku: { name: ‘Standard_LRS’ } kind: ‘StorageV2’ } |
2.3. Comparison with Alternative Tools
2.4. Role of GitHub Actions in CI/CD
3. Project Overview and Objectives
3.1. Purpose of the Project
3.2. Business and Technical Problem Statement
3.3. Goals of Automated Deployment
- -
- Consistency: One must ensure that every deployment of the infrastructure is identical across all environments, thereby reducing drift and manual errors. Automated deployments reduce the risk of environment-specific configurations being unintentionally introduced. Each environment—development, QA, and production—is provisioned from the same source code and templates, guaranteeing a uniform setup. This consistency simplifies testing, reduces troubleshooting time, and increases overall system reliability. In turn, this creates a more predictable foundation for application behavior and performance across stages.
- -
- Maintainability: Azure Bicep’s modular, human-readable syntax can be used to make infrastructure code easy to review, update, and extend. By abstracting common patterns into discrete modules, updates can be localized without disrupting other components. The declarative nature of Bicep helps teams focus on what should be provisioned rather than how, making changes intuitive even for newcomers. Code is structured to align with best practices in software engineering, such as separation of concerns and single responsibility. This approach significantly reduces technical debt and allows the infrastructure to evolve with project needs.
- -
- Reusability: One can build reusable Bicep modules that encapsulate best practices for defining Azure resources, such as networking, storage, and compute. These modules are designed to be parameterized and environment-agnostic, enabling broad applicability across multiple projects. For example, a single networking module can serve different applications by adjusting only a few parameters, such as CIDR ranges or subnet names. Reusability also accelerates new deployments by reducing the need to rewrite foundational code from scratch. As modules mature, they serve as standardized building blocks aligned with organizational governance.
- -
- Automation: Manual deployment steps can be eliminated through GitHub Actions workflows, enabling truly continuous delivery of infrastructure. This automation reduces the burden on DevOps teams and minimizes the risk of human error during critical operations. Automated validation, formatting, and linting steps are also incorporated to maintain code quality throughout the CI/CD process. The automation design supports rapid iteration while maintaining compliance with organizational standards.
- -
- Security: Secure credential management can be integrated using GitHub Secrets, enforcing least privilege and protecting sensitive information during deployment. Service principles used for automation are scoped only to the necessary resources and roles, in line with the principle of least privilege. Credentials are stored in encrypted GitHub secrets, which are only accessible to approved workflows and never exposed in logs or code. This prevents unauthorized access and reduces the attack surface for the deployment pipeline. Additionally, secret rotation policies can be easily implemented to reduce the long-term risk of credential compromise.
- -
- Traceability: Git version control can be leveraged for all infrastructure definitions, enabling teams to track changes over time, perform code reviews, and roll back when necessary. Every modification to the infrastructure codebase is captured in a committed history, making it easy to audit and reason about changes. Pull requests can be used to conduct peer reviews and apply automated tests before code is merged. In the event of a failed deployment or misconfiguration, previous known-good states can be restored with minimal effort. This provides not only technical transparency but also governance and compliance value.
- -
- Collaboration: Developers and operations teams are enabled to work together on a single source of truth for infrastructure, fostering shared ownership and reducing silos. The adoption of IaC empowers developers to contribute directly to infrastructure definitions without relying solely on DevOps engineers. Documentation, comments, and a clear module structure lower the barrier to entry for team members with different backgrounds. Git-based workflows further encourage collaboration by integrating infrastructure into the same review and release processes as application code. This cultural shift supports agile methodologies and enhances organizational agility.
- -
- Cost Control: The creation of appropriately sized environments is supported, avoiding overprovisioning and enabling easier teardown of unused resources. Automation allows teams to rapidly provision ephemeral environments for testing, which can be destroyed automatically when no longer needed. Parameters and templates include options to define SKU sizes, storage tiers, and scaling rules, helping teams correctly size their infrastructure. Cost tracking tags are also integrated to provide better visibility into resource usage across environments. Over time, this improves budgeting accuracy and aligns cloud expenditure with actual operational needs.
- -
- It guides readers in understanding which aspects of the design were most critical to the authors.
- -
- It enables teams seeking to implement similar architectures to focus their resources strategically.
- -
- It lays the foundation for potential quantitative assessment of goal achievement in future extensions of the project.
3.4. Target Environments
- -
- Development Environment (Dev):
- -
- Quality Assurance Environment (QA):
- -
- Production Environment (Prod):
4. Solution Architecture
4.1. High-Level Architecture Diagram
- -
- A Virtual Network with multiple subnets (including delegated subnets for specific services).
- -
- The Azure Container App to run the application in a serverless container environment.
- -
- A PostgreSQL Flexible Server for the relational database needs of the application, connected through private endpoints.
- -
- Azure AI Search to provide robust search capabilities with integrated private endpoint connectivity.
- -
- Managed identity for secure authentication between the app and other Azure services without embedded secrets.
- -
- The Azure Container Registry to store container images for deployment.
4.2. Azure Resource Provision
4.2.1. Virtual Network
- -
- Definition of address space to segment IP ranges appropriately.
- -
- Multiple subnets, with dedicated subnets for application containers; database services; Azure AI Search; and private endpoints.
- -
- Delegated subnets where needed (for Azure Container Apps), enabling Azure to manage necessary network configurations while maintaining isolation.
- -
- Integration with Azure Private DNS Zones for private name resolution of resources.
4.2.2. Azure Container App
- -
- A container image pulled from Azure Container Registry (ACR).
- -
- Ingress configuration, optionally restricted to internal access only.
- -
- Managed Identity integration, allowing the app to authenticate securely to Azure resources (PostgreSQL, Azure AI Search) without storing secrets.
- -
- Scaling rules to automatically adjust capacity based on load.
4.2.3. PostgreSQL Flexible Server
- -
- Private endpoint connectivity to the VNet.
- -
- VNet integration for traffic isolation.
- -
- Tier and compute size parameters, allowing environments to use cost-optimized or production-grade configurations.
- -
- Admin credentials stored securely in Azure Key Vault, with deployment-time parameterization.
4.2.4. Azure AI Search
- -
- Search service provisioning, including SKU and partition count parameters;
- -
- Private endpoint configuration to eliminate public access;
- -
- Integration with the Virtual Network and Private DNS Zones.
4.2.5. Managed Identity
- -
- Secure authentication to Azure services such as PostgreSQL and Azure AI Search;
- -
- Bicep definition, including creating a Managed Identity at deployment, ensuring secure practices by default;
- -
- Managed identity roles to secure access to resources in the environment on the subscription level.
4.2.6. Private Endpoints and DNS Zones
- -
- The PostgreSQL Flexible Server;
- -
- Azure AI Search;
- -
- The Azure Container Registry (optional, if blocking public access).
- -
- Creation and association of DNS Zones with the Vnet;
- -
- Configuration of necessary A records for Private Endpoint IP addresses;
- -
- All Private Endpoints are part of the relevant resource module.
4.2.7. Azure Container Registry
- -
- ACR instance with a chosen SKU (Basic or Premium, depending on the environment);
- -
- Optional private endpoint for network-restricted access;
- -
- Role assignments for Managed Identity or GitHub Actions to push/pull images.
4.3. Network Security Design
4.3.1. Subnets and Segmentation
- -
- Application subnet, which hosts containerized workloads (Azure Container Apps);
- -
- Data subnet, which is reserved for stateful services such as PostgreSQL Flexible Server;
- -
- Private Endpoint subnet, which is dedicated to Private Endpoints.
4.3.2. Delegated Subnets
4.3.3. Private Endpoints and Private DNS Zones
- -
- Automatic resolution of service FQDNs to private IPs;
- -
- Simplified deployment and maintenance;
- -
- Fully private network communication.
4.4. Parameterization for Environments
5. Bicep Modules’ Design
5.1. Modular Approach
5.2. Main Orchestration (main.bicep)
5.3. Security Practices in Infrastructure Modules
6. GitHub Actions CI/CD Pipelines
6.1. Prerequisites and Setup
6.2. GitHub Secrets’ Structure
- -
- AZURE_CREDENTIALS—map, hash table;
{“clientId”: “xxx-xxx-xxx”,
“clientSecret”: “xxx-xxx-xxx”,
“subscriptionId”: “xxx-xxx-xxx”,
“tenantId”: “xxx-xxx-xxx”}
- -
- AZURE_CLIENT_SECRET—string, SPN secret;
- -
- AZURE_CLIENT_ID—string, SPN client ID;
- -
- AZURE_SUBSCRIPTION—string, Azure Subscription ID;
- -
- PG_PASSWORD—string between 8 and 128 characters long including characters from at least three of the following categories: uppercase letters, lowercase letters, numbers, and non-alphanumeric characters;
- -
- PG_USER—string, PSQL flex server username.
6.3. GitHub Actions Workflows
6.3.1. Deployment Workflow (deploy-multi-infra.yml)
- -
- Checking out the Repository: The workflow begins by fetching the latest code from the repository. This ensures both the application code and infrastructure as code (Bicep templates) are available to the pipeline.
- -
- Azure Login: Authentication consumes the secret. This step establishes a secure CLI session against the Azure subscription, scoped to the permissions of the service principal.
- -
- Resource Group Creation or Validation: The pipeline ensures the target Azure Resource Group exists (or creates it if absent).
- -
- Container Image Building and Pushing: One of the distinguishing features of this pipeline is that it integrates application deployment with infrastructure provisioning. The container image is built from source. It is then pushed to Azure Container Registry (ACR), ensuring that the deployed container app always references a reproducible artifact.
- -
- Bicep Deployment: This step deploys the infrastructure described in Bicep, injecting environment-specific parameters. The result is a fully provisioned Azure environment, including Virtual Networks, Container Apps, PostgreSQL Flexible Server, Managed Identities, Azure AI Search, Private Endpoints, DNS Zones, and ACR integration. By integrating the build, push, and deploy stages, the workflow achieves application-centric infrastructure deployments, aligning with best practices for cloud-native development.
6.3.2. Deletion Workflow (delete-infra.yml)
- -
- Checking out the Repository, which retrieves any required configuration;
- -
- Azure Login, which authenticates securely using the same credentials, ensuring only authorized SPN can initiate deletions;
- -
- Removing Managed Identity roles and Resource Group Deletion, which removes the MI-assigned roles and then deletes the entire resource group and all contained resources, providing a predictable and complete cleanup process. The process allows the pipeline to finish immediately while the deletion proceeds asynchronously in Azure.
6.4. Security Considerations
7. Discussion
7.1. Strengths of the Approach
7.2. Challenges and Limitations
7.3. Goal Evaluation and Efficiency Overview
7.4. Future Improvements
8. Conclusions
Author Contributions
Funding
Data Availability Statement
Conflicts of Interest
Appendix A. Virtual Network Bicep Module
param vnetName string param location string param addressPrefix string param subnets array resource vnet ‘Microsoft.Network/virtualNetworks@2023-11-01’ = { name: vnetName location: location properties: { addressSpace: { addressPrefixes: [addressPrefix] } subnets: [ for subnet in subnets: { name: subnet.name properties: union({ addressPrefix: subnet.prefix }, contains(subnet, ‘delegated’) && subnet.delegated != ‘’ ? { delegations: [ { name: ‘${subnet.name}-delegation’ properties: { serviceName: subnet.delegated } } ] } : {}) } ] } } output subnetNames array = [for subnet in subnets: subnet.name] output subnetIds array = [ for subnet in subnets: resourceId(‘Microsoft.Network/virtualNetworks/subnets’, vnet.name, subnet.name) ] output vnetId string = vnet.id output vnetName string = vnet.name |
Appendix B. Full Azure Container App Bicep Module for Secure Deployment
param containerAppName string param location string param containerAppImage string param environmentId string param managedIdentityId string param managedIdentityClientId string @secure() param postgresAdminUser string @secure() param postgresAdminPassword string param postgresServerName string param searchServiceEndpoint string param acrLoginServer string resource containerApp ‘Microsoft.App/containerApps@2024-10-02-preview’ = { name: containerAppName location: location identity: { type: ‘UserAssigned’ userAssignedIdentities: { ‘${managedIdentityId}’: {} } } properties: { template: { containers: [ { name: ‘app-container’ image: containerAppImage resources: { cpu: 32 memory: ‘256Gi’ } env: [ { name: ‘PG_USER’ secretRef: ‘postgres-username’ } { name: ‘PG_PASSWORD’ secretRef: ”postgres-password‘ } { name: ‘PG_HOST’ value: ‘${postgresServerName}.postgres.database.azure.com’ } { name: ‘PG_DB’ value: ”llm_database‘ } { name: ‘PG_PORT’ value: ‘5432’ } { name: ‘AZURE_MI_ID’ value: managedIdentityClientId } { name: ‘AZURE_SEARCH_ENDPOINT’ value: searchServiceEndpoint } { name: ‘AZURE_SEARCH_INDEX_NAME’ value: ‘llm-index’ } ] } ] } configuration: { ingress: { external: true targetPort: 80 transport: ‘auto’ } registries: [ { identity: managedIdentityId server: acrLoginServer } ] secrets: [ { name: ‘postgres-username’ value: postgresAdminUser } { name: ‘postgres-password’ value: postgresAdminPassword } ] } environmentId: environmentId } } |
Appendix C. Secure PostgreSQL Flexible Server Deployment Template (Bicep)
param location string param postgresServerName string param postgresAdminUser string @secure() param postgresAdminPassword string param dnsZonePsqlId string param psqlDelegatedSubnetId string resource flexibleServer ‘Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01’ = { name: postgresServerName location: location properties: { administratorLogin: postgresAdminUser administratorLoginPassword: postgresAdminPassword version: ‘13’ availabilityZone: ‘1’ storage: { storageSizeGB: 128 } network: { publicNetworkAccess: ‘Disabled’ delegatedSubnetResourceId: psqlDelegatedSubnetId privateDnsZoneArmResourceId: dnsZonePsqlId } } sku: { name: ‘Standard_D2s_v3’ tier: ‘GeneralPurpose’ } } resource postgresDatabase ‘Microsoft.DBforPostgreSQL/flexibleServers/databases@2022-12-01’ = { name: ‘environment-db’ parent: flexibleServer properties: {} } output postgresServerId string = flexibleServer.id |
Appendix D. Private Azure Cognitive Search Deployment (Bicep)
param searchServiceName string param location string param sku string = ‘basic’ param privateendpointsubnetId string param dnsZoneAiSearchId string resource search ‘Microsoft.Search/searchServices@2023-11-01’ = { name: searchServiceName location: location sku: { name: sku } properties: { publicNetworkAccess: ‘disabled’ networkRuleSet: {} replicaCount: 1 partitionCount: 1 hostingMode: ‘default’ authOptions: { aadOrApiKey: { aadAuthFailureMode: ‘http403’ } } } } resource aiSearchPE ‘Microsoft.Network/privateEndpoints@2023-05-01’ = { name: ‘ai-search-private-endpoint’ location: location properties: { subnet: { id: privateendpointsubnetId } privateLinkServiceConnections: [ { name: ‘${searchServiceName}-pls-connection’ properties: { privateLinkServiceId: search.id groupIds: [ ‘searchService’ ] } } ] } } resource privateDnsZoneGroup ‘Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01’ = { name: ‘default’ parent: aiSearchPE properties: { privateDnsZoneConfigs: [ { name: ‘search-dns-config’ properties: { privateDnsZoneId: dnsZoneAiSearchId } } ] } } output aiSearchPrivateEndpointId string = aiSearchPE.id output aiSearchPrivateEndpointName string = aiSearchPE.name output searchServiceId string = search.id output searchServiceEndpoint string = ‘https://${search.name}.search.windows.net’ |
Appendix E. Bicep Template for Managed Identity Provisioning and Role Assignment
param identityName string param location string resource userAssignedIdentity ‘Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31’ = { name: identityName location: location } output managedIdentityId string = userAssignedIdentity.id output managedIdentityClientId string = userAssignedIdentity.properties.clientId output managedIdentityPrincipalId string = userAssignedIdentity.properties.principalId |
param managedIdentityPrincipalId string targetScope = ‘subscription’ resource uaiRbacContributor ‘Microsoft.Authorization/roleAssignments@2022-04-01’ = { name: guid(‘containerAppManIdContributorRoleAssignment’, managedIdentityPrincipalId) scope: subscription() properties: { roleDefinitionId: subscriptionResourceId(‘Microsoft.Authorization/roleDefinitions’, ‘b24988ac-6180-42a0-ab88-20f7382dd24c’) // Contributor principalId: managedIdentityPrincipalId principalType: ‘ServicePrincipal’ } } resource uaiRbacAiSearch ‘Microsoft.Authorization/roleAssignments@2022-04-01’ = { name: guid(‘containerAppManIdSearchIndDataContrRoleAssignment’, managedIdentityPrincipalId) scope: subscription() properties: { roleDefinitionId: subscriptionResourceId(‘Microsoft.Authorization/roleDefinitions’, ‘8ebe5a00-799e-43f5-93ac-243d3dce84a7’) // Search Index Data Contributor principalId: managedIdentityPrincipalId principalType: ‘ServicePrincipal’ } } resource uaiRbacSearchContributor ‘Microsoft.Authorization/roleAssignments@2022-04-01’ = { name: guid(‘containerAppManIdSearchContributorRoleAssignment’, managedIdentityPrincipalId) scope: subscription() properties: { roleDefinitionId: subscriptionResourceId(‘Microsoft.Authorization/roleDefinitions’, ‘7ca78c08-252a-4471-8644-bb5ff32d4ba0’) // Search Service Contributor principalId: managedIdentityPrincipalId principalType: ‘ServicePrincipal’ } } resource uaiRbacAcrPull ‘Microsoft.Authorization/roleAssignments@2022-04-01’ = { name: guid(‘containerAppManIdAcrPullRoleAssignment’, managedIdentityPrincipalId) scope: subscription() properties: { roleDefinitionId: subscriptionResourceId(‘Microsoft.Authorization/roleDefinitions’, ‘7f951dda-4ed3-4680-a7ca-43fe172d538d’) // AcrPull principalId: managedIdentityPrincipalId principalType: ‘ServicePrincipal’ } } resource uaiRbacAcrPush ‘Microsoft.Authorization/roleAssignments@2022-04-01’ = { name: guid(‘containerAppManIdAcrPushRoleAssignment’, managedIdentityPrincipalId) scope: subscription() properties: { roleDefinitionId: subscriptionResourceId(‘Microsoft.Authorization/roleDefinitions’, ‘8311e382-0749-4cb8-b61a-304f252e45ec’) // AcrPush principalId: managedIdentityPrincipalId principalType: ‘ServicePrincipal’ } } |
Appendix F. Bicep Template for Private DNS Zones and VNet Integration
param vnetName string param vnetId string // Private DNS zone for Azure PostgreSQL resource dnsZonePsql ‘Microsoft.Network/privateDnsZones@2020-06-01’ = { name: ‘privatelink.postgres.database.azure.com’ location: ‘global’ } resource vnetLinkPsql ‘Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01’ = { name: ‘${vnetName}-link-psql’ location: ‘global’ parent: dnsZonePsql properties: { virtualNetwork: { id: vnetId } registrationEnabled: false } } /// Private DNS zone for Azure AI Search resource dnsZoneAiSearch ‘Microsoft.Network/privateDnsZones@2020-06-01’ = { name: ‘privatelink.search.windows.net’ location: ‘global’ } resource vnetLinkAiSearch ‘Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01’ = { name: ‘${vnetName}-link-ai-search’ location: ‘global’ parent: dnsZoneAiSearch properties: { virtualNetwork: { id: vnetId } registrationEnabled: false } } /// Private DNS zone for Azure Content Safety resource dnsZoneContentSafety ‘Microsoft.Network/privateDnsZones@2020-06-01’ = { name: ‘privatelink.cognitiveservices.azure.com’ location: ‘global’ } resource vnetLinkContentSafety ‘Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01’ = { name: ‘${vnetName}-link-content-safety’ location: ‘global’ parent: dnsZoneContentSafety properties: { virtualNetwork: { id: vnetId } registrationEnabled: false } } // Outputs output dnsZonePsqlId string = dnsZonePsql.id output dnsZonePsqlName string = dnsZonePsql.name output vnetLinkPsqlId string = vnetLinkPsql.id output dnsZoneAiSearchId string = dnsZoneAiSearch.id output dnsZoneAiSearchName string = dnsZoneAiSearch.name output vnetLinkAiSearchId string = vnetLinkAiSearch.id output dnsZoneContentSafetyId string = dnsZoneContentSafety.id output dnsZoneContentSafetyName string = dnsZoneContentSafety.name output vnetLinkContentSafetyId string = vnetLinkContentSafety.id |
Appendix G. Main Bicep Orchestration Module
///////// PARAMETERS ///////// param location string param vnetName string param addressPrefix string param subnets array param environmentName string param containerAppName string param containerAppImage string param identityName string param postgresServerName string param postgresAdminUser string @secure() param postgresAdminPassword string param searchServiceName string param searchSku string param acrLoginServerName string ///////// VARIABLES ///////// var subnetNamesArray = [for s in subnets: s.name] var appSubnetName = ‘app-subnet’ var appSubnetIndex = indexOf(subnetNamesArray, appSubnetName) var appSubnetId = vnet.outputs.subnetIds[appSubnetIndex] var psqlDelegatedSubnetName = ‘psql-delegated-subnet’ var psqlDelegatedSubnetIndex = indexOf(subnetNamesArray, psqlDelegatedSubnetName) var psqlDelegatedSubnetId = vnet.outputs.subnetIds[psqlDelegatedSubnetIndex] var privateendpointsubnetName = ‘private-endpoint-subnet’ var privateendpointsubnetIndex = indexOf(subnetNamesArray, privateendpointsubnetName) var privateendpointsubnetId = vnet.outputs.subnetIds[privateendpointsubnetIndex] var dnsZonePsqlId = privateDnsZone.outputs.dnsZonePsqlId var managedIdentityPrincipalId = identityModule.outputs.managedIdentityPrincipalId ///////// RESOURCES ///////// // Module RBAC role assignments module rbacRoles ‘resources/rbac-roles/rbacroles.bicep’ = { name: ‘deployRbacRoles’ scope: subscription() params: { managedIdentityPrincipalId: managedIdentityPrincipalId } } // Module Managed identity module identityModule ‘resources/managed-identity/managedidentity.bicep’ = { name: ‘deployManagedIdentity’ params: { identityName: identityName location: location } } //// Module AI Search module searchModule ‘resources/ai-search/search.bicep’ = { name: ‘deploySearchService’ params: { searchServiceName: searchServiceName location: location sku: searchSku privateendpointsubnetId: privateendpointsubnetId dnsZoneAiSearchId: privateDnsZone.outputs.dnsZoneAiSearchId } } // Module Private DNS zone module privateDnsZone ‘resources/private-dns-zones/dnszones.bicep’ = { name: ‘deployPrivateDnsZones’ params: { vnetName: vnetName vnetId: vnet.outputs.vnetId } } // Module Virtual network module vnet ‘resources/network/vnet.bicep’ = { name: ‘deployVnet’ params: { vnetName: vnetName location: location addressPrefix: addressPrefix subnets: subnets } } // Module Container app module containerAppModule ‘resources/container-app/containerapp.bicep’ = { name: ‘deployContainerApp’ params: { containerAppName: containerAppName location: location containerAppImage: containerAppImage environmentId: appEnvModule.outputs.environmentId managedIdentityId: identityModule.outputs.managedIdentityId postgresAdminPassword: postgresAdminPassword postgresAdminUser: postgresAdminUser postgresServerName: postgresServerName managedIdentityClientId: identityModule.outputs.managedIdentityClientId searchServiceEndpoint: searchModule.outputs.searchServiceEndpoint acrLoginServer: acrLoginServerName } } // Module Container app environment module appEnvModule ‘resources/app-env/appenv.bicep’ = { name: ‘deployAppEnv’ params: { environmentName: environmentName location: location appSubnetId: appSubnetId } } // Module PostgreSQL flexible server module psqlModule ‘resources/psql-flexible-server/flexibleserver.bicep’ = { name: ‘deployPSQLFlexServer’ params: { location: location postgresServerName: postgresServerName postgresAdminUser: postgresAdminUser postgresAdminPassword: postgresAdminPassword psqlDelegatedSubnetId: psqlDelegatedSubnetId dnsZonePsqlId: dnsZonePsqlId } } ///////// OUTPUTS ///////// output vnetName string = vnetName output subnetNames array = vnet.outputs.subnetNames output containerAppEnvironmentId string = appEnvModule.outputs.environmentId output managedIdentityId string = identityModule.outputs.managedIdentityId output managedIdentityClientId string = identityModule.outputs.managedIdentityClientId output managedIdentityPrincipalId string = identityModule.outputs.managedIdentityPrincipalId output searchServiceId string = searchModule.outputs.searchServiceId output searchServiceEndpoint string = searchModule.outputs.searchServiceEndpoint |
Appendix H. GitHub Actions Workflow for Infrastructure and Application Deployment
name: Deployment Workflow on: workflow_dispatch: inputs: environment: description: ‘Target environment’ required: true default: ‘dev’ type: choice options: - dev - qa - prod target: description: ‘Target deployment scope’ required: true default: ‘all’ type: choice options: - infra - acr - all jobs: Build-And-Push-Image: runs-on: ubuntu-latest environment: ${{ github.event.inputs.environment }} steps: - name: Checkout code uses: actions/checkout@v3 - name: Azure Login uses: azure/login@v2 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Ensure ACR RG Exists if: github.event.inputs.target == ‘acr’ || github.event.inputs.target == ‘all’ run: | RG_EXISTS=$(az group exists --name ${{ github.event.inputs.environment }}-acr-rg) if [ "$RG_EXISTS" = true ]; then echo "Resource group exists." else echo "Resource group does not exist. Creating …" az group create \ --name ${{ github.event.inputs.environment }}-acr-rg \ --location ‘swedencentral’ echo "Resource group ${{ github.event.inputs.environment }}-acr-rg created." fi - name: Ensure ACR exists if: github.event.inputs.target == ‘acr’ || github.event.inputs.target == ‘all’ shell: bash run: | ACR_NAME=${{ github.event.inputs.environment }}azurecontainerregistry ACR_RG=${{ github.event.inputs.environment }}-acr-rg if az acr show --name "$ACR_NAME" --resource-group "$ACR_RG" &>/dev/null; then echo "ACR $ACR_NAME already exists." else echo "ACR $ACR_NAME not found, creating…" az acr create \ --name "$ACR_NAME" \ --resource-group "$ACR_RG" \ --sku Premium \ --location swedencentral echo "ACR $ACR_NAME created." fi - name: ACR Login if: github.event.inputs.target == ‘acr’ || github.event.inputs.target == ‘all’ uses: docker/login-action@v3 with: registry: ${{ github.event.inputs.environment }}azurecontainerregistry.azurecr.io username: ${{ secrets.AZURE_CLIENT_ID }} password: ${{ secrets.AZURE_CLIENT_SECRET }} - name: Build and push image if: github.event.inputs.target == ‘acr’ || github.event.inputs.target == ‘all’ uses: docker/build-push-action@v4 with: context: app file: app/Dockerfile push: true tags: | ${{ github.event.inputs.environment }}azurecontainerregistry.azurecr.io/app-image:latest - name: Ensure Infra RG Exists if: github.event.inputs.target == ‘infra’ || github.event.inputs.target == ‘all’ run: | RG_EXISTS=$(az group exists --name ${{ github.event.inputs.environment }}-infra-rg) if [ "$RG_EXISTS" = true ]; then echo "Resource group exists." else echo "Resource group does not exist. Creating …" az group create \ --name ${{ github.event.inputs.environment }}-infra-rg \ --location ‘swedencentral’ echo "Resource group ${{ github.event.inputs.environment }}-infra-rg created." fi - name: Bicep Deploy if: github.event.inputs.target == ‘infra’ || github.event.inputs.target == ‘all’ uses: Azure/bicep-deploy@v2.1.0 with: name: infra-deployment type: deployment operation: create scope: resourceGroup subscription-id: ${{ secrets.AZURE_SUBSCRIPTION }} resource-group-name: ${{ github.event.inputs.environment }}-infra-rg template-file: infra/main.bicep parameters-file: infra/parameters/${{ github.event.inputs.environment }}.bicepparam parameters: > { "postgresAdminPassword":"${{ secrets.PG_PASSWORD }}", "postgresAdminUser":"${{ secrets.PG_USER }}" } |
Appendix I. GitHub Actions Workflow for Infrastructure Teardown
name: Delete Azure Resources on: workflow_dispatch: inputs: environment: description: ‘Target environment’ required: true default: ‘dev’ type: choice options: - dev - qa - prod target: description: ‘RG to delete’ required: true default: ‘all’ type: choice options: - infra - acr - all jobs: Delete-Resources: runs-on: ubuntu-latest environment: ${{ github.event.inputs.environment }} steps: - name: Checkout code uses: actions/checkout@v3 - name: Azure Login uses: azure/login@v2 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Delete Infra Resource Group if: github.event.inputs.target == ‘infra’ || github.event.inputs.target == ‘all’ run: | echo "Checking for Infra Resource Group: ${{ github.event.inputs.environment }}-infra-rg" INFRA_RG_EXISTS=$(az group exists --resource-group ${{ github.event.inputs.environment }}-infra-rg) if [ "$INFRA_RG_EXISTS" = true ]; then OBJECT_ID=$(az identity show --name "ContAppManIdentity-${{ github.event.inputs.environment }}" --resource-group "${{ github.event.inputs.environment }}-infra-rg" --query ‘principalId’ -o tsv) echo "Managed Identity found: $OBJECT_ID. Delete all role assignments." az role assignment delete --assignee "$OBJECT_ID" --scope /subscriptions/${{ secrets.AZURE_SUBSCRIPTION }} echo "Roles have been removed for Managed Identity" echo "Infra resource group exists. Deleting..." az group delete --resource-group "${{ github.event.inputs.environment }}-infra-rg" --yes echo "Infra resource group deleted." else echo "Infra resource group does not exist." fi - name: Delete ACR Resource Group if: github.event.inputs.target == ‘acr’ || github.event.inputs.target == ‘all’ run: | echo "Checking for ACR Resource Group: ${{ github.event.inputs.environment }}-acr-rg" RG_EXISTS=$(az group exists --resource-group ${{ github.event.inputs.environment }}-acr-rg) if [ "$RG_EXISTS" = true ]; then echo "ACR resource group exists. Deleting..." az group delete --resource-group "${{ github.event.inputs.environment }}-acr-rg" --yes echo "ACR resource group deleted." else echo "ACR resource group does not exist." fi |
References
- Pipinellis, A. GitHub Essentials: Unleash the Power of Collaborative Development; Packt Publishing: Birmingham, UK, 2018. [Google Scholar]
- Soni, M. Mastering GitHub Actions: Automate Your Workflow with CI/CD; Packt Publishing: Birmingham, UK, 2021. [Google Scholar]
- Zaal, S. Azure DevOps Explained; Packt Publishing: Birmingham, UK, 2021. [Google Scholar]
- Westby, E.J.H. Git for Teams: A User-Centered Approach to Creating Efficient Workflows in Git; O’Reilly Media: Sebastopol, CA, USA, 2015. [Google Scholar]
- Loeliger, J.; McCullough, M. Version Control with Git: Powerful Tools and Techniques for Collaborative Software Development; O’Reilly Media: Sebastopol, CA, USA, 2012. [Google Scholar]
- Laster, B. Learning GitHub Actions: Automation and Integration of CI/CD with GitHub; O’Reilly Media: Sebastopol, CA, USA, 2023. [Google Scholar]
- Silverman, R.E. Git Pocket Guide: A Working Introduction; O’Reilly Media: Sebastopol, CA, USA, 2013. [Google Scholar]
- Reddington, C. GitHub Actions in Action; Manning: Shelter Island, NY, USA, 2022. [Google Scholar]
- Forsgren, N.; Humble, J.; Kim, G. Accelerate: The Science of Lean Software and DevOps; IT Revolution Press: Portland, OR, USA, 2018. [Google Scholar]
- Zaal, S. Azure DevOps: A Complete Guide to CI/CD Pipelines; Packt Publishing: Birmingham, UK, 2022. [Google Scholar]
- Kim, G.; Humble, J.; Debois, P.; Willis, J. The DevOps Handbook: How to Create World-Class Agility, Reliability, and Security in Technology Organizations; IT Revolution Press: Portland, OR, USA, 2021. [Google Scholar]
- Humble, J.; Farley, D. Continuous Delivery: Reliable Software Releases Through Build, Test, and Deployment Automation; Addison-Wesley: Boston, MA, USA, 2010. [Google Scholar]
- Davis, J.; Daniels, R. Effective DevOps: Building a Culture of Collaboration, Affinity, and Tooling at Scale; O’Reilly Media: Sebastopol, CA, USA, 2016. [Google Scholar]
- Chacon, S.; Straub, B. Pro Git; Apress: New York, NY, USA, 2014. [Google Scholar]
- Morris, K. Infrastructure as Code: Managing Servers in the Cloud; O’Reilly Media: Sebastopol, CA, USA, 2016. [Google Scholar]
- Duvall, P.M.; Matyas, S.; Glover, A. Continuous Integration: Improving Software Quality and Reducing Risk; Addison-Wesley: Boston, MA, USA, 2007. [Google Scholar]
- Soni, M. DevOps with Azure: Implementing DevOps Using Microsoft Azure; Apress: New York, NY, USA, 2020. [Google Scholar]
- Humble, J.; Molesky, J.; O’Reilly, B. Lean Enterprise: How High-Performance Organizations Innovate at Scale; O’Reilly Media: Sebastopol, CA, USA, 2015. [Google Scholar]
- Price, M.J. Azure DevOps for Beginners: A Step-by-Step Guide to CI/CD Pipelines; Packt Publishing: Birmingham, UK, 2022. [Google Scholar]
- Moyle, E.; Kelley, D. Practical Cybersecurity Architecture—Second Edition: A Guide to Creating and Implementing Robust Designs for Cybersecurity Architects; Packt Publishing: Birmingham, UK, 2023. [Google Scholar]
- Hüttermann, M. DevOps for Developers; Apress: New York, NY, USA, 2012. [Google Scholar]
- Newman, S. Building Microservices: Designing Fine-Grained Systems, 2nd ed.; O’Reilly Media: Sebastopol, CA, USA, 2021. [Google Scholar]
- Sinha, C. Mastering Azure DevOps: A Comprehensive Guide to Implementing CI/CD Pipelines; Apress: New York, NY, USA, 2021. [Google Scholar]
- Arundel, J.; Domingus, J. Cloud Native DevOps with Kubernetes: Building, Deploying, and Scaling Modern Applications in the Cloud, 2nd ed.; O’Reilly Media: Sebastopol, CA, USA, 2022. [Google Scholar]
- Farcic, V.; Pope, D. DevOps Toolkit Series; Leanpub: Victoria, BC, Canada, 2020. [Google Scholar]
- Mitesh, S. Hands-On Azure DevOps: Implementing CI/CD Pipelines; Apress: New York, NY, USA, 2021. [Google Scholar]
- Leszko, R. Continuous Delivery with Docker and Jenkins: Create Secure Applications by Building Complete CI/CD Pipelines, 2nd ed.; Packt Publishing: Birmingham, UK, 2019. [Google Scholar]
- Limoncelli, T.; Chalup, S.; Hogan, C. The Practice of Cloud System Administration: DevOps and SRE Practices for Web Services; Addison-Wesley: Boston, MA, USA, 2021. [Google Scholar]
- Cain, R. Azure Infrastructure as Code Using Bicep; Apress: New York, NY, USA, 2023. [Google Scholar]
- Laster, B. GitHub Actions: The Complete Guide; O’Reilly Media: Sebastopol, CA, USA, 2024. [Google Scholar]
- Been, H. Infrastructure as Code on Azure with Bicep: Automating Resource Deployment; Manning Publications: Shelter Island, NY, USA, 2023. [Google Scholar]
- Soni, M. GitHub Actions for DevOps Engineers; Packt Publishing: Birmingham, UK, 2023. [Google Scholar]
- Laster, B. Professional Git; Wrox: Hoboken, NJ, USA, 2016. [Google Scholar]
- Krief, M. Learning DevOps: A Comprehensive Guide to Accelerating DevOps Culture Adoption with Terraform, Azure DevOps, Kubernetes, and Jenkins, 2nd ed.; Packt Publishing: Birmingham, UK, 2022. [Google Scholar]
- Machiraju, S.; Gaurav, S. Azure Automation Using the ARM Template and Bicep; Springer: Berlin/Heidelberg, Germany, 2023. [Google Scholar]
Feature/Tool | Bicep | Terraform | Pulumi | AWS CloudFormation |
---|---|---|---|---|
Type | Declarative | Declarative (HCL) | Imperative/Declarative (via code) | Declarative |
Cloud Provider Support | Azure-only | Multi-cloud (Azure, AWS, GCP) | Multi-cloud | AWS-only |
Language/Syntax | Domain-specific Bicep language | HashiCorp Configuration Language (HCL) | General-purpose languages (Python, TypeScript) | JSON or YAML |
Ease of Use | High (simple syntax, modularity) | Moderate (requires HCL familiarity) | Depends on programming experience | Moderate to low (verbose, complex for large deployments) |
Integration with Azure | Native support; rapid feature updates | Slower adoption of new Azure features | Depends on provider APIs | N/A |
Integration with AWS | None | Strong | Strong | Native |
Modularity andReusability | Strong support for modules | Strong module ecosystem | High (via programming constructs and libraries) | Moderate (nested stacks supported) |
State Management | Handled by Azure Resource Manager | Requires backend configuration | Abstracted or optional | Managed internally |
Learning Curve | Low (especially for Azure teams) | Moderate | Higher (for non-developers) | Moderate to high |
Complex Logic Support | Limited | Limited | Strong (due to the use of full programming languages) | Limited |
Feature Gaps/Limitations | Azure-only; no multi-cloud | Potential delays in support for new Azure features | Runtime environment required; complexity in orchestration | Verbose; complex YAML/JSON syntax |
Ideal Use Case | Azure-centric deployments | Multi-cloud or hybrid cloud strategies | Developer-focused teams building reusable components | AWS-centric, large-scale infrastructure |
Goal | Description | Weight (1–5) | Rationale |
---|---|---|---|
Security | Protection of credentials, access control, private networking | 5 | Critical for compliance and trust in production systems |
Consistency | Ensuring identical deployments across environments | 5 | Foundation for reproducibility and environment parity |
Automation | Fully automated deployment and teardown workflows | 5 | Enables CI/CD and reduces human error |
Maintainability | Modular structure, easy updates and reviews | 4 | Important for long-term evolution and code quality |
Traceability | Git-based version control, history, rollback capabilities | 4 | Enhances auditability and debugging |
Reusability | Parametrized modules applicable across projects | 3 | Useful but secondary to security and consistency |
Collaboration | Shared ownership across development and operations | 3 | Culturally important but harder to measure technically |
Cost Control | Right-sizing, ephemeral environments, teardown automation | 3 | Practical but dependent on business policies |
Disclaimer/Publisher’s Note: The statements, opinions and data contained in all publications are solely those of the individual author(s) and contributor(s) and not of MDPI and/or the editor(s). MDPI and/or the editor(s) disclaim responsibility for any injury to people or property resulting from any ideas, methods, instructions or products referred to in the content. |
© 2025 by the authors. Licensee MDPI, Basel, Switzerland. This article is an open access article distributed under the terms and conditions of the Creative Commons Attribution (CC BY) license (https://creativecommons.org/licenses/by/4.0/).
Share and Cite
Manolov, V.; Gotseva, D.; Hinov, N. Creating Automated Microsoft Bicep Application Infrastructure from GitHub in the Azure Cloud. Future Internet 2025, 17, 359. https://doi.org/10.3390/fi17080359
Manolov V, Gotseva D, Hinov N. Creating Automated Microsoft Bicep Application Infrastructure from GitHub in the Azure Cloud. Future Internet. 2025; 17(8):359. https://doi.org/10.3390/fi17080359
Chicago/Turabian StyleManolov, Vladislav, Daniela Gotseva, and Nikolay Hinov. 2025. "Creating Automated Microsoft Bicep Application Infrastructure from GitHub in the Azure Cloud" Future Internet 17, no. 8: 359. https://doi.org/10.3390/fi17080359
APA StyleManolov, V., Gotseva, D., & Hinov, N. (2025). Creating Automated Microsoft Bicep Application Infrastructure from GitHub in the Azure Cloud. Future Internet, 17(8), 359. https://doi.org/10.3390/fi17080359