From Code to Cloud: Automating Django App Deployment with GitHub Actions and Azure Web Apps

Deploying applications seamlessly and securely is every developer's dream. With GitHub Actions and Azure Web Apps, that dream can become a reality. But as they say, no good deployment happens without a fair share of challenges. In this article, I’ll take you on a journey through building a CI/CD pipeline for deploying a Django-based application containerized with Docker to Azure Web Apps.

You’ll discover the workflow, the roadblocks we hit along the way, and how we tackled them to ensure smooth sailing.


The Big Picture

The goal was simple: automate the deployment of a Django application to Azure Web Apps. The app was containerized with Docker, and the pipeline was designed to:

  1. Build the Docker image.

  2. Push it to Docker Hub.

  3. Deploy it to an Azure Web App.

The tools in play:

  • GitHub Actions: To automate the workflow.

  • Docker: To containerize the application.

  • Azure Web Apps: For hosting the app.

  • Azure Service Principal: For secure deployments.

Sounds straightforward, right? Well, the devil is in the details. Let’s dive in.


The Workflow in Action

Here’s the GitHub Actions workflow YAML file that powered the entire process:

# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy container app to Azure Web App - djangok8ssneh

on:
  push:
    branches:
      - master
  workflow_dispatch:

jobs:
  build:
    runs-on: 'ubuntu-latest'

    steps:
    - uses: actions/checkout@v2

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Log in to registry
      uses: docker/login-action@v2
      with:
        registry: https://index.docker.io/v1/
        username: ${{ secrets.AzureAppService_ContainerUsername_392caf8095fc4c21aa31160e3232eb77 }}
        password: ${{ secrets.AzureAppService_ContainerPassword_89b68f62a5d849868b696eea8d35bf54 }}

    - name: Build and push container image to registry
      uses: docker/build-push-action@v3
      with:
        push: true
        tags: index.docker.io/${{ secrets.AzureAppService_ContainerUsername_392caf8095fc4c21aa31160e3232eb77 }}/djangok8s:${{ github.sha }}
        file: ./Dockerfile

  deploy:
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: 'production'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}

    steps:
    - name: Deploy to Azure Web App
      id: deploy-to-webapp
      uses: azure/webapps-deploy@v2
      with:
        app-name: 'djangok8ssneh'
        slot-name: 'production'
        publish-profile: ${{ secrets.AzureAppService_PublishProfile_aede91b4efc14e9bb4a65b3ecca7794e }}
        images: 'index.docker.io/${{ secrets.AzureAppService_ContainerUsername_392caf8095fc4c21aa31160e3232eb77 }}/djangok8s:${{ github.sha }}'

The Challenges and How We Overcame Them

1. Docker Push Access Denied

When the pipeline first attempted to push the Docker image, we encountered a dreaded error:

push access denied, repository does not exist or may require authorization

Root Cause:

  1. The repository djangok8s didn’t exist on Docker Hub.

  2. The PAT (Personal Access Token) lacked the required permissions.

Solution:

  • Created the Repository: Logged in to Docker Hub and manually created the djangok8s repository.

  • Generated a New PAT: Ensured the PAT had write access and updated it in GitHub Secrets.


2. Azure Web App Deployment Fails with ENOTFOUND

The deployment step failed with:

Error: getaddrinfo ENOTFOUND <app-name>.scm.azurewebsites.net

Root Cause: The .scm.azurewebsites.net endpoint for Kudu (Azure's deployment engine) wasn’t resolving due to DNS propagation issues.

Solution:

  1. Restarted the Web App:

     az webapp restart --name django-todo-app --resource-group my-resource-group
    
  2. Verified DNS Resolution: Ran nslookup to ensure the endpoint was reachable.


3. Authorization Failure for Azure Web App

Deployment failed due to incorrect Azure service principal credentials.

Solution:

  1. Regenerated the service principal:

     az ad sp create-for-rbac --name "github-action-sp" --role contributor --scopes /subscriptions/<subscription-id>
    
  2. Updated the AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID secrets in GitHub.


4. Excessive Image Tags

Each pipeline run generated a new image tag based on the commit SHA, cluttering Docker Hub.

Solution:

  1. Added a stable latest tag alongside the SHA tag:

     docker tag ${{ secrets.DOCKER_USERNAME }}/djangok8s:${{ github.sha }} ${{ secrets.DOCKER_USERNAME }}/djangok8s:latest
     docker push ${{ secrets.DOCKER_USERNAME }}/djangok8s:latest
    
  2. Scheduled periodic cleanups for unused tags.


Best Practices from the Experience

  1. Secure Secrets Management:

    • Store sensitive information like Docker credentials and Azure service principal in GitHub Secrets.

    • Rotate credentials regularly.

  2. Traceable Builds:

    • Use commit SHAs as image tags for easy debugging and traceability.

    • Always tag a stable version (latest) for production use.

  3. Fail Fast with Debugging:

    • Add debugging steps in the workflow to identify issues early, such as checking DNS resolution or listing Docker images.
  4. Automate Cleanup:

    • Periodically clean up unused Docker images to optimize storage on Docker Hub.

Conclusion

With a well-configured GitHub Actions workflow, deploying a Django app to Azure Web Apps becomes a breeze. The challenges along the way taught valuable lessons in debugging, secret management, and workflow optimization. By leveraging best practices, you can build a robust CI/CD pipeline that ensures smooth deployments.

What challenges have you faced while automating deployments? Let’s discuss in the comments!