06 - CI/CD (Dedicated)¶
This tutorial sets up CI/CD for Dedicated with standard zip deployment. Dedicated supports Kudu/SCM and zipdeploy workflows, which makes GitHub Actions integration straightforward.
Prerequisites¶
- Completed 05 - Infrastructure as Code
- Function App deployed on Dedicated
- Variables set:
export RG="rg-func-dedicated-dev"
export APP_NAME="func-dedi-<unique-suffix>"
export PLAN_NAME="asp-dedi-b1-dev"
export STORAGE_NAME="stdedidev<unique>"
export LOCATION="koreacentral"
What You'll Build¶
You will package the Python Function App from apps/python, deploy it with Zip Deploy and remote build settings, and implement an automated GitHub Actions deployment workflow.
Infrastructure Context
Plan: Dedicated (B1) | Network: Public internet | VNet: ❌ (requires Standard+ tier)
Basic B1 has no VNet integration or private endpoints. The app runs on a fixed App Service Plan (always on, no scale-to-zero). VNet support requires upgrading to Standard (S1) or Premium (P1v3) tier.
flowchart TD
INET[Internet] -->|HTTPS| FA[Function App\nDedicated B1-P3v3\nLinux Python 3.11]
subgraph VNET["VNet 10.0.0.0/16"]
subgraph INT_SUB["Integration Subnet 10.0.1.0/24\nDelegation: Microsoft.Web/serverFarms"]
FA
end
subgraph PE_SUB["Private Endpoint Subnet 10.0.2.0/24"]
PE_BLOB[PE: blob]
PE_QUEUE[PE: queue]
PE_TABLE[PE: table]
PE_FILE[PE: file]
end
end
PE_BLOB --> ST["Storage Account"]
PE_QUEUE --> ST
PE_TABLE --> ST
PE_FILE --> ST
subgraph DNS[Private DNS Zones]
DNS_BLOB[privatelink.blob.core.windows.net]
DNS_QUEUE[privatelink.queue.core.windows.net]
DNS_TABLE[privatelink.table.core.windows.net]
DNS_FILE[privatelink.file.core.windows.net]
end
PE_BLOB -.-> DNS_BLOB
PE_QUEUE -.-> DNS_QUEUE
PE_TABLE -.-> DNS_TABLE
PE_FILE -.-> DNS_FILE
FA -.->|System-Assigned MI| ENTRA[Microsoft Entra ID]
FA --> AI[Application Insights]
RFP["📦 WEBSITE_RUN_FROM_PACKAGE=1\nNo content share required"] -.- FA
ALWAYS_ON["⚙️ Always On: true\nFixed capacity"] -.- FA
style FA fill:#5c2d91,color:#fff
style VNET fill:#E8F5E9,stroke:#4CAF50
style ST fill:#FFF3E0
style DNS fill:#E3F2FD flowchart LR
A[GitHub push] --> B[GitHub Actions workflow]
B --> C["Build and deploy apps/python"]
C --> D[Dedicated Function App] Steps¶
Step 1 - Build a deployable zip package¶
python -m venv .venv
source .venv/bin/activate
pip install --requirement apps/python/requirements.txt
cd apps/python
zip --recurse-paths ../functionapp.zip .
cd ..
Step 2 - Deploy with zipdeploy¶
az functionapp config appsettings set \
--name $APP_NAME \
--resource-group $RG \
--settings \
SCM_DO_BUILD_DURING_DEPLOYMENT=true \
ENABLE_ORYX_BUILD=true
az functionapp deployment source config-zip \
--name $APP_NAME \
--resource-group $RG \
--src functionapp.zip
CLI overrides SCM_DO_BUILD_DURING_DEPLOYMENT
The az functionapp deployment source config-zip command automatically sets SCM_DO_BUILD_DURING_DEPLOYMENT=false, which prevents pip install from running during deployment. For Python apps on Dedicated, prefer func azure functionapp publish $APP_NAME --python instead of manual zip deploy — it handles the remote build correctly.
Step 3 - Verify deployment and endpoint health¶
az functionapp deployment list-publishing-profiles \
--name $APP_NAME \
--resource-group $RG \
--output table
curl --request GET "https://$APP_NAME.azurewebsites.net/api/health"
Step 4 - Add GitHub Actions workflow (recommended)¶
Create .github/workflows/deploy-dedicated.yml:
name: deploy-dedicated
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy Azure Functions app
uses: Azure/functions-action@v1
with:
app-name: ${{ vars.AZURE_FUNCTIONAPP_NAME }}
package: apps/python
publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
scm-do-build-during-deployment: true
enable-oryx-build: true
Step 5 - Deployment slots (S1+ only)¶
Deployment slots are unavailable on B1. Upgrade to S1 or P1v2, then create and use a staging slot:
az appservice plan update \
--name $PLAN_NAME \
--resource-group $RG \
--sku S1
az functionapp deployment slot create \
--name $APP_NAME \
--resource-group $RG \
--slot staging
az functionapp deployment source config-zip \
--name $APP_NAME \
--resource-group $RG \
--slot staging \
--src functionapp.zip
az functionapp deployment slot swap \
--name $APP_NAME \
--resource-group $RG \
--slot staging \
--target-slot production
Requires Standard tier or higher
Deployment slots are not available on Basic (B1) tier. Upgrade to Standard (S1) or Premium (P1v2) before using slots.
Verification¶
az functionapp deployment source config-zip ...:
{
"active": true,
"author": "N/A",
"complete": true,
"deployer": "ZipDeploy",
"id": "xxxxxxxxxxxxxxxx",
"message": "Created via a push deployment",
"status": 4,
"url": "https://func-dedi-<unique-suffix>.scm.azurewebsites.net/api/deployments/xxxxxxxxxxxxxxxx"
}
curl --request GET "https://$APP_NAME.azurewebsites.net/api/health":
az functionapp deployment slot create ... (after S1 upgrade):
{
"hostNames": [
"func-dedi-<unique-suffix>-staging.azurewebsites.net"
],
"name": "func-dedi-<unique-suffix>/slots/staging",
"state": "Running"
}
Next Steps¶
You now have a repeatable Dedicated deployment pipeline with optional slot-based release flow on S1/P1v2.
See Also¶
- Tutorial Overview & Plan Chooser
- Python Language Guide
- Platform: Hosting Plans
- Operations: Deployment
- Recipes Index