CI/CD Pipeline with Docker and Azure
Build a CI/CD pipeline that auto-deploys a FastAPI app to Azure Container Apps.
Introduction
30 Second Summary
Every time a developer at your company finishes a feature, the code somehow ends up running on a live server minutes later. Nobody manually copies files or restarts anything. That entire journey from "code on my laptop" to "live on the internet" is automated by something called a CI/CD pipeline.
In this project, you will build that exact pipeline from scratch. You'll create a Python API, package it inside a Docker container, deploy it to Azure Container Apps, and wire up GitHub Actions so your app automatically redeploys every time code is merged.
What You'll Build
A complete deployment pipeline using Docker, GitHub Actions, and Azure Container Apps that automatically builds and deploys your FastAPI application when code is merged to the main branch.
By the end of this project, you'll have:
- A FastAPI application running inside a Docker container, deployed live to Azure.
- A GitHub Actions CI/CD pipeline that automatically builds and deploys your app when code is merged to main.
- Hands-on experience with a branch-based workflow and Azure security practices like managed identity and HTTPS enforcement.
- Secret Mission: Integrate Azure Key Vault to securely store and retrieve an API key in your app.
Are there any prerequisites?
You should have basic Python knowledge and be comfortable using a terminal. You'll also need a Windows PC with VS Code installed.
Before We Start
Before We Start...
Install Docker Desktop and Azure CLI
Before you can containerize your app and deploy it to Azure, you need two key tools on your machine. Docker lets you package your code into containers that run identically everywhere. Azure CLI lets you manage Azure resources directly from your terminal.
In this step, you will install both tools and confirm they are ready to use alongside Python and Git (which you already have).
In this step, get ready to:
- Install Docker Desktop on your Windows machine.
- Install Azure CLI for managing Azure resources from the terminal.
- Verify all four tools are working together.
Download and install Docker Desktop
Docker Desktop is the application that runs the Docker engine on Windows. It uses WSL 2 (Windows Subsystem for Linux) as its backend to run Linux containers on your machine.
- Check if Docker is already installed by running this command in your terminal:
docker --version
✔️ I see a version number
Docker is already installed on your machine. You can skip ahead to the next substep.
- Press the Windows key to open your search bar.
- Type Docker Desktop and press Enter to open it.
- Wait for Docker Desktop to finish starting up. You should see the Docker Desktop dashboard appear.
ⓧ Command not found
Docker is not installed yet. Follow these steps to install it.
- Open the Docker Desktop for Windows installation page in your browser.
- Click the Download for Windows button to download the installer.
- Double-click Docker Desktop Installer.exe to run the installer.
- When the installer asks which installation mode you prefer, select Per-user (recommended). This does not require administrator privileges.
- When prompted about WSL 2, ensure the Use WSL 2 instead of Hyper-V option is selected. This is the backend Docker uses to run Linux containers on Windows.
- Follow the remaining wizard prompts and select Close when the installation finishes.
What is WSL 2?
WSL 2 (Windows Subsystem for Linux) is a feature built into Windows that lets you run a full Linux environment. Docker needs this because containers are a Linux technology. WSL 2 provides the Linux kernel that Docker uses to run containers on your Windows machine.
Will this cost anything?
Docker Desktop is free for personal use, education, and small businesses (fewer than 250 employees and under $10M revenue). You are using it for personal learning, so no paid subscription is required.
- Press the Windows key to open your search bar.
- Type Docker Desktop and press Enter to open it.
- Wait for Docker Desktop to finish starting up. You should see the Docker Desktop dashboard appear.
Enabling WSL 2 may require admin privileges
If this is the first time WSL 2 has been enabled on your machine, Windows may ask for administrator credentials. This is a one-time setup. After WSL 2 is enabled, Docker Desktop can run without admin privileges going forward.
Docker Desktop not starting?
If Docker Desktop shows a WSL-related error, your system may need a restart to finish enabling the WSL 2 feature. Restart your computer and try opening Docker Desktop again.
If you see a message about virtualization being disabled, you may need to enable it in your BIOS settings. Search for your laptop model and "enable virtualization BIOS" for specific instructions.
Help me troubleshoot Docker Desktop not starting on Windows
Install Azure CLI
The Azure CLI is the command-line tool that lets you create and manage Azure resources without clicking through the portal. You will use it to create your container registry and deploy your app.
- Check if Azure CLI is already installed by running this command:
az version
✔️ I see version info
Azure CLI is already installed on your machine. You are ready to move on to the final verification.
ⓧ Command not found
Azure CLI is not installed yet. You will install it using WinGet, the Windows package manager.
- Install Azure CLI by running this command in PowerShell:
winget install --exact --id Microsoft.AzureCLI
What does this command do?
WinGet is the built-in Windows package manager. The --exact flag ensures you get the exact package named Microsoft.AzureCLI rather than a fuzzy match. The --id flag specifies the unique package identifier.
- The installer will ask you to accept the license agreement. Type Y and press Enter to proceed.
- Wait for the installation to complete. You should see a message confirming the install was successful.
- Close your current terminal window and open a new one. This is required so the terminal picks up the new az command path.
- Verify the installation by running:
az version
You should see version information printed in your terminal, confirming Azure CLI is ready.
Still seeing command not found after installing?
Make sure you closed and reopened your terminal after installation. The path update only takes effect in new terminal sessions.
If WinGet itself is not recognized, make sure you are running Windows 10 version 1809 or later with App Installer from the Microsoft Store.
Help me get Azure CLI working on Windows
Verify all tools are working
Now that both Docker and Azure CLI are installed, run a final check to confirm all four tools your project needs are ready.
- Confirm all four tools are working by running these commands:
docker --version
az version
python --version
git --version
Each command should return a version number. If any command fails, revisit the corresponding install section above.
One of the commands is not working?
If docker --version fails, make sure Docker Desktop is running. The Docker CLI only works when the Docker Desktop application is open.
If python --version fails, try python3 --version instead. On some Windows installations the command uses python3 as the executable name.
Help me get all my tools working
Your development environment is ready. Next up, you will create your FastAPI app and write the Dockerfile that packages it into a container.
Create Your FastAPI App and Dockerfile
Your development tools are all set up and verified. Docker, Azure CLI, Python, and Git are ready to go.
In this step, you'll write a simple API and package it into a container. Containers wrap your code and all its dependencies into a portable unit that runs the same way everywhere.
In this step, get ready to:
- Create a project folder and write a FastAPI app with two endpoints.
- Write a Dockerfile to containerize your app.
- Build and run the container locally to test it.
Create your project folder and FastAPI app
FastAPI is a modern Python web framework for building APIs. You'll create a simple app with two endpoints: one that reports the app's health status, and one that returns sample data.
- Move to your Desktop by running this command in your terminal:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir my-fastapi-app
cd my-fastapi-app
- Confirm the folder exists by running:
ls
You should see an empty output since the folder is brand new.
- Open this folder in VS Code by running:
code .
What does code . do?
The code command launches VS Code from the terminal. The . tells it to open the current folder as a workspace. Windows adds code to your system PATH automatically when you install VS Code.
code command not recognized?
If your terminal says code is not recognized, close and reopen your terminal, then try again. If it still fails, open VS Code manually: press the Windows key, type VS Code, press Enter, then use File > Open Folder to select your my-fastapi-app folder on the Desktop.
Help me open my project folder in VS Code
- In VS Code, select the Explorer view in the left sidebar.
- Click the New File... button at the top of the Explorer panel.
- Type main.py and press Enter.
- Paste the following code into main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def health_check():
return {"status": "healthy", "service": "my-fastapi-app"}
@app.get("/items")
def get_items():
return {
"items": [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 29.99},
{"id": 3, "name": "Keyboard", "price": 79.99},
]
}
What does this code do?
This creates a FastAPI application with two routes.
- app = FastAPI() creates the application instance that handles incoming HTTP requests.
- The / endpoint is a health check. It returns a simple JSON response confirming the service is running.
- The /items endpoint returns sample product data. This simulates a real API that serves data to a frontend.
- Save main.py by pressing Ctrl+S.
- Create another new file in the Explorer panel by clicking New File... again.
- Name it requirements.txt and press Enter.
- Paste the following into requirements.txt:
fastapi==0.115.12
uvicorn==0.34.3
What is requirements.txt?
This file lists the exact Python packages your app needs. fastapi is the web framework, and uvicorn is the server that runs your FastAPI app. Pinning exact versions (like ==0.115.12) ensures your app behaves the same everywhere it runs.
- Save requirements.txt by pressing Ctrl+S.
You should now see both main.py and requirements.txt listed in the Explorer sidebar.
✔️ Awesome, I've got everything!
Great. Make sure both files are saved before moving on.
ⓧ I'd like to double check the full code
Here is main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def health_check():
return {"status": "healthy", "service": "my-fastapi-app"}
@app.get("/items")
def get_items():
return {
"items": [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 29.99},
{"id": 3, "name": "Keyboard", "price": 79.99},
]
}
Here is requirements.txt:
fastapi==0.115.12
uvicorn==0.34.3
Write your Dockerfile
A Dockerfile is a recipe that tells Docker how to build an image of your app. It specifies the base environment, installs dependencies, copies your code, and defines how to start the app.
- Create another new file in the Explorer panel by clicking New File....
- Name it Dockerfile (no file extension) and press Enter.
- Paste the following into Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
What does this Dockerfile do?
- FROM python:3.12-slim starts from a lightweight Python 3.12 image. This gives you Python without the extra bulk of a full OS.
- WORKDIR /app sets the working directory inside the container.
- COPY requirements.txt . and RUN pip install installs dependencies first. Docker caches this layer, so it skips reinstalling if your dependencies haven't changed.
- COPY . . copies your application code into the container.
- CMD defines the command that runs when the container starts. It launches uvicorn on port 8000.
- Save Dockerfile by pressing Ctrl+S.
You should now see three files in the Explorer sidebar: Dockerfile, main.py, and requirements.txt.
Don't see all three files?
Make sure you created Dockerfile without any extension (not Dockerfile.txt). If Explorer shows it with a .txt extension, right-click the file and select Rename to remove it.
Help me fix my Dockerfile name
✔️ Awesome, I've got everything!
Great. Make sure the file is saved before moving on.
ⓧ I'd like to double check the full code
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Build and test your container locally
Now you'll use Docker to build an image from your Dockerfile and run it as a container. This is how you verify everything works before deploying to the cloud.
- Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, usually above Tab).
- Build your Docker image by running this command:
docker build -t my-fastapi-app .
What does this command do?
docker build reads your Dockerfile and creates an image. The -t my-fastapi-app flag gives the image a name (a tag) so you can reference it later. The . at the end tells Docker to use the current folder as the build context.
The first build takes a minute or two as Docker downloads the Python base image. You should see several steps completing, ending with a line like Successfully tagged my-fastapi-app:latest.
Build failing with an error?
Make sure Docker Desktop is running. You should see the Docker whale icon in your system tray. If the build fails with a network error, check your internet connection since Docker needs to download the base image.
If you see a file-not-found error, make sure your terminal is in the my-fastapi-app folder. Run ls and confirm you see Dockerfile, main.py, and requirements.txt.
Help me fix my Docker build
- Run your container by executing this command:
docker run -p 8000:8000 my-fastapi-app
What does this command do?
docker run starts a container from your image. The -p 8000:8000 flag maps port 8000 on your machine to port 8000 inside the container. This lets you access your app through your browser at http://localhost:8000.
You should see uvicorn start up with a message showing it's running on 0.0.0.0:8000.
- Open your browser and visit http://localhost:8000.
You should see a JSON response: {"status":"healthy","service":"my-fastapi-app"}.
- Try visiting http://localhost:8000/items to see your sample product data.
- When you're done testing, go back to your terminal and press Ctrl+C to stop the container.
Browser showing "site can't be reached"?
Make sure you see the uvicorn startup message in your terminal. If the terminal shows an error instead, check that port 8000 isn't already in use by another application.
Also confirm you typed the URL correctly: http://localhost:8000 (not https).
Help me access my containerized app in the browser
Your containerized API is running locally. Next up, you'll push this to Azure so it's accessible to anyone on the internet.
Set Up Azure Resources
Your FastAPI app is running perfectly in a local container. The next goal is to get it running live on the internet so anyone can access it.
To do that, you need cloud infrastructure. In this step, you will create an Azure account, set up a container registry to store your Docker image, and deploy the app to Azure Container Apps.
In this step, get ready to:
- Sign up for Azure and log in via the CLI.
- Create a container registry and build your image in the cloud.
- Deploy your app to Azure Container Apps and verify it is live.
Create your Azure free account and sign in
Azure gives new customers $200 in free credit for 30 days. This is more than enough to cover everything in this project.
- If you do not already have an Azure account, create one at azure.microsoft.com/free.
- Follow the sign-up prompts and complete identity verification.
- Once your account is ready, sign in to Azure from your terminal by running this command:
az login
A browser window opens asking you to sign in with your Microsoft account. After signing in, your terminal shows your subscription details.
- Install the Container Apps extension and register the required resource providers by running these commands:
az extension add --name containerapp --upgrade
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights
What do these commands do?
The az extension add --name containerapp --upgrade command installs the Azure CLI extension for Container Apps.
The two az provider register commands tell Azure to enable the Container Apps and Log Analytics services on your subscription. These providers are required before you can create a Container App.
Seeing a permissions error?
Make sure you completed az login successfully first. Your terminal should show your subscription name and ID.
If the provider registration command hangs, it can take a few minutes. Wait for it to complete before moving on.
Help me troubleshoot my Azure CLI login or provider registration.
Create your registry and build the image in Azure
Instead of pushing your local Docker image to Azure, you will build it directly in the cloud. Azure Container Registry (ACR) stores your Docker images and can build them from your Dockerfile without needing Docker locally.
- Create a resource group to hold all your Azure resources by running this command:
az group create --name myResourceGroup --location eastus
What is a resource group?
A resource group is a logical container in Azure that holds related resources together. When you are done with the project, you can delete the entire group to clean up everything at once.
- Create an Azure Container Registry by running this command:
az acr create --resource-group myResourceGroup --name [[ACRNAME="yourregistryname"]] --sku Basic
How do I choose a registry name?
Your registry name must be globally unique within Azure. It must be 5-50 characters and contain only lowercase letters and numbers (no dashes or special characters).
A good pattern is your name plus a few numbers. For example, sarahdev2026 or nextworkfastapi42.
- Make sure your terminal is in your project folder (the one containing your Dockerfile).
- Build your Docker image directly in Azure by running this command:
az acr build --registry [[ACRNAME="yourregistryname"]] --image my-fastapi-app:latest --file Dockerfile .
What does this command do?
The az acr build command uploads your project files (the build context) to Azure and runs docker build in the cloud. The built image is automatically stored in your registry.
The . at the end tells the command to use the current directory as the build context, which includes your Dockerfile, main.py, and requirements.txt.
You should see streaming build logs in your terminal, ending with a success message that the image was pushed to your registry.
Build failed or timed out?
Make sure you are running the command from inside your project folder (the one with Dockerfile). If you see a "file not found" error, double-check that your Dockerfile, main.py, and requirements.txt all exist in the current directory.
Help me fix my az acr build command that is failing.
Deploy to Azure Container Apps
Your image is now stored in Azure. The final piece is deploying it as a live, publicly accessible app using Azure Container Apps.
- Deploy your container app by running this command:
az containerapp up --name my-fastapi-app --resource-group myResourceGroup --registry-server [[ACRNAME="yourregistryname"]].azurecr.io --image [[ACRNAME="yourregistryname"]].azurecr.io/my-fastapi-app:latest --ingress external --target-port 8000
What does az containerapp up do?
This single command creates a Container Apps environment, pulls your image from the registry, and deploys it as a live app. The --ingress external flag makes your app publicly accessible on the internet.
The --target-port 8000 tells Azure which port your app listens on inside the container. This matches the EXPOSE 8000 in your Dockerfile and the uvicorn configuration in your CMD.
This command takes a few minutes to complete. When it finishes, look for a URL in the output that looks like https://my-fastapi-app.<random-text>.<region>.azurecontainerapps.io.
- Copy the URL from the terminal output.
- Paste it into your browser.
- You should see the JSON response from your health endpoint: {"status": "healthy", "service": "my-fastapi-app"}.
- Try adding /items to the end of the URL. You should see your items list in JSON format.
App not responding or showing an error?
Give it a minute after deployment finishes. Azure Container Apps needs time to start the container.
If you see a 404 or connection error after waiting, check that you copied the full URL including https://.
Help me troubleshoot my Azure Container App deployment that is not working.
Why az containerapp up instead of manual setup?
The az containerapp up command creates the Container Apps environment, Log Analytics workspace, and the app itself in a single command. This is Microsoft's recommended quickstart path for getting a container running in Azure as fast as possible.
Your app is now live on the internet, deployed from a Docker image stored in Azure Container Registry. Next up, you will set up a CI/CD pipeline with GitHub Actions so your app automatically redeploys whenever you merge code.
Set Up GitHub with Branching and CI/CD
Your app is live in Azure, but right now the only way to deploy changes is by running commands manually. In a real team, you never deploy by hand.
Instead, you connect your code repository to a CI/CD pipeline that automatically builds and deploys every time code is merged. In this step, you will set up GitHub Actions to deploy your Container App whenever code lands on the main branch.
In this step, get ready to:
- Push your project code to a new GitHub repository.
- Create a GitHub Actions workflow that deploys on push to main.
- Use a feature branch and pull request to trigger an automated deployment.
Push your code to GitHub and create a service principal
GitHub Actions needs a way to authenticate with Azure so it can deploy on your behalf. You will create a service principal, which is like a dedicated robot account that GitHub uses to talk to Azure securely.
- Initialize a Git repository in your project folder by running this command:
git init
- Add all your files and make your first commit by running these commands:
git add .
git commit -m "Initial commit"
- Rename the default branch to main by running:
git branch -M main
- Go to github.com in your browser.
- Click the + icon in the top right, then select New repository.
- Enter a repository name (e.g. my-fastapi-app).
- Leave all other settings as default and click Create repository.
- Copy the repository URL from the setup page (it looks like https://github.com/your-username/my-fastapi-app.git).
- Connect your local repo to GitHub and push your code by running these commands:
git remote add origin [[REPO_URL="https://github.com/your-username/my-fastapi-app.git"]]
git push -u origin main
What did you just do?
You initialized a local Git repository, committed your project files, and pushed them to a new GitHub repository. Your code now lives both on your machine and on GitHub.
Now create a service principal so GitHub can authenticate with Azure.
- Get your Azure subscription ID by running:
az account show --query id --output tsv
- Copy the ID that is printed (it looks like a long string of letters and numbers separated by dashes).
- Create the service principal by running this command, replacing the subscription ID placeholder with yours:
az ad sp create-for-rbac --name my-app-credentials --role contributor --scopes /subscriptions/[[SUBSCRIPTION_ID="your-subscription-id"]]/resourceGroups/myResourceGroup --json-auth --output json
What does this command do?
This creates a service principal named my-app-credentials with the Contributor role scoped to your myResourceGroup resource group.
The --json-auth flag tells Azure to output the credentials in the JSON format that GitHub Actions expects. The output contains clientId, clientSecret, subscriptionId, and tenantId.
- Copy the entire JSON output from the command. You will paste this into GitHub in the next step.
Now add this JSON as a secret in your GitHub repository.
- In your GitHub repository, click Settings in the top navigation bar.
- In the left sidebar, expand Secrets and variables, then click Actions.
- Click New repository secret.
- In the Name field, enter AZURE_CREDENTIALS.
- In the Secret field, paste the entire JSON output you copied.
- Click Add secret.
Getting a permissions error on the service principal command?
This command requires Owner or User Access Administrator role on the subscription. If you are using a free trial account, you should have Owner by default. Make sure you are logged in with az login before running the command.
Help me fix this permissions error
Create the GitHub Actions workflow
A GitHub Actions workflow is a YAML file that tells GitHub what to do when certain events happen. Your workflow will trigger only on pushes to main, which means deployments only happen when code is merged through a pull request.
- Create the workflow directory structure by running:
mkdir -p .github/workflows
- In VS Code, create a new file at .github/workflows/deploy.yml and paste the following code:
name: Deploy to Azure Container Apps
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build and deploy Container App
uses: azure/container-apps-deploy-action@v1
with:
appSourcePath: ${{ github.workspace }}
acrName: [[ACR_NAME="your-registry-name"]]
containerAppName: my-fastapi-app
resourceGroup: myResourceGroup
What does this workflow do?
- on: push: branches: - main means this workflow only runs when code is pushed to the main branch.
- azure/login@v1 authenticates GitHub with Azure using your AZURE_CREDENTIALS secret.
- azure/container-apps-deploy-action@v1 builds your Docker image using the Dockerfile in your repo, pushes it to your Azure Container Registry, and deploys it to your Container App.
Find your ACR name
Replace the acrName placeholder with the name of the Azure Container Registry you created in the previous step. ACR names are alphanumeric only (no dashes) and must be globally unique.
You can find yours by running this command in your terminal:
az acr list --query "[].name" --output tsv
- Save the file.
- Commit and push the workflow to main by running:
git add .
git commit -m "Add CI/CD workflow"
git push
- Go to your GitHub repository in the browser and click the Actions tab.
- You should see a workflow run triggered by your push. Wait for it to complete with a green checkmark.
Workflow failed with a red X?
Click on the failed workflow run to see the error logs. Common issues include a typo in acrName (must match your registry exactly) or an expired/invalid AZURE_CREDENTIALS secret.
Help me debug my failed workflow
✔️ Awesome, I've got everything!
Great. Double check you have saved .github/workflows/deploy.yml and that the workflow run succeeded on the Actions tab.
ⓧ I'd like to double check the full code
Here is the complete workflow file for comparison:
name: Deploy to Azure Container Apps
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build and deploy Container App
uses: azure/container-apps-deploy-action@v1
with:
appSourcePath: ${{ github.workspace }}
acrName: <your-registry-name>
containerAppName: my-fastapi-app
resourceGroup: myResourceGroup
Use a feature branch to trigger an automated deployment
Now you will simulate the real workflow your team uses every day. You will create a feature branch, make a code change, open a pull request, and merge it to main. The merge triggers your CI/CD pipeline to deploy automatically.
- Create a new feature branch by running:
git checkout -b feature/add-new-item
What is a feature branch?
A feature branch is a separate copy of your code where you make changes without affecting main. When your changes are ready, you open a pull request to merge them back. This keeps main stable and deployable at all times.
- Open main.py in VS Code.
- Find the items list inside the get_items() function.
- Add a new item after the Keyboard entry so the list looks like this:
@app.get("/items")
def get_items():
return {
"items": [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 29.99},
{"id": 3, "name": "Keyboard", "price": 79.99},
{"id": 4, "name": "Monitor", "price": 299.99},
]
}
What did you change?
You added a fourth item (Monitor) to the /items endpoint's response. This is a small, safe change that is easy to verify once deployed.
- Save main.py.
- Commit and push your feature branch by running:
git add .
git commit -m "Add Monitor to items list"
git push -u origin feature/add-new-item
- Go to your GitHub repository in the browser.
- You should see a banner that says your branch had recent pushes. Click Compare & pull request.
- Review the changes shown in the diff. You should see your new Monitor item.
- Click Create pull request.
- On the pull request page, click Merge pull request.
- Click Confirm merge.
This merge pushes your change to main, which triggers the workflow.
- Click the Actions tab in your repository.
- Watch the new workflow run. Wait for the green checkmark.
- Once the workflow completes, visit your Container App URL in the browser and add /items to the end.
- You should see the Monitor item in the JSON response.
Why this workflow matters
You just completed the exact cycle your team uses: branch off main, make a change, open a PR for review, merge, and let the pipeline deploy. Nobody ran a deploy command manually. The pipeline handled everything.
Don't see the Monitor item at /items?
The GitHub Actions workflow takes 2-3 minutes to build and deploy. Check the Actions tab to confirm the run finished successfully. If it failed, check the logs for the build step.
Help me troubleshoot my deployment
Your CI/CD pipeline is fully operational. Next up, you will harden your app's security with managed identity and HTTPS enforcement.
Secure Your App in Azure
Your app is live and auto-deploying from GitHub. But right now it's using admin credentials to pull images from your container registry.
In production, passwords stored anywhere are a risk. Your company eliminates this by using managed identity. The app proves who it is to Azure without any stored credentials. In this step, you'll switch to that approach and harden your app's network security.
In this step, get ready to:
- Enable managed identity to eliminate passwords for image pulls.
- Verify HTTPS-only ingress and understand Azure Front Door.
- Add and remove IP restrictions to practice least-privilege network access.
Enable managed identity for ACR pulls
Your Container App currently uses admin credentials (a username and password) to pull images from Azure Container Registry. Managed identity replaces those credentials with an automatically managed Azure identity. No passwords to rotate, no secrets to leak.
- Enable system-assigned managed identity on your Container App by running this command:
az containerapp identity assign -n my-fastapi-app -g myResourceGroup --system-assigned
What does this command do?
This attaches a system-assigned identity to your Container App. Azure creates and manages this identity automatically. When the app is deleted, the identity is deleted too.
- You should see JSON output that includes a principalId field. Copy this value. You'll need it in the next command.
- Assign the AcrPull role to your managed identity so it can pull images from your registry. Run:
az role assignment create --assignee [[PRINCIPAL_ID="your principalId from the previous output"]] --role AcrPull --scope $(az acr show -n [[ACR_NAME="your registry name"]] -g myResourceGroup --query id -o tsv)
What does this command do?
The AcrPull role grants your managed identity permission to pull container images from the registry. The --scope flag targets the specific registry resource.
- Now configure your Container App to use the managed identity for registry pulls instead of admin credentials. Run:
az containerapp registry set -n my-fastapi-app -g myResourceGroup --server [[ACR_NAME="your registry name"]].azurecr.io --identity system
What does this command do?
The --identity system flag tells the Container App to use its system-assigned managed identity when authenticating to the registry. No username or password is needed anymore.
- Verify the identity is active by running:
az containerapp identity show -n my-fastapi-app -g myResourceGroup
- You should see output showing "type": "SystemAssigned" with a principalId and tenantId.
Why managed identity instead of passwords?
Admin credentials are a shared username and password. Anyone with those credentials has full pull access to your registry. If they leak, you have to rotate them everywhere they're used.
Managed identity is tied to your specific app. Azure handles token generation and rotation. There's nothing to leak because no password exists. This is exactly how your company secures its Azure deployments.
Seeing an authorization error?
Make sure you copied the correct principalId from the identity assign output. If you used the wrong value, re-run the az role assignment create command with the correct ID.
If the registry set command fails, confirm that the role assignment has propagated. Azure role assignments can take up to a minute to take effect.
Why is my managed identity not pulling images?
Verify HTTPS-only ingress and understand Azure Front Door
Your Container App's ingress controls how traffic reaches your app. By default, Azure Container Apps enforces HTTPS. Let's confirm this is active on your app.
- Check your ingress configuration by running:
az containerapp ingress show -n my-fastapi-app -g myResourceGroup
- In the output, look for the allowInsecure field. It should be set to false.
What does allowInsecure: false mean?
When allowInsecure is false, any HTTP request on port 80 is automatically redirected to HTTPS on port 443. All traffic between clients and your app is encrypted.
This is the default for Azure Container Apps, so you don't need to change anything. Your app already enforces HTTPS.
What is Azure Front Door?
Your company layers Azure Front Door on top of Container Apps' built-in HTTPS. Front Door provides a Web Application Firewall (WAF) to block common attacks, DDoS protection to absorb traffic floods, and global load balancing to route users to the nearest region.
You won't set up Front Door in this project (it's a separate, larger piece of infrastructure). But now you understand why it exists in your company's architecture. HTTPS is the baseline. Front Door adds enterprise-grade protection on top.
Add IP restrictions
IP restrictions let you control exactly who can reach your app by network address. This demonstrates the principle of least-privilege network access. You'll add a rule to allow only your current IP, then remove it.
- First, find your current public IP address by running:
curl ifconfig.me
- You should see your public IP address printed in the terminal (for example, 203.0.113.42).
- Add an IP restriction rule that allows only your IP by running:
az containerapp ingress access-restriction set -n my-fastapi-app -g myResourceGroup --rule-name "allow-my-ip" --ip-address [[MY_IP="your IP address"]]/32 --description "Allow only my IP" --action Allow
What does this command do?
The --action Allow with a specific --ip-address means only traffic from that IP can reach your app. The /32 suffix means "exactly this one IP address" in CIDR notation.
Everyone else is blocked. This is how your company locks down internal services to only allow traffic from known networks.
- Verify the restriction is active by visiting your app's URL in the browser. You should still see the health check response because you're coming from the allowed IP.
- Now remove the restriction so your app is accessible again for testing. Run:
az containerapp ingress access-restriction remove -n my-fastapi-app -g myResourceGroup --rule-name "allow-my-ip"
- Visit your app's URL again to confirm it's publicly accessible.
Getting blocked after adding the restriction?
If you can't reach your app, your actual public IP might differ from what curl ifconfig.me reported (VPN, corporate proxy, or ISP NAT). Remove the rule with the remove command above and try again with your correct IP.
Help me fix my IP restriction rule
Your app is now secured with production-grade practices. Managed identity eliminates passwords, HTTPS encrypts all traffic, and IP restrictions demonstrate least-privilege access. Next up, you'll tackle the Secret Mission and integrate Azure Key Vault.
Secret mission
Integrate Azure Key Vault
Your company never puts secrets in code or config files. Add Azure Key Vault to your app so it can securely load an API key at startup using managed identity. Follow the same branch, PR, merge workflow to deploy it.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your Azure resources are covered by your $200 free credit, but the Container Registry charges approximately $0.167/day even when idle.
Cost warning
Your Azure Container Registry (Basic SKU) costs approximately $0.167/day (~$5/month), which comes out of your $200 free credit. The Container App itself is on the free tier (180,000 vCPU-seconds/month) and does not incur charges unless you exceed that allowance.
If you do not plan to use these resources again, choose the Delete option below to stop all charges immediately.
Resources you used:
- Azure Resource Group (myResourceGroup) containing your Container App, Container Registry, and Key Vault.
- Azure Service Principal (created for GitHub Actions authentication).
- GitHub repository with the AZURE_CREDENTIALS secret.
- Local project folder on your Desktop.
- Docker Desktop (installed in Step 1).
- Azure CLI (installed in Step 1).
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend this project soon.
- Your Container App stays live at its public URL. The free tier covers normal learning traffic.
- Your Container Registry continues storing your Docker image (~$5/month, deducted from your $200 free credit).
- Docker Desktop and Azure CLI remain installed on your machine for future projects.
Pause - I'll come back to this later
Shut down running compute to free up resources while keeping your files and cloud infrastructure intact.
- Scale your Container App to zero replicas by running this command:
az containerapp update --name my-fastapi-app --resource-group myResourceGroup --min-replicas 0 --max-replicas 0
- This stops all compute charges. Your Container Registry still charges for image storage (~$0.167/day).
- When you are ready to resume, push a new commit to main or scale replicas back up.
- Close Docker Desktop to free up system memory. Right-click the Docker icon in your system tray and select Quit Docker Desktop.
Delete - I don't want to use this again
Remove all project resources and stop all charges. This is irreversible.
- Sign out of Azure CLI to clear cached credentials:
az logout
- Delete your entire Azure resource group (this removes the Container App, Container Registry, and Key Vault in one command):
az group delete --name myResourceGroup --yes
- Delete the service principal you created for GitHub Actions. Find its app ID in the output you saved when you ran az ad sp create-for-rbac, then run:
az ad sp delete --id [[SP_APP_ID="your service principal appId"]]
- Delete your GitHub repository. Go to your repository on GitHub, click Settings, scroll to the bottom of the page, and click Delete this repository.
- Delete your local project folder by running:
Remove-Item -Recurse -Force ~\Desktop\my-fastapi-app
- Uninstall Docker Desktop. Open Settings on Windows, select Apps, then Installed apps. Search for Docker Desktop and click Uninstall.
- Uninstall Azure CLI. In the same Installed apps screen, search for Microsoft CLI 2.0 for Azure and click Uninstall.
That's a Wrap!
That's a Wrap!
Nice work! You just built the exact pipeline your company uses to get code from a developer's machine to a live Azure deployment.
You've learned how to:
- Build and containerize a FastAPI app with Docker, then deploy it to Azure Container Apps via Azure Container Registry.
- Set up a GitHub Actions CI/CD pipeline with a branch-based workflow that auto-deploys only when code is merged to main through a pull request.
- Secure your app with managed identity for password-free ACR pulls and HTTPS-only ingress to enforce encrypted traffic.
- Integrate Azure Key Vault for secure secret management, following the same pattern your company uses to keep credentials out of code.
You now understand every step of your company's deployment and security workflow. When your seniors talk about managed identity, Key Vault, or Front Door, you know exactly what those are and why they matter.
Ready to quiz yourself?