Simple Flask project for practicing and validating CI/CD workflows.
- General overview
- Local run
- Azure setup
- GitHub secrets (required)
- Azure template deployment
- VS Code manual deploy
- Deployment Flow
- Notes
- Troubleshooting
- Additional documentation
- Practice secure OIDC-based Azure deployments
- Avoid publishing long-lived credentials
- Validate ARM-based infrastructure automation
- Create a reusable CI/CD template for future projects
- Includes Docker checks in CI: Local image build test / DockerHub published-image test
- Centralizes reusable shell logic under
scripts/for Docker and Azure operations - Includes a Jenkins pipeline (
Jenkinsfile) that replicates the DockerHub test flow as an alternative CI option
- Create venv:
python -m venv .venv - Activate venv:
\.venv\Scripts\Activate.ps1 - Install deps:
pip install -r requirements.txt
Prerequisites:
- Install Azure CLI (
az) locally.
Quick commands:
# Login
az login
# List subscriptions
az account list --output table
# Set the subscription you want to deploy to
az account set --subscription <your-subscription-id>Create GitHub secrets:
AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_SUBSCRIPTION_ID
For GitHub App authentication and Docker workflows, also set:
GH_APP_KEYDOCKERHUB_USERNAMEDOCKERHUB_TOKEN
Important:
AZURE_CLIENT_IDshould be the client ID of the identity used for OIDC authentication (managed identity or App Registration).AZURE_TENANT_IDmust match that identity tenant.AZURE_SUBSCRIPTION_IDmust be the target subscription.
DockerHub secret notes:
DOCKERHUB_USERNAMEmust be only the Docker Hub account name (example:brkndocker).DOCKERHUB_TOKENshould be a Docker Hub access token with permission to push images.
IMPORTANT
The Docker test workflows (.github/workflows/test-docker.yml and .github/workflows/test-dockerhub.yml) require GitHub App authentication to be set up. Without GH_APP_KEY and GH_APPLICATION_ID configured, these workflows will fail.
Check out this documentation for a guide on Github App authentication and how to safely use GitHub secrets for both OIDC and App token scenarios:
GitHub App Authentication Example
To replicate the DockerHub test flow outside of GitHub Actions using a local Jenkins pipeline, see:
In this repository the ARM template provisions a system-assigned managed identity attached to the Web App.
Alternatively, an Azure App Registration (Service Principal) can be used for CI/CD authentication.
In general:
-
App Registration is typically preferred for CI/CD pipelines, since it represents an external workload (e.g., GitHub Actions) authenticating to Azure using OIDC.
-
Managed Identity is commonly used by Azure resources themselves (VMs, App Service, Functions) to securely access other Azure services such as Key Vault, Storage, or databases.
-
App Registration with Flexible Federated Credentials also allows to run branch-specific workflows without long-lived secrets, by configuring the federated credential to only allow tokens from specific branches or workflows.
See the additional documentation for a step-by-step guide on configuring App Registration with OIDC:
Flexible Federated Credential Setup
- Scripts prompt for
WEBAPP_NAME,LOCATION,GITHUB_ORGANIZATION_NAME, andRESOURCE_GROUP. - Defaults come from
azuredeploy.parameters.json. - Customize values such as
webAppName,location,githubOrganizationName, SKU settings, andtagsin the parameters file. WEBAPP_NAMEmust be globally unique in Azure App Service.- Not all Azure regions support all SKUs/resources - (Canada Central is recommended for testing).
- Federated credential creation can fail if the specified values do not match your GitHub Actions OIDC subject.
- Deployment provisions App Service and identity resources from
azuredeploy.json.
Follow these steps on your local machine:
# Login to Azure and set subscription if not done before
az login
az account set --subscription <your-subscription-id>PowerShell (Windows):
# Go to the azure directory to have access to the scripts
cd azure
$env:WEBAPP_NAME="your-webapp-name"
$env:LOCATION="canadacentral"
$env:GITHUB_ORGANIZATION_NAME="your-github-user-or-org"
$env:RESOURCE_GROUP="your-rg-name"
# Run Script File
./deploy-webapp-powershell.ps1
#Force prompt without persisting new values:
./deploy-webapp-powershell.ps1 -NoCache
# Clear cached prompt values:
./deploy-webapp-powershell.ps1 -ClearCache
Bash:
# Go to the azure directory to have access to the scripts
cd azure
export WEBAPP_NAME=your-webapp-name
export LOCATION=canadacentral
export GITHUB_ORGANIZATION_NAME=your-github-user-or-org
export RESOURCE_GROUP=your-rg-name
# Run Script File
bash ./deploy-webapp-bash.sh
# Force prompt and ignore shell-cached values:
bash ./deploy-webapp-bash.sh --no-cache
# Clear cached prompt values:
bash ./deploy-webapp-bash.sh --clear-cache
# Optional: deploy directly with parameters file
az deployment group create \
--resource-group <your-rg> \
--template-file azuredeploy.json \
--parameters @azuredeploy.parameters.json- Linux App Service Plan (B1)
- Linux Python Web App (Python 3.12)
- System-assigned managed identity
- Startup command: Empty by default - (set later by deployment workflow or manual config)
- Tags including
repo=<repository-name>
- Install Azure App Service extension
- Sign in and select your Web App
- Right-click project folder -> Deploy to Web App...
- Configure the Managed Identity or App Registration in the Azure portal
- Add Federated credential and permissions to the Managed Identity / App Registration
- Startup command (if needed):
gunicorn --bind=0.0.0.0 --timeout 600 app.main:app
Main workflow deploys to an existing Azure Web App. It does not create one.
flowchart LR
A[GitHub Push] --> B[GitHub Actions]
B --> C[Build + Package]
C --> D[Azure OIDC Login]
D --> E[Deploy to App Service]
E --> F[Restart + Set Startup Command]
- Main deploy workflow:
.github/workflows/build-flask-wapp.yml - Optional dispatch inputs:
webapp_name,resource_group,tag_key(defaultrepo),tag_value - Resolution order:
webapp_name- repo variable
WEBAPP_NAME - tag lookup (
tag_key+tag_valueor repo-name default)
-
.github/workflows/publish-docker-image.yml- Builds and pushes the Jenkins Agent image (
brkndocker/jenkins-agent) to Docker Hub.
- Builds and pushes the Jenkins Agent image (
-
.github/workflows/test-docker.yml- Builds a local Docker image and runs the full local flow via
app/tests/docker_local_test.py. - Validates GitHub App token authentication (requires
GH_TOKENandTARGET_REPOenv vars). - Pushes test result markdown to the target repository directly from within the test (skipped on pull request events).
- Builds a local Docker image and runs the full local flow via
-
.github/workflows/test-dockerhub.yml- Builds
brkndocker/ghapp-testfromdocker/dockerhub-image/Dockerfileand runs in-container tests viaapp/tests/dockerhub_test.py. - Builds, tests, and pushes the image (including a SHA-tagged version) on each successful run.
- Builds
-
Alternative CI pipeline:
Jenkinsfile— replicates the DockerHub test flow outside of GitHub Actions using a Docker agent. See Jenkins Configuration for setup instructions.
-
scripts/azure-webapp-workflow.sh- Shared Azure helper for app resolution, preflight validation, packaging, deployment with retry, and startup configuration.
-
scripts/entrypoint.sh- Container entrypoint used inside the DockerHub image (
brkndocker/ghapp-test) for authenticated git clone with retry logic.
- Container entrypoint used inside the DockerHub image (
The two test workflows validate GitHub App token authentication from different execution contexts:
| Workflow | Test File | Execution Context | Git Clone Location |
|---|---|---|---|
| test-docker.yml | docker_local_test.py |
GitHub Actions runner | Inside container (from host) |
| test-dockerhub.yml | dockerhub_test.py |
Freshly built container | Inside container (entrypoint.sh) |
This dual-context approach ensures:
- GH_TOKEN works when used directly from GitHub Actions
- GH_TOKEN works when passed into a pre-built Docker container
- Git clone with retry logic works inside the container environment
- Flask app integration tests pass in both local and pre-built images
- Upload artifact with
app/andrequirements.txt - Download to
deploy_pkg, validate required files - Zip as
deploy.zip - Deploy with retry using shared script mode (
deploy_with_retry) - On success: set startup command and restart app using shared script mode (
configure_startup)
- Startup command used:
gunicorn --bind=0.0.0.0 --timeout 600 app.main:app
- Set subscription once per shell and verify with
az account show. - App Service names are globally unique.
- Security: GitHub OIDC only (no long-lived Azure secrets), least-privilege RBAC, and isolated Web App identity.
- The
.github/workflows/pgbench-test.ymlworkflow triggers automatically on push and pull requests topgbench/*branches, and also supports manualworkflow_dispatch(with optionalwebapp_nameandresource_groupinputs). - For
pgbench/*branch-based deployments to work with OIDC, configure an App Registration with Flexible Federated Credentials (see Flexible Federated Credential Setup).
- No web app resolved: provide
WEBAPP_NAMEvariable,webapp_nameon workflow run, or matching tag on the web app. AuthorizationFailed: grant at least Reader on the target Web App/Resource Group.- OIDC mismatch: ensure federated credential subject matches repo/branch/ref.
- Wrong subscription: run
az account showandaz account set --subscription <id>.
Detailed guides related to this CI/CD setup:
-
Flexible Federated Credential Setup
Step-by-step configuration for Azure OIDC authentication using flexible federated credentials. -
GitHub App Authentication Example
Overview of GitHub App authentication in GitHub Actions, explaining how it differs from PATs, how App tokens differ from the auto-generatedGITHUB_TOKEN, and how to safely use both for secure cross-repository automation. -
Jenkins Configuration
Step-by-step guide for setting up a local Jenkins instance with Docker-in-Docker, configuring the Docker Cloud agent, creating the required credentials, and running theJenkinsfilepipeline.
-
Azure OIDC authentication with GitHub Actions
https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure-openid-connect -
Flexible Federated Identity Credentials (Microsoft Entra)
https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-flexible-federated-identity-credentials -
Azure App Service deployment with GitHub Actions
https://learn.microsoft.com/en-us/azure/app-service/deploy-github-actions
