π¨ Important Notice: This template is currently under active development and should be considered a DRAFT version. Features, configurations, and documentation may change without notice. Use in production environments is not recommended at this time.
A production-ready, secure, and compliant infrastructure template for deploying containerized applications to Azure Landing Zone environments. This template follows Azure Landing Zone security guardrails and BC Government cloud deployment best practices.
- Full-stack containerized application: NestJS backend + React/Vite frontend
- Secure Azure infrastructure: Landing Zone compliant with proper network isolation
- Database management: PostgreSQL with Flyway migrations and optional CloudBeaver admin UI
- CI/CD pipeline: GitHub Actions with OIDC authentication
- Infrastructure as Code: Terraform with Terragrunt for multi-environment management
- Monitoring & observability: Azure Monitor, Application Insights, and comprehensive logging
- Security best practices: Managed identities, private endpoints, and network security groups
- Azure CLI v2.50.0+ - Installation Guide
- GitHub CLI v2.0.0+ - Installation Guide
- Terraform v1.5.0+ - Installation Guide
- Terragrunt v0.50.0+ - Installation Guide
- Docker or Podman - Docker Installation
- BCGOV Azure account with appropriate permissions - Registry Link
- GitHub repository with Actions enabled
- Azure subscription with Owner or Contributor role
- Access to Azure Landing Zone with network connectivity configured
/quickstart-azure-containers
βββ .github/ # GitHub Actions CI/CD workflows
β βββ workflows/
β βββ pr-open.yml # PR validation and deployment
β βββ pr-close.yml # PR cleanup
β βββ pr-validate.yml # Code quality checks
β βββ prune-env.yml # Environment cleanup
βββ infra/ # Terraform infrastructure code
β βββ main.tf # Root configuration
β βββ providers.tf # Azure provider configuration
β βββ variables.tf # Global variables
β βββ outputs.tf # Infrastructure outputs
β βββ modules/ # Reusable infrastructure modules
β βββ backend/ # App Service for NestJS API
β βββ frontend/ # App Service for React SPA
β βββ postgresql/ # PostgreSQL Flexible Server
β βββ flyway/ # Database migration service
β βββ network/ # VNet, subnets, NSGs
β βββ monitoring/ # Log Analytics, App Insights
β βββ frontdoor/ # Azure Front Door CDN
βββ backend/ # NestJS TypeScript API
β βββ src/ # API source code
β β βββ users/ # User management module
β β βββ common/ # Shared utilities
β β βββ middleware/ # Request/response middleware
β βββ prisma/ # Database ORM configuration
β β βββ schema.prisma # Database schema definition
β βββ test/ # E2E API tests
β βββ Dockerfile # Container build configuration
βββ frontend/ # React + Vite SPA
β βββ src/ # Frontend source code
β β βββ components/ # React components
β β βββ routes/ # Application routes
β β βββ services/ # API integration
β β βββ interfaces/ # TypeScript interfaces
β βββ e2e/ # Playwright end-to-end tests
β βββ public/ # Static assets
β βββ Dockerfile # Container build configuration
βββ migrations/ # Flyway database migrations
β βββ sql/ # SQL migration scripts
β βββ Dockerfile # Migration runner container
β βββ entrypoint.sh # Migration execution script
βββ terragrunt/ # Environment-specific configurations
β βββ terragrunt.hcl # Root configuration
β βββ dev/ # Development environment
β βββ test/ # Testing environment
β βββ prod/ # Production environment
β βββ tools/ # Tools/utilities environment
βββ docker-compose.yml # Local development stack
βββ initial-azure-setup.sh # Azure setup automation script
βββ package.json # Monorepo configuration
# Use this template to create a new repository
gh repo create my-azure-app --template bcgov/quickstart-azure-containers --public
# Clone your new repository
git clone https://github.com/your-org/my-azure-app.git
cd my-azure-app
The initial-azure-setup.sh
script automates the complete Azure environment setup with OIDC authentication for GitHub Actions.
- Azure CLI logged in (
az login
) - GitHub CLI (optional, for automatic secret creation)
- Azure subscription with appropriate permissions
- Existing Azure Landing Zone resource group
# Make the setup script executable
chmod +x initial-azure-setup.sh
# Run with required parameters
./initial-azure-setup.sh \
--resource-group "ABCD-dev-networking" \
--identity-name "my-app-github-identity" \
--github-repo "myorg/my-azure-app" \
--environment "dev" \
--assign-roles "Contributor" \
--create-storage \
--create-github-secrets
# Production setup with custom storage and multiple roles
./initial-azure-setup.sh \
--resource-group "ABCD-prod-networking" \
--identity-name "my-app-prod-identity" \
--github-repo "myorg/my-azure-app" \
--environment "prod" \
--assign-roles "Contributor,Storage Account Contributor" \
--contributor-scope "/subscriptions/your-subscription-id" \
--create-storage \
--storage-account "myappprodtfstate" \
--create-github-secrets
# Dry run to preview changes
./initial-azure-setup.sh \
--resource-group "ABCD-dev-networking" \
--identity-name "my-app-github-identity" \
--github-repo "myorg/my-azure-app" \
--environment "dev" \
--assign-roles "Contributor" \
--create-storage \
--dry-run
π Identity & Authentication:
- Creates a user-assigned managed identity in your Landing Zone resource group
- Configures OIDC federated identity credentials for GitHub Actions
- Sets up environment-specific authentication (no secrets stored in Azure)
πΎ Terraform State Management:
- Creates a secure Azure storage account for Terraform state files
- Enables blob versioning for state file protection
- Configures appropriate access permissions for the managed identity
π GitHub Integration:
- Automatically creates GitHub environment if
--create-github-secrets
is used - Sets up required secrets in your GitHub repository:
AZURE_CLIENT_ID
AZURE_TENANT_ID
AZURE_SUBSCRIPTION_ID
VNET_NAME
(derived from resource group)VNET_RESOURCE_GROUP_NAME
β‘ Azure Permissions:
- Assigns specified roles to the managed identity
- Configures storage-specific permissions for Terraform state management
- Validates all configurations and provides verification
Parameter | Required | Description | Example |
---|---|---|---|
--resource-group |
β | Landing Zone resource group name | ABCD-dev-networking |
--identity-name |
β | Name for the managed identity | my-app-github-identity |
--github-repo |
β | Repository in format owner/repo | myorg/my-azure-app |
--environment |
β | GitHub environment name | dev , test , prod |
--assign-roles |
β | Comma-separated Azure roles | Contributor,Storage Account Contributor |
--contributor-scope |
β | Scope for role assignment | /subscriptions/xxx (default: subscription) |
--create-storage |
β | Create Terraform state storage | Flag (no value) |
--storage-account |
β | Custom storage account name | myapptfstate (auto-generated if not specified) |
--create-github-secrets |
β | Auto-create GitHub secrets | Flag (requires GitHub CLI) |
--dry-run |
β | Preview changes without execution | Flag (no value) |
After running the script, verify the setup:
# Check managed identity was created
az identity show --name "my-app-github-identity" --resource-group "ABCD-dev-networking"
# Verify federated credentials
az identity federated-credential list --identity-name "my-app-github-identity" --resource-group "ABCD-dev-networking"
# Test GitHub Actions authentication (in your repository)
gh workflow run test-azure-connection # if you have a test workflow
If you didn't use the --create-github-secrets
flag, manually add the following secrets to your GitHub repository (Settings > Secrets and variables > Actions > Environment secrets
):
AZURE_CLIENT_ID=<managed-identity-client-id>
AZURE_TENANT_ID=<your-azure-tenant-id>
AZURE_SUBSCRIPTION_ID=<your-azure-subscription-id>
VNET_NAME=<landing-zone-vnet-name>
VNET_RESOURCE_GROUP_NAME=<landing-zone-rg-name>
DB_MASTER_PASSWORD=<secure-database-password-min-12-chars>
π‘ Tip: The setup script outputs the exact values to use for these secrets if you didn't use auto-creation.
# Install dependencies for all packages
npm install
# Start local development environment
docker-compose up -d
# Run database migrations
docker-compose exec migrations flyway migrate
# Start backend development server
cd backend && npm run start:dev
# Start frontend development server (in new terminal)
cd frontend && npm run dev
Access your local application:
- Frontend: http://localhost:5173
- Backend API: http://localhost:3000 (default; see
docker-compose.yml
for overrides) - Database: localhost:5432 (postgres/default)
The repository includes comprehensive CI/CD workflows:
# Triggered on: Pull Request creation
# Actions:
# 1. Build and test frontend/backend containers
# 2. Run security scans and linting
# 3. Plan Terraform infrastructure changes
# 4. Ability to manually deploy for testing to tools
# 5. Run end-to-end tests
# Triggered on: Merge to main branch
# Actions:
# 1. Build and push production containers
# 2. Deploy to staging environment
# 3. Run full test suite
# 4. Deploy to production (with approval)
# Navigate to environment configuration
cd terragrunt/dev # or test/prod
# Initialize and plan
terragrunt init
terragrunt plan
# Apply changes
terragrunt apply
The template uses Flyway for database schema management:
-- V1.0.0__init.sql
CREATE SCHEMA IF NOT EXISTS app;
CREATE TABLE app.users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
# Local development
docker-compose exec migrations flyway migrate
# Production (via container)
docker run --rm \
-v $(pwd)/migrations/sql:/flyway/sql:ro \
-e FLYWAY_URL=jdbc:postgresql://your-db:5432/app \
-e FLYWAY_USER=your-user \
-e FLYWAY_PASSWORD=your-password \
flyway/flyway:11-alpine migrate
Optional CloudBeaver container provides web-based database management:
- Access:
https://your-app-cloudbeaver.azurewebsites.net
- Features: Query editor, schema browser, data export/import
- Private endpoints for all Azure services
- Network Security Groups with least-privilege rules
- Azure Front Door with WAF protection
- VNet integration for App Services
- Managed identities for service-to-service authentication
- OIDC authentication for GitHub Actions (no stored credentials)
- HTTPS everywhere with TLS 1.3 minimum
- Security headers (HSTS, CSP, X-Frame-Options)
- Container scanning in CI/CD pipeline
resource "azurerm_linux_web_app" "backend" {
# ... other configuration
site_config {
minimum_tls_version = "1.3"
ftps_state = "Disabled"
# IP restrictions for enhanced security
ip_restriction {
service_tag = "AzureFrontDoor.Backend"
action = "Allow"
priority = 100
headers {
x_azure_fdid = [var.frontend_frontdoor_resource_guid]
}
}
ip_restriction {
name = "DenyAll"
action = "Deny"
priority = 500
ip_address = "0.0.0.0/0"
}
}
}
resource "azurerm_application_insights" "main" {
name = "${var.app_name}-appinsights"
location = var.location
resource_group_name = var.resource_group_name
application_type = "web"
workspace_id = azurerm_log_analytics_workspace.main.id
}
resource "azurerm_log_analytics_workspace" "main" {
name = "${var.app_name}-log-analytics"
location = var.location
resource_group_name = var.resource_group_name
sku = var.log_analytics_sku
retention_in_days = var.log_analytics_retention_days
}
Access monitoring through:
- Azure Portal: Resource group > Monitoring
- Application Insights: Performance, failures, dependencies
- Log Analytics: Custom queries and alerts
- Azure Monitor: Infrastructure metrics and alerts
The GitHub Actions workflows include:
- Unit tests for frontend and backend
- Integration tests with test database
- E2E tests in containerized environment
- Security scanning of dependencies and containers
- Performance testing with load simulation
The template supports multiple environments with Terragrunt:
terragrunt/
βββ terragrunt.hcl # Root configuration
βββ dev/
β βββ terragrunt.hcl # Development overrides
βββ test/
β βββ terragrunt.hcl # Testing overrides
βββ prod/
β βββ terragrunt.hcl # Production overrides
βββ tools/
βββ terragrunt.hcl # Tools/utilities environment
include "root" {
path = find_in_parent_folders()
}
inputs = {
app_service_sku_name_backend = "B1"
app_service_sku_name_frontend = "B1"
postgres_sku_name = "B_Standard_B1ms"
backend_autoscale_enabled = false
enable_psql_sidecar = true
}
include "root" {
path = find_in_parent_folders()
}
inputs = {
app_service_sku_name_backend = "P1V3"
app_service_sku_name_frontend = "P1V3"
postgres_sku_name = "GP_Standard_D2s_v3"
backend_autoscale_enabled = true
enable_psql_sidecar = false
postgres_ha_enabled = true
}
Issue: OIDC authentication fails
Error: No subscription found. Run 'az account set' to select a subscription.
Solution:
- Verify
AZURE_CLIENT_ID
,AZURE_TENANT_ID
, andAZURE_SUBSCRIPTION_ID
secrets - Ensure managed identity has proper federated credentials
- Check that repository URL matches federated identity configuration
Issue: State file conflicts or locks
Error: Error acquiring the state lock
Solution:
# Force unlock (use with caution)
terragrunt force-unlock <lock-id>
# Or check Azure storage account permissions
az storage blob list --account-name your-storage --container-name tfstate
Issue: App Service fails to pull container (if using ACR)
Error: Failed to pull image: unauthorized
Solution:
- Verify managed identity has
AcrPull
role on container registry - Check container registry URL in app settings
- Ensure container image exists and is accessible
Issue: Backend cannot connect to PostgreSQL
Error: getaddrinfo ENOTFOUND your-postgres-server
Solution:
- Verify VNet integration and private endpoint configuration
- Check PostgreSQL firewall rules
- Ensure connection string environment variables are correct
- if you are using pgpool make sure you have this line
ssl: process.env.PGSSLMODE === 'require' ? { rejectUnauthorized: false } : false,
# Enable debug logging
az config set core.log_level=debug
# Check resource status
az webapp show --name your-app --resource-group your-rg
# View app service logs
az webapp log tail --name your-app --resource-group your-rg
- Terraform Azure Provider
- Terragrunt Documentation
- NestJS Documentation
- React + Vite Documentation
- Prisma Documentation
We welcome contributions to improve this template! Please see our Contributing Guidelines for details.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Built with β€οΈ by the NRIDS Team