skip to Main Content

I have built a Python Dash web application which has the following .env, .env.development, and .env.production files. And this file is currently deployed in an Azure App Service through a build&deploy.yml file CICD through Azure DevOps. But I want to implement such that when the web app is deployed to the DEV environment, it uses the .env.development file and if to PROD env, uses the .env.production file. Furthermore, is there a way to run locally and the app picks up the values from the .env.development and .env file?

A React programmer here, in React that is the way I used to code in terms of environment handling. Any tips for Python web application development in terms of environment handling? And how do I fix the problem I stated above? Any help or suggestions would be really helpful, thanks!

My .env file looks like this:

REACT_VERSION=18.2.0

AS I am using Dash Mantine Components (package from Dash), they require to create a .env file with the following value.

.env.development:

ENVIRONMENT="dev"

.env.production:

ENVIRONMENT="prod"

And this is the file that fetches the values of the dev or prod (env.py):

from dotenv import load_dotenv
import os

load_dotenv()
environment = os.getenv('ENVIRONMENT')
print("ENV: ", environment)

Currently when I run this locally, it prints: ENV: None

And this is my build&deploy.yml file:

trigger: 
  branches:
    include:
      - main

pr:
  autoCancel: false
  branches:
     include:
      - develop
 
pool:
  vmImage: 'vm-pool-image'
 
variables:
  pythonVersion: '3.12' 
 
stages:
- stage: ArchiveArtifact
  jobs:
  - job:
    steps:
    - task: UsePythonVersion@0
      inputs:
        versionSpec: '$(pythonVersion)'
        addToPath: true

    - script: |
        sed 's/${GITHUB_TOKEN}/'"$(GitHubToken)"'/' requirements.txt > temp_requirements.txt
        mv temp_requirements.txt requirements.txt
      displayName: 'Replace GitHub token in requirements.txt'
 
    - task: ArchiveFiles@2
      inputs:
        rootFolderOrFile: '$(Build.SourcesDirectory)'
        includeRootFolder: false
        archiveType: 'zip'
        archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
        replaceExistingArchive: true
 
    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'
 
- stage: DeployDev
  displayName: 'Deploy to Dev'
  dependsOn: ArchiveArtifact
  jobs:
  - deployment: DevDeploy
    pool: 'dev-agent-pool'
    environment: 'dev'
    strategy:
     runOnce:
       deploy:
        steps:
        - task: AzureWebApp@1
          inputs:
            azureSubscription: 'subscription'
            appType: 'webAppLinux'
            appName: 'web-app-dev-name'
            package: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
            deploymentMethod: 'auto'
            
- stage: DeployProd
  displayName: 'Deploy to Prod'
  dependsOn: DeployDev
  condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/main') , eq(variables['Build.SourceBranch'], 'refs/heads/release/*')))
  jobs:
  - deployment: ProdDeploy
    pool: 'agent-pool'
    environment: 'prod'
    strategy:
     runOnce:
       deploy:
        steps:
        - task: AzureWebApp@1
          inputs:
            azureSubscription: 'prod_subscription'
            appType: 'webAppLinux'
            appName: 'web-app-prod-name'
            package: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
            deploymentMethod: 'auto'

2

Answers


  1. Since you are deploying the same artifacts into different web apps, you may consider directly using os.getenv to get the environment variables value from each app settings instead of from each .env file in your repo.

    Here is my sample code snippet for your reference.

    @app.route("/env/")
    def env():
        env = os.getenv('env', 'default_env')
        build = os.getenv('build', 'default_build')
        return render_template(
            "env.html",
            env=env,
            build=build
        )
    

    env.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Flask App</title>
    </head>
    <body>
        <h1>Welcome to the Flask App</h1>
        <p>Environment: {{ env }}</br>Build: {{ build }}</p>
    
    </body>
    </html>
    

    YAML pipeline for deployment

    - stage: CD
      jobs:
      - deployment: Deploy
        environment: E-Dev
        strategy:
          runOnce:
            deploy:
              steps:
              - task: AzureRmWebAppDeployment@4
                inputs:
                  ConnectionType: 'AzureRM'
                  azureSubscription: 'ARMSvcCnnSub0'
                  appType: 'webAppLinux'
                  WebAppName: '$(WebAppDev)'
                  packageForLinux: '$(Pipeline.Workspace)/**/*.zip'
                  AppSettings: >
                    -env "dev"
                    -build "$(Build.BuildId)"
                    -PWD "$(SecretVar)"
    
    

    Image

    As another benefit, we can pass secret variable values during pipeline deployment without disclosing such values in our code.

    Login or Signup to reply.
  2. By default, load_dotenv() will read the .env file in your project.

    is there a way to run locally and the app picks up the values from the .env.development and .env file?

    Yes. You can use the following python code:

    load_dotenv('actual env file path')
    

    env.py

    from dotenv import load_dotenv
    import os
    
    load_dotenv()
    load_dotenv('.env.development')
    
    environment = os.getenv('ENVIRONMENT')
    REACT_VERSION = os.getenv('REACT_VERSION')
    
    print("ENV: ", environment)
    print("REACT_VERSION: ", REACT_VERSION)
    

    Result:

    enter image description here

    I want to implement such that when the web app is deployed to the DEV environment, it uses the .env.development file and if to PROD env, uses the .env.production file.

    To meet your requirement, you can set the app settings in the Web App environment variables(As Alvin shared in the answer) to let the python script to decide which .env file to load.

    Here is an example:

    env.py

    from dotenv import load_dotenv
    import os
    
    load_dotenv()
    APP_ENVIRONMENT = os.getenv("ENV") 
    if APP_ENVIRONMENT == 'dev':
     load_dotenv('.env.development')
    if APP_ENVIRONMENT == 'prod':
     load_dotenv('.env.production')
    environment = os.getenv('ENVIRONMENT')
    REACT_VERSION = os.getenv('REACT_VERSION')
    
    print("ENV: ", environment)
    print("REACT_VERSION: ", REACT_VERSION)
    

    You can manually set the ENV variable app settings in Web App-> Environment variables -> App settings.

    Or you can set it in Pipeline Azure Web APP task.

    For example:

    - task: AzureWebApp@1
      inputs:
        azureSubscription: 'subscription'
        appType: 'webAppLinux'
        appName: 'web-app-dev-name'
        package: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
        deploymentMethod: 'auto'
        appSettings: '-ENV dev'
    

    YAML Sample:

    stages:
    - stage: ArchiveArtifact
      jobs:
      - job:
        steps:
        - task: UsePythonVersion@0
          inputs:
            versionSpec: '$(pythonVersion)'
            addToPath: true
    
        - script: |
            sed 's/${GITHUB_TOKEN}/'"$(GitHubToken)"'/' requirements.txt > temp_requirements.txt
            mv temp_requirements.txt requirements.txt
          displayName: 'Replace GitHub token in requirements.txt'
     
        - task: ArchiveFiles@2
          inputs:
            rootFolderOrFile: '$(Build.SourcesDirectory)'
            includeRootFolder: false
            archiveType: 'zip'
            archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
            replaceExistingArchive: true
     
        - task: PublishBuildArtifacts@1
          inputs:
            PathtoPublish: '$(Build.ArtifactStagingDirectory)'
            ArtifactName: 'drop'
            publishLocation: 'Container'
     
    - stage: DeployDev
      displayName: 'Deploy to Dev'
      dependsOn: ArchiveArtifact
      jobs:
      - deployment: DevDeploy
        pool: 'dev-agent-pool'
        environment: 'dev'
        strategy:
         runOnce:
           deploy:
            steps:
            - task: AzureWebApp@1
              inputs:
                azureSubscription: 'subscription'
                appType: 'webAppLinux'
                appName: 'web-app-dev-name'
                package: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
                deploymentMethod: 'auto'
                appSettings: '-ENV dev'
                
    - stage: DeployProd
      displayName: 'Deploy to Prod'
      dependsOn: DeployDev
      condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/main') , eq(variables['Build.SourceBranch'], 'refs/heads/release/*')))
      jobs:
      - deployment: ProdDeploy
        pool: 'agent-pool'
        environment: 'prod'
        strategy:
         runOnce:
           deploy:
            steps:
            - task: AzureWebApp@1
              inputs:
                azureSubscription: 'prod_subscription'
                appType: 'webAppLinux'
                appName: 'web-app-prod-name'
                package: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
                deploymentMethod: 'auto'
                appSettings: '-ENV Prod'
    

    When the ENV value is set to dev in We APP app settings, the env.py script will read the .env.development file. Or it will read the .env.production file.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search