Skip to main content

Flask Environments

When developing Flask applications, you'll often need different configurations for various stages of your application lifecycle. In this guide, we'll explore how to set up and manage different environments in Flask applications, including development, testing, and production.

Introduction to Flask Environments

Flask environments refer to different configurations of your application based on where it's running. Each environment has its own specific requirements and considerations:

  • Development: Prioritizes developer convenience with debugging tools and detailed error messages
  • Testing: Focuses on isolating components for automated testing
  • Production: Emphasizes security, performance, and stability

Understanding how to properly configure these environments is essential for building robust Flask applications.

Setting Environment Variables

Flask uses the FLASK_ENV environment variable to determine which environment it's running in. By default, Flask assumes it's running in production.

Setting Environment in Terminal

bash
# For Windows Command Prompt
set FLASK_ENV=development
set FLASK_DEBUG=1

# For Windows PowerShell
$env:FLASK_ENV = "development"
$env:FLASK_DEBUG = 1

# For macOS/Linux
export FLASK_ENV=development
export FLASK_DEBUG=1
note

As of Flask 2.0, FLASK_ENV is deprecated in favor of FLASK_DEBUG for enabling debug mode. However, many developers still use FLASK_ENV for environment identification.

Configuration Management in Flask

Flask allows you to manage configurations in several ways. Let's explore some common approaches:

1. Using Python Classes for Configuration

Create a config.py file to store your different environment configurations:

python
class Config:
"""Base configuration."""
SECRET_KEY = 'default-secret-key'
SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'

class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db'

class ProductionConfig(Config):
"""Production configuration."""
DEBUG = False
SQLALCHEMY_DATABASE_URI = 'postgresql://user:password@localhost/prod_db'

config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}

Then, in your main app file, load the configuration based on the environment:

python
from flask import Flask
from config import config
import os

def create_app(config_name=None):
"""Create and configure the Flask application."""
app = Flask(__name__)

if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'default')

app.config.from_object(config[config_name])

# Initialize extensions and register blueprints here

return app

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

2. Using Environment-Specific .env Files

Another popular approach is using .env files with the python-dotenv package:

First, install the package:

bash
pip install python-dotenv

Create environment-specific .env files:

.env.development:

SECRET_KEY=dev-secret-key
DEBUG=True
DATABASE_URL=sqlite:///dev.db

.env.production:

SECRET_KEY=prod-secret-key
DEBUG=False
DATABASE_URL=postgresql://user:password@localhost/prod_db

Load the appropriate .env file in your application:

python
from flask import Flask
from dotenv import load_dotenv
import os

def create_app():
app = Flask(__name__)

# Determine which .env file to load
env = os.environ.get('FLASK_ENV', 'development')
load_dotenv(f'.env.{env}')

# Configure the app
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['DEBUG'] = os.environ.get('DEBUG') == 'True'
app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL')

return app

Creating an Environment-Aware Flask Application

Let's build a simple Flask application that demonstrates environment awareness:

python
# app.py
from flask import Flask, render_template
import os

app = Flask(__name__)

# Load configuration based on environment
env = os.environ.get('FLASK_ENV', 'development')
if env == 'development':
app.config.from_object('config.DevelopmentConfig')
elif env == 'testing':
app.config.from_object('config.TestingConfig')
else:
app.config.from_object('config.ProductionConfig')

@app.route('/')
def index():
# Show environment information
return render_template('index.html',
environment=env,
debug=app.debug,
config_info=app.config)

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

Create a simple template to display the environment information:

html
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Flask Environment Example</title>
</head>
<body>
<h1>Flask Environment Demo</h1>
<p><strong>Current Environment:</strong> {{ environment }}</p>
<p><strong>Debug Mode:</strong> {{ debug }}</p>
<h2>Configuration Details</h2>
<ul>
<li><strong>Secret Key:</strong> [HIDDEN]</li>
<li><strong>Database URL:</strong> {{ config_info.get('SQLALCHEMY_DATABASE_URI', 'Not set') }}</li>
</ul>
</body>
</html>

Environment-Specific Features

Different environments often require specific features. Here are some common environment-specific considerations:

Development Environment

python
if app.config['DEBUG']:
# Enable Flask-DebugToolbar
from flask_debugtoolbar import DebugToolbarExtension
toolbar = DebugToolbarExtension(app)

@app.route('/debug-info')
def debug_info():
"""Route only available in development."""
return {
'config': {k: str(v) for k, v in app.config.items()},
'environment': os.environ.get('FLASK_ENV', 'development')
}

Testing Environment

python
if app.config['TESTING']:
# Disable CSRF protection for testing
app.config['WTF_CSRF_ENABLED'] = False

# Use in-memory database for testing
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'

Production Environment

python
if not app.config['DEBUG'] and not app.config['TESTING']:
# Configure production-specific logging
import logging
from logging.handlers import RotatingFileHandler

file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Application startup')

# Enable Sentry error monitoring (if installed)
try:
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

sentry_sdk.init(
dsn="YOUR_SENTRY_DSN",
integrations=[FlaskIntegration()]
)
except ImportError:
# Sentry not installed, skip integration
pass

Best Practices for Flask Environments

  1. Never store sensitive information in your code: Use environment variables or .env files (that are excluded from version control) for secrets.

  2. Set reasonable defaults: Your application should work even if environment-specific variables are not set.

  3. Separate instance configurations: Use Flask's instance_path for environment-specific configurations that should not be in version control.

  4. Test all environments: Make sure your application works correctly in all environments before deployment.

  5. Use proper logging: Configure different logging levels and destinations based on the environment.

  6. Utilize environment-specific dependencies: For example, have development-only packages in requirements-dev.txt.

Real-World Example: Multi-Environment Flask Application

Here's a more complete example incorporating the concepts we've discussed:

Project Structure

my_flask_app/
├── .env.development
├── .env.production
├── .gitignore
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ └── templates/
├── config.py
├── instance/
│ └── config.py
├── requirements.txt
└── run.py

Configuration File

python
# config.py
import os
from dotenv import load_dotenv

# Load the appropriate .env file
flask_env = os.environ.get('FLASK_ENV', 'development')
dotenv_path = f'.env.{flask_env}'
load_dotenv(dotenv_path)

class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get('SECRET_KEY', 'default-key')
SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///dev.db')

class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
WTF_CSRF_ENABLED = False

class ProductionConfig(Config):
"""Production configuration."""
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

# Production security settings
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
REMEMBER_COOKIE_SECURE = True
REMEMBER_COOKIE_HTTPONLY = True

config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}

Application Factory

python
# app/__init__.py
from flask import Flask
from config import config

def create_app(config_name=None):
"""Create and configure the Flask application."""
app = Flask(__name__, instance_relative_config=True)

# Load default configuration
if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'default')

app.config.from_object(config[config_name])

# Load instance config (if it exists)
app.config.from_pyfile('config.py', silent=True)

# Initialize extensions
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)

# Register blueprints
from app.routes import main_bp
app.register_blueprint(main_bp)

# Environment-specific setup
if app.debug:
# Development-specific setup
try:
from flask_debugtoolbar import DebugToolbarExtension
toolbar = DebugToolbarExtension(app)
except ImportError:
pass

if not app.debug and not app.testing:
# Production-specific setup
import logging
from logging.handlers import RotatingFileHandler
import os

# Ensure logs directory exists
if not os.path.exists('logs'):
os.mkdir('logs')

file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Application startup')

return app

Run File

python
# run.py
import os
from app import create_app

app = create_app(os.environ.get('FLASK_ENV'))

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

Summary

Managing environments in Flask applications is essential for developing and deploying robust web applications. We've covered:

  • How to set and use environment variables in Flask
  • Different approaches to configuration management
  • Environment-specific features and considerations
  • Best practices for handling different environments
  • A real-world example of a multi-environment Flask application

By properly configuring your Flask environments, you can ensure your application is secure, maintainable, and behaves correctly in different contexts, from development to production.

Additional Resources

Exercises

  1. Create a simple Flask application with different configurations for development and production environments.
  2. Modify the example application to read database credentials from environment variables.
  3. Implement environment-specific logging for your Flask application.
  4. Create a Flask application that uses different templates based on the current environment.
  5. Build a configuration system that can be overridden with both environment variables and command-line arguments.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)