Skip to main content

Flask: Managing Environment Variables

Flask: Managing Environment Variables

Environment variables are a secure and flexible way to manage configuration settings in Flask applications, particularly for sensitive data like secret keys, database URLs, and API tokens. Built on Flask’s lightweight core and leveraging Jinja2 Templating and Werkzeug WSGI, Flask seamlessly integrates with environment variables to ensure modularity, security, and environment-specific configurations. This tutorial explores Flask managing environment variables, covering setup, best practices, and practical applications for secure and scalable configuration management.


01. Why Use Environment Variables in Flask?

Environment variables store configuration settings outside the codebase, preventing sensitive data exposure in version control and enabling easy switching between development, testing, and production environments. Flask’s configuration system, integrated with Jinja2 Templating for rendering and Werkzeug WSGI for request handling, supports environment variables to enhance security, maintainability, and deployment flexibility.

Example: Basic Environment Variable Usage

from flask import Flask
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', '0') == '1'

@app.route('/')
def index():
    return f"Secret: {app.config['SECRET_KEY']}, Debug: {app.config['DEBUG']}"

if __name__ == '__main__':
    app.run()

Shell Command to Set Variables:

export SECRET_KEY='my-secret'
export FLASK_DEBUG='1'
python app.py

Output (visiting /):

Secret: my-secret, Debug: True

Explanation:

  • os.environ.get - Retrieves environment variables with a fallback.
  • SECRET_KEY - Secures sessions.
  • FLASK_DEBUG - Controls debug mode.

02. Key Environment Variable Techniques

Flask supports environment variables through Python’s os module and configuration methods. The table below summarizes key techniques and their applications:

Technique Description Use Case
Direct Access os.environ.get Load variables into app.config
Config Integration app.config.from_envvar Load config file path from variable
Environment Detection FLASK_ENV Switch between dev/prod settings
Third-Party Tools python-dotenv Load variables from .env files
Validation Custom checks Ensure required variables are set


2.1 Direct Access with os.environ

Example: Loading Environment Variables

from flask import Flask
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///default.db')

@app.route('/')
def index():
    return f"Database: {app.config['SQLALCHEMY_DATABASE_URI']}"

if __name__ == '__main__':
    app.run()

Shell Command:

export SECRET_KEY='secure-key'
export DATABASE_URL='sqlite:///app.db'
python app.py

Output (visiting /):

Database: sqlite:///app.db

Explanation:

  • os.environ.get - Safely retrieves variables with defaults.
  • Ideal for sensitive settings like database URLs.

2.2 Using app.config.from_envvar

Example: Config File via Environment Variable

from flask import Flask

app = Flask(__name__)
app.config.from_envvar('FLASK_CONFIG', silent=True)

@app.route('/')
def index():
    return f"Secret: {app.config.get('SECRET_KEY', 'Not set')}"

if __name__ == '__main__':
    app.run()

Config File (config.py):

SECRET_KEY = 'env-loaded-secret'
DEBUG = True

Shell Command:

export FLASK_CONFIG='config.py'
python app.py

Output (visiting /):

Secret: env-loaded-secret

Explanation:

  • from_envvar - Loads a Python config file specified by an environment variable.
  • silent=True - Prevents errors if the variable is unset.

2.3 Environment Detection with FLASK_ENV

Example: Environment-Based Config

from flask import Flask
import os

app = Flask(__name__)
env = os.environ.get('FLASK_ENV', 'development')

if env == 'production':
    app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'prod-secret')
    app.config['DEBUG'] = False
    app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///prod.db')
else:
    app.config['SECRET_KEY'] = 'dev-secret'
    app.config['DEBUG'] = True
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///dev.db'

@app.route('/')
def index():
    return f"Environment: {env}, Debug: {app.config['DEBUG']}"

if __name__ == '__main__':
    app.run()

Shell Command:

export FLASK_ENV=production
export SECRET_KEY='secure-prod-key'
export DATABASE_URL='postgresql://user:password@host:port/db'
python app.py

Output (visiting /):

Environment: production, Debug: False

Explanation:

  • FLASK_ENV - Determines the environment (development/production).
  • Adjusts settings dynamically based on the environment.

2.4 Using python-dotenv for .env Files

Example: Loading .env File

from flask import Flask
from dotenv import load_dotenv
import os

load_dotenv()  # Load .env file

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///default.db')

@app.route('/')
def index():
    return f"Database: {app.config['SQLALCHEMY_DATABASE_URI']}"

if __name__ == '__main__':
    app.run()

.env File:

SECRET_KEY=my-dotenv-secret
DATABASE_URL=sqlite:///dotenv.db

Output (visiting /):

Database: sqlite:///dotenv.db

Explanation:

  • python-dotenv - Loads variables from a .env file.
  • Keep .env out of version control for security.

2.5 Validating Environment Variables

Example: Environment Variable Validation

from flask import Flask
import os

app = Flask(__name__)

required_vars = ['SECRET_KEY', 'DATABASE_URL']
for var in required_vars:
    if not os.environ.get(var):
        raise EnvironmentError(f"Missing required environment variable: {var}")

app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', '0') == '1'

@app.route('/')
def index():
    return "Configuration validated!"

if __name__ == '__main__':
    app.run()

Shell Command:

export SECRET_KEY='secure-key'
export DATABASE_URL='sqlite:///app.db'
python app.py

Output (missing variable):

EnvironmentError: Missing required environment variable: SECRET_KEY

Explanation:

  • Validates required variables to prevent runtime issues.
  • Raises an error if critical settings are missing.

2.6 Incorrect Environment Variable Usage

Example: Hardcoding Instead of Environment Variables

from flask import Flask

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hardcoded-secret'  # Incorrect: Hardcoded
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hardcoded.db'

@app.route('/')
def index():
    return "App running"

if __name__ == '__main__':
    app.run()

Output:

App running

Explanation:

  • Hardcoding sensitive data risks exposure in version control.
  • Solution: Use os.environ.get for environment variables.

03. Effective Usage

3.1 Recommended Practices

  • Use python-dotenv for local development with .env files.

Example: Comprehensive Environment Variable Setup

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
import os

load_dotenv()

app = Flask(__name__)

# Validate required variables
required_vars = ['SECRET_KEY', 'DATABASE_URL']
for var in required_vars:
    if not os.environ.get(var):
        raise EnvironmentError(f"Missing required environment variable: {var}")

# Load configurations
env = os.environ.get('FLASK_ENV', 'development')
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['DEBUG'] = env == 'development'

db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

@app.route('/')
def index():
    return f"Environment: {env}, Database: {app.config['SQLALCHEMY_DATABASE_URI']}"

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run()

.env File:

SECRET_KEY=my-secure-secret
DATABASE_URL=sqlite:///app.db
FLASK_ENV=development

Output (visiting /):

Environment: development, Database: sqlite:///app.db
  • Combines .env files, validation, and environment detection.
  • Ensures secure and flexible configuration.

3.2 Practices to Avoid

  • Avoid committing .env files to version control.

Example: Committing .env File

from flask import Flask
from dotenv import load_dotenv
import os

load_dotenv()

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')

@app.route('/')
def index():
    return "App running"

if __name__ == '__main__':
    app.run()

.env File (committed to git):

SECRET_KEY=exposed-secret  # Incorrect: In version control

Output:

App running
  • Committing .env exposes sensitive data.
  • Solution: Add .env to .gitignore.

04. Common Use Cases

4.1 Secure Web Application Configuration

Use environment variables to configure a web application securely.

Example: Web App with Environment Variables

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
import os

load_dotenv()

app = Flask(__name__)

required_vars = ['SECRET_KEY', 'DATABASE_URL']
for var in required_vars:
    if not os.environ.get(var):
        raise EnvironmentError(f"Missing required environment variable: {var}")

app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['DEBUG'] = os.environ.get('FLASK_ENV', 'development') == 'development'

db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

@app.route('/')
def index():
    return "Web app running"

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run()

.env File:

SECRET_KEY=web-app-secret
DATABASE_URL=sqlite:///web.db
FLASK_ENV=development

Explanation:

  • Secures sensitive data with environment variables.
  • Validates required settings for reliability.

4.2 Secure API Configuration

Configure a Flask API using environment variables for production.

Example: Secure API Config

from flask import Flask, jsonify
from dotenv import load_dotenv
import os

load_dotenv()

app = Flask(__name__)

required_vars = ['SECRET_KEY', 'API_KEY']
for var in required_vars:
    if not os.environ.get(var):
        raise EnvironmentError(f"Missing required environment variable: {var}")

app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
app.config['API_KEY'] = os.environ['API_KEY']
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'

@app.route('/api/data')
def data():
    return jsonify({'message': 'Secure API', 'api_key': app.config['API_KEY']})

if __name__ == '__main__':
    app.run(ssl_context='adhoc')

.env File:

SECRET_KEY=api-secret
API_KEY=secure-api-key

Explanation:

  • Secures API with environment variables and secure session settings.
  • Validates critical variables like API keys.

Conclusion

Managing environment variables in Flask, powered by Jinja2 Templating and Werkzeug WSGI, ensures secure and flexible configuration for applications. Key takeaways:

  • Use os.environ.get and python-dotenv for secure variable management.
  • Leverage FLASK_ENV for environment-specific settings.
  • Validate required variables to prevent errors.
  • Avoid hardcoding or committing sensitive data.

With environment variables, Flask applications can achieve robust, secure, and environment-agnostic configurations, streamlining development and deployment processes!

Comments