Azure, Azure, and more Azure - GitHub Actions CI/CD | Deploy containers to Azure Web App
I’ve been using this setup for my own blog, and I wanted to document the process of deploying a containerized application to Azure Web App using GitHub Actions. In a previous post, I covered how to use Azure Container Registry Tasks to trigger builds automatically when code changes. This time, I want to focus on the GitHub Actions approach, which gives you more control over the build and deployment pipeline directly from your repository.
Self-study links
- Introduction to GitHub Actions (~30 min)
- Build and deploy applications to Azure by using GitHub Actions (~45 min)
- Deploy and run a containerized web app with Azure App Service (~46 min)
- Automate your workflow with GitHub Actions (GitHub Docs)
.
Notes:
GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository, or deploy merged pull requests to production.
Workflow
The idea is to create a CI/CD pipeline that builds a Docker image from our application, pushes it to Azure Container Registry (ACR), and deploys it to an Azure Web App. Every time we push code to the main branch, the pipeline will run automatically.
For our example, I’m going to use a React application scaffolded with Vite. But this approach works with any application you can containerize — a .NET API, a Node.js server, a static site generator like Hexo, etc.
Prerequisites
- An Azure subscription (free account)
- Azure CLI installed
- Docker installed
- A GitHub repository
Create the app
If you already have an application, skip this step. Otherwise, let’s scaffold a new React app using Vite.
NOTE: create-react-app is no longer maintained. Vite is the recommended alternative for new React projects.
1 | npm create vite@latest my-azure-app -- --template react |
Dockerize the app
Create a Dockerfile in the root of your project. We use a multi-stage build to keep the final image small — the first stage builds the application, and the second stage serves it with Nginx. This way, the final image only contains the static files and the web server, not the Node.js toolchain.
1 | # Stage 1: Build the app |
If you are using React Router or any client-side routing, you need to configure Nginx to redirect all requests to index.html. Create a nginx.conf file in the root of your project:
1 | server { |
You can test the Docker image locally before deploying:
1 | docker build -t my-azure-app . |
Then go to http://localhost:8080 and make sure everything works.
Set up Azure resources
First, log in to Azure and create the resources we need. I’m going to use real names so you can see the full picture — just replace them with yours.
1 | az login |
Create a Resource Group:
1 | az group create --name github-actions-rg --location eastus |
Create an Azure Container Registry. NOTE: The name must be alphanumeric and between 5 and 50 characters. Also, enable admin access so GitHub Actions can authenticate with username/password.
1 | az acr create \ |
Create an App Service Plan. This defines the compute resources for your Web App. You can use the F1 (Free) tier for testing.
1 | az appservice plan create \ |
Create the Azure Web App and link it to the ACR image:
1 | az webapp create \ |
Now, configure the Web App to pull images from your ACR. Get the ACR credentials first:
1 | az acr credential show --name myappacrregistry |
Then set the container configuration:
1 | az webapp config container set \ |
Configure GitHub Secrets
Go to your GitHub repository > Settings > Secrets and variables > Actions and add the following secrets:
- ACR_USERNAME: The ACR admin username (from
az acr credential show --name myappacrregistry) - ACR_PASSWORD: The ACR admin password (same command)
- AZURE_WEBAPP_PUBLISH_PROFILE: Go to the Azure Portal > your Web App > Overview > click Download publish profile and paste the entire XML content as the secret value
NOTE: For production environments, Azure recommends using OpenID Connect (OIDC) with federated credentials instead of passwords. This approach is more secure because it doesn’t require storing long-lived credentials. You can check the official guide if you want to set it up.
Create the GitHub Actions workflow
Create the file .github/workflows/deploy.yml in your repository:
1 | name: Build and deploy container to Azure Web App |
A few things to notice about this workflow:
- We use Docker Buildx for improved build performance and caching capabilities
- We push two tags: the commit SHA for traceability and
latestfor the Web App to pick up the most recent image - The build and deploy are separate jobs. This way, if the build fails, we don’t attempt deployment. The
needs: buildensures the deploy job waits for the build to complete - The
workflow_dispatchtrigger allows you to run the pipeline manually from the GitHub Actions tab - We use a publish profile for deployment authentication. This is simpler than configuring a service principal and works well for single-app deployments
Push and verify
Commit your changes and push to the main branch:
1 | git add . |
Go to the Actions tab in your GitHub repository and you should see the workflow running. If both jobs turn green, your app is deployed.
You can find your Web App URL in the Azure Portal under your Web App > Overview. It should be something like https://my-app-webapp.azurewebsites.net.
NOTE: The first deployment might take a few minutes because Azure needs to pull the image and start the container. Subsequent deployments will be faster.
Issues I had
If you run into problems, here are some issues I encountered:
“Admin user is disabled” error when pushing to ACR: Make sure admin access is enabled. Run
az acr update -n myappacrregistry --admin-enabled trueto enable it. You can also check it in the Azure Portal under your ACR > Access keys.“Application not registered with AAD” when deploying: Check that your publish profile is correct and hasn’t expired. Download a fresh one from the Azure Portal. If you’re using a service principal instead, make sure it has the
Contributorrole on the Web App andAcrPushon the ACR.Container fails to start on Azure Web App: Check the container logs in the Azure Portal under your Web App > Deployment Center > Logs. Common issues include wrong port configuration (Azure expects port 80 or 8080 by default) and missing environment variables.
404 errors with React Router: Make sure your
nginx.conffile has thetry_files $uri $uri/ /index.html;directive. Without it, Nginx won’t redirect deep links to your React application.Image not found during deploy: This usually happens when the image tag in the deploy step doesn’t match what was pushed in the build step. Make sure both steps use the same
${{ github.sha }}tag.
ACR Tasks vs GitHub Actions
In my previous post about containers, I covered how to use Azure Container Registry Tasks to trigger builds and deployments automatically. Both approaches are valid, but here’s my take:
ACR Tasks is great when you want Azure to handle everything — the build happens inside Azure, so you don’t need a GitHub Actions runner. It’s simpler if your pipeline is just “build and deploy”. But I had some issues getting it to work with private repositories and custom Dockerfile paths.
GitHub Actions gives you more flexibility. You can add linting, testing, notifications, and any other step you need. You also get better visibility into what’s happening through the GitHub UI. For most projects, this is what I would recommend.
Useful Links
- Deploy to Azure Web App using GitHub Actions
- Use a custom container for App Service
- GitHub Actions for Azure
- Docker Build Push Action
- Azure Login with OIDC
- Vite - Getting Started
Thanks for reading. I hope you find it useful.
❤️