githubyamlazure-web-app-servicefastapi

Deploying and starting a FastAPI app in Azure Web App


I have deployed a FastAPI to azure web apps. I have used the following yaml:

webapps-actions

name: Build and deploy Python app to Azure Web App - fast-api-port

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python version
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Create and start virtual environment
        run: |
          python -m venv venv
          source venv/bin/activate
      
      - name: Install dependencies
        run: pip install -r requirements.txt
        
      # Optional: Add step to run tests here (PyTest, Django test suites, etc.)

      - name: Zip artifact for deployment
        run: zip release.zip ./* -r

      - name: Upload artifact for deployment jobs
        uses: actions/upload-artifact@v4
        with:
          name: python-app
          path: |
            release.zip
            !venv/

  deploy:
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: 'Production'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
    permissions:
      id-token: write #This is required for requesting the JWT

    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v4
        with:
          name: python-app

      - name: Unzip artifact for deployment
        run: unzip release.zip

      
      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_xx }}
          tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_xx }}
          subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_xx }}

      - name: 'Deploy to Azure Web App'
        uses: azure/webapps-deploy@v3
        id: deploy-to-webapp
        with:
          app-name: 'fast-api-port'
          slot-name: 'Production'

I can see the files now exists on the server: enter image description here

However when I navigate to the url of the web app I get following: enter image description here

How do I start the uvicorn server in the deployment? Somehow I need the fastapi to get started.

I added following in Configuration: enter image description here

Code:

from fastapi import FastAPI,Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi_azure_auth import SingleTenantAzureAuthorizationCodeBearer
import uvicorn
from fastapi import FastAPI, Security
import os
from typing import Dict

from settings import Settings

from pydantic import AnyHttpUrl,BaseModel

from contextlib import asynccontextmanager
from typing import AsyncGenerator

from fastapi_azure_auth.user import User

settings = Settings()

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """
    Load OpenID config on startup.
    """
    await azure_scheme.openid_config.load_config()
    yield


app = FastAPI(
    swagger_ui_oauth2_redirect_url='/oauth2-redirect',
    swagger_ui_init_oauth={
        'usePkceWithAuthorizationCodeGrant': True,
        'clientId': settings.OPENAPI_CLIENT_ID,
        'scopes': settings.SCOPE_NAME,
    },
)

if settings.BACKEND_CORS_ORIGINS:
    app.add_middleware(
        CORSMiddleware,
        allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
        allow_credentials=True,
        allow_methods=['*'],
        allow_headers=['*'],
    )

azure_scheme = SingleTenantAzureAuthorizationCodeBearer(
    app_client_id=settings.APP_CLIENT_ID,
    tenant_id=settings.TENANT_ID,
    scopes=settings.SCOPES,
)

class User(BaseModel):
    name: str
    roles: list[str] = []


@app.get("/", dependencies=[Security(azure_scheme)])
async def root():
    print("Yo bro")
    return {"whoIsTheBest": "DNA Team is"}

@app.get("/test", dependencies=[Security(azure_scheme)])
async def root():
    print("Yo test")
    return {"whoIsTheBest": "DNA Team is!"}

@app.get("/me", dependencies=[Security(azure_scheme)])
async def me(request: Request):
    print("Me")
    return User(roles=request.state.user.roles,name=request.state.user.name)

if __name__ == '__main__':
    uvicorn.run('main:app', reload=True)

    

Logs: enter image description here


Solution

  • I tried your code and successfully deployed it Azure Web app without any issues.

    I got the same error after deploying the application, so to resolve the issue I've configured the below startup command in my Azure Web app configuration section.

    gunicorn --worker-class uvicorn.workers.UvicornWorker --timeout 600 --access-logfile '-' --error-logfile '-' main:app
    

    enter image description here

    requirements.txt:

    fastapi-azure-auth
    pydantic-settings
    uvicorn
    pydantic
    fastapi
    

    main .py:

    import uvicorn
    from fastapi import FastAPI, Security
    from fastapi.middleware.cors import CORSMiddleware
    from fastapi_azure_auth import SingleTenantAzureAuthorizationCodeBearer
    from pydantic import AnyHttpUrl
    from pydantic_settings import BaseSettings
    class Settings(BaseSettings):
        BACKEND_CORS_ORIGINS: list[str | AnyHttpUrl] = ['http://localhost:8000']
        OPENAPI_CLIENT_ID: str = "<openapi-client-id>"
        APP_CLIENT_ID: str = "<App-client-id>"
        TENANT_ID: str = "<Tenant-id>"
        SCOPE_DESCRIPTION: str = "user_impersonation"
       
        @property
        def SCOPE_NAME(self) -> str:
            return f'api://{self.APP_CLIENT_ID}/{self.SCOPE_DESCRIPTION}'
       
        @property
        def SCOPES(self) -> dict:
            return {
                self.SCOPE_NAME: self.SCOPE_DESCRIPTION,
            }
      
        @property
        def OPENAPI_AUTHORIZATION_URL(self) -> str:
            return f"https://login.microsoftonline.com/{self.TENANT_ID}/oauth2/v2.0/authorize"
        
        @property
        def OPENAPI_TOKEN_URL(self) -> str:
            return f"https://login.microsoftonline.com/{self.TENANT_ID}/oauth2/v2.0/token"
     
        class Config:
            env_file = '.env'
            env_file_encoding = 'utf-8'
            case_sensitive = True
     
    settings = Settings()
     
    app = FastAPI(
        swagger_ui_oauth2_redirect_url='/oauth2-redirect',
        swagger_ui_init_oauth={
            'usePkceWithAuthorizationCodeGrant': True,
            'clientId': settings.OPENAPI_CLIENT_ID,
            'scopes': settings.SCOPE_NAME,
        },
    )
     
    if settings.BACKEND_CORS_ORIGINS:
        app.add_middleware(
            CORSMiddleware,
            allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
            allow_credentials=True,
            allow_methods=['*'],
            allow_headers=['*'],
        )
     
    azure_scheme = SingleTenantAzureAuthorizationCodeBearer(
        app_client_id=settings.APP_CLIENT_ID,
        tenant_id=settings.TENANT_ID,
        scopes=settings.SCOPES,
    )
     
    @app.on_event('startup')
    async def load_config() -> None:
        """
        Load OpenID config on startup.
        """
        await azure_scheme.openid_config.load_config()
     
    @app.get("/", dependencies=[Security(azure_scheme)])
    async def root():
        return {"message": "Hello World"}
     
    if __name__ == '__main__':
        uvicorn.run('main:app', reload=True)
    

    workflow file:

    name: Build and deploy Python app to Azure Web App - fastapiappad
     
    on:
      push:
        branches:
          - main
      workflow_dispatch:
     
    jobs:
      build:
        runs-on: ubuntu-latest
     
        steps:
          - uses: actions/checkout@v4
     
          - name: Set up Python version
            uses: actions/setup-python@v5
            with:
              python-version: '3.11'
     
          - name: Create and start virtual environment
            run: |
              python -m venv venv
              source venv/bin/activate
          - name: Install dependencies
            run: pip install -r requirements.txt
     
          - name: Zip artifact for deployment
            run: zip release.zip ./* -r
     
          - name: Upload artifact for deployment jobs
            uses: actions/upload-artifact@v4
            with:
              name: python-app
              path: |
                release.zip
                !venv/
     
      deploy:
        runs-on: ubuntu-latest
        needs: build
        environment:
          name: 'Production'
          url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
        permissions:
          id-token: write #This is required for requesting the JWT
     
        steps:
          - name: Download artifact from build job
            uses: actions/download-artifact@v4
            with:
              name: python-app
     
          - name: Unzip artifact for deployment
            run: unzip release.zip
          
          - name: Login to Azure
            uses: azure/login@v2
            with:
              client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_567E8DE5C0184859AA5CE7A1734B4FC2 }}
              tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_68D8756C56714C6D8C105DF2BC96EBDE }}
              subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_D8A75A645E934359908917C8364D5670 }}
     
          - name: 'Deploy to Azure Web App'
            uses: azure/webapps-deploy@v3
            id: deploy-to-webapp
            with:
              app-name: 'fastapiappad'
              slot-name: 'Production'
    

    I've successfully deployed the application to Azure Web App via GitHub actions.

    Output:

    enter image description here