1

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'
3
  • Would you consider using App Service environment variables instead? Commented Jul 10 at 10:03
  • Hi @JasonSusanto, Have you got a chance to check the answers below leverage the environment variables in App settings? Hope the information can help resolve your query in this post. Thx and wish you a happy weekend. Commented Jul 12 at 8:06
  • Hi @JasonSusanto, May I know if either of the answers below leverage the environment variables in App settings can help resolve your query in this post. Thx. Commented Jul 15 at 6:26

2 Answers 2

0

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.

0

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.