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
# 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
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:
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:
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:
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:
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:
# 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:
<!-- 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
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
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
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
-
Never store sensitive information in your code: Use environment variables or
.env
files (that are excluded from version control) for secrets. -
Set reasonable defaults: Your application should work even if environment-specific variables are not set.
-
Separate instance configurations: Use Flask's
instance_path
for environment-specific configurations that should not be in version control. -
Test all environments: Make sure your application works correctly in all environments before deployment.
-
Use proper logging: Configure different logging levels and destinations based on the environment.
-
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
# 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
# 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
# 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
- Flask Configuration Documentation
- Environment and Debug Features in Flask
- python-dotenv Documentation
- The Twelve-Factor App Methodology (particularly the "Config" section)
Exercises
- Create a simple Flask application with different configurations for development and production environments.
- Modify the example application to read database credentials from environment variables.
- Implement environment-specific logging for your Flask application.
- Create a Flask application that uses different templates based on the current environment.
- 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! :)