This post walks through how to implement a “build once, deploy everywhere” pattern using Azure Developer CLI (azd) that provisions environment-specific infrastructure and promotes applications from dev to prod with the same build artifacts. You’ll learn how to use conditional Bicep deployment, environment variable injection, package preservation across environments, and automated CI/CD promotion from development to production.
Environment-Specific Infrastructure
When deploying applications across environments, different requirements emerge:
Component | Development | Production |
---|---|---|
Networking | Public access | VNet integration + Private endpoints |
Storage | Public with restrictions | Private endpoints only |
App Service Plan | B2 Basic | S1 Standard |
Security | Managed identity | Enhanced network isolation |
Rather than maintaining separate infrastructure templates or complex configuration files for each environment, we can use a single set of Bicep files that adapt based on an environment variable. This approach eliminates infrastructure drift while keeping deployments simple and consistent.
Set up resources based on an environment variable
To conditionally provision resources based on environment type, azd leverages an environment variable AZURE_ENV_TYPE
to make decisions at deployment time.
Behind the scenes, azd passes AZURE_ENV_TYPE
to Bicep as the envType
parameter:
@description('Environment type - determines networking configuration (dev/test/prod)')
@allowed(['dev', 'test', 'prod'])
param envType string = 'dev'
This parameter then drives conditional resource deployment in Bicep:
For Network Deployment:
// Deploy network infrastructure only for production environments
module network './network.bicep' = if (envType == 'prod') {
name: 'networkDeployment'
params: {
location: location
tags: tags
abbrs: abbrs
resourceToken: resourceToken
}
}
Similarly for Storage Configuration:
module storageAccount 'br/public:avm/res/storage/storage-account:0.17.2' = {
name: 'storageAccount'
params: {
name: storageAccountName
allowSharedKeyAccess: false
publicNetworkAccess: envType == 'prod' ? 'Disabled' : 'Enabled'
networkAcls: envType == 'prod' ? {
defaultAction: 'Deny'
virtualNetworkRules: []
bypass: 'AzureServices'
} : {
defaultAction: 'Allow'
virtualNetworkRules: []
bypass: 'AzureServices'
}
// ... other configuration
}
}
Finally for App Service Plan Sizing:
sku: {
name: envType == 'prod' ? 'S1' : 'B2'
tier: envType == 'prod' ? 'Standard' : 'Basic'
}
Enhance default CI/CD workflow
azd includes a powerful command to bootstrap CI/CD pipeline:
azd pipeline config
This generates a basic workflow. However, for dev-to-prod promotion, enhance it with these steps:
1. Package Once:
- name: Package Application
run: |
mkdir -p ./dist
azd package app --output-path ./dist/app-package.zip
# Create a backup copy for production deployment
cp ./dist/app-package.zip ./app-package-backup.zip
echo "✅ Application packaged and backup created"
2. Deploy to Dev:
- name: Deploy to Development
run: azd deploy app --from-package ./dist/app-package.zip --no-prompt
3. Validation Gate:
- name: Validate Application
run: |
echo "🔍 Validating application in development environment..."
# Add your validation logic here:
# - Health checks and integration tests
# - Security and compliance scanning
# - Performance validation
sleep 3 # Simulate validation time
echo "✅ Application validation passed"
4. Set environment variables:
- name: Promote to Production
run: |
# Create production environment name by replacing -dev with -prod, or adding -prod if no -dev suffix
PROD_ENV_NAME="${AZURE_ENV_NAME%-dev}-prod"
echo "Production environment name: $PROD_ENV_NAME"
# Set environment variables for this step
export AZURE_ENV_NAME="$PROD_ENV_NAME"
export AZURE_ENV_TYPE="prod"
5. Deploy to Prod:
# Use the same package created earlier - true "build once, deploy everywhere"
PACKAGE_PATH="./app-package-backup.zip"
if [ -f "$PACKAGE_PATH" ]; then
echo "🚀 Deploying to production using existing package: $PACKAGE_PATH"
azd deploy app --from-package "$PACKAGE_PATH" --no-prompt
echo "✅ Production deployment completed successfully"
6. Clean Up:
# Clean up the backup package after successful deployment
rm -f "$PACKAGE_PATH"
echo "🧹 Cleaned up package backup"
else
echo "❌ Package backup not found - falling back to regular deployment"
azd deploy --no-prompt
fi
Try It Out
You can try this approach using the complete implementation here.
Watch the walkthrough: See this entire process in action in the video below that shows the complete setup and deployment flow.
Step-by-Step Setup
Follow these steps to set up both development and production environments, then configure the CI/CD pipeline:
1. Initialize project from the template
azd init -t https://github.com/puicchan/azd-dev-prod-appservice-storage
This downloads the complete implementation with all Bicep files and enhanced GitHub Actions workflow.
2. Set Up Development Environment
azd up
When prompted for the environment name, use myproj-dev
(or your preferred naming pattern with -dev
suffix).
Note: The default envType
is dev
, so you don’t need to set the AZURE_ENV_TYPE
environment variable for development. The infrastructure will automatically provision with public access and cost-optimized resources.
3. Set Up Production Environment
Create and configure the production environment:
# Create new production environment
azd env new myproj-prod
# Set environment type to production
azd env set AZURE_ENV_TYPE prod
# Deploy production environment
azd up
This provisions production infrastructure with VNet integration, private endpoints, and enhanced security. Note that running azd up
deploys the app to the production infrastructure as well. This is for demonstration purpose plus we are still in development stage. In a real-world scenario, you’d probably never deploy your app directly if it is already in production.
4. Switch Back to Development Environment
azd env select myproj-dev
You’re now ready to develop and test in the development environment.
5. Make Code Changes
Edit your application code (e.g., modify app/templates/index.html
or app.py
) to test the promotion workflow.
6. Configure CI/CD Pipeline
azd pipeline config
The GitHub Actions workflow is enhanced with dev-to-prod promotion logic. Specifically, the pipeline will:
- Deploy and validate in development (`myproj-dev`)
- Automatically promote to production (`myproj-prod`) using the same package
- Handle environment naming conversion automatically
Once configured, every push to the main branch will trigger the automated dev-to-prod promotion pipeline!
Conclusion
In summary, this pattern combines conditional Bicep deployment, package preservation, and smart environment naming to create reliable dev-to-prod promotions with the same build artifacts.
Questions about implementation or want to share your own approach? Join the discussion here.
0 comments
Be the first to start the discussion.