Flask configuration can be a surprisingly tricky beast to get right, especially when you need to manage vastly different settings for development and production environments.

Let’s see this in action. Imagine a simple Flask app:

from flask import Flask
import os

app = Flask(__name__)

# Load configuration from environment variables or a config file
config_name = os.environ.get('APP_CONFIG', 'development')
app.config.from_object(f'config.{config_name.capitalize()}')

@app.route('/')
def hello():
    return f"Hello! The database URI is: {app.config.get('SQLALCHEMY_DATABASE_URI')}"

if __name__ == '__main__':
    app.run(debug=app.config.get('DEBUG'))

Now, let’s define our configuration files. Create a config.py file in the same directory:

# config.py

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY', 'a-very-secret-key-for-dev')
    DEBUG = False
    TESTING = False
    SQLALCHEMY_DATABASE_URI = 'sqlite:///default.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

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

class Production(Config):
    SECRET_KEY = os.environ.get('SECRET_KEY') # Must be set in production
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') # Must be set in production

To run this in development:

# In your terminal, in the same directory as app.py and config.py
export APP_CONFIG='development'
python app.py

You’ll see: Hello! The database URI is: sqlite:///dev.db and the app will run with debug=True.

To run this in production (hypothetically, as we’re not actually deploying):

# In your terminal, in the same directory as app.py and config.py
export APP_CONFIG='production'
export SECRET_KEY='your_super_secret_production_key'
export DATABASE_URL='postgresql://user:password@host:port/dbname' # Example
python app.py

You’ll see: Hello! The database URI is: postgresql://user:password@host:port/dbname and the app will run with debug=False.

This system solves the problem of having a single codebase that needs to behave differently based on its deployment environment. It leverages Python’s object-oriented features and Flask’s built-in configuration loading to keep sensitive information out of your code and to tailor settings like database connections, secret keys, and debugging modes. The core idea is to define a base configuration and then inherit from it for specific environments, overriding only what’s necessary.

The app.config.from_object() method is key here. It takes a string that represents a Python dotted path to an object (like a class or module). Flask then inspects that object and loads its attributes into the application’s configuration dictionary. This allows for a clean separation of concerns, where your config.py file becomes the single source of truth for all environment-specific settings.

The os.environ.get() calls are crucial for production. They allow you to inject configuration values from the operating system’s environment variables. This is a standard practice for security and flexibility, as you can change these values without redeploying your application. For example, your DATABASE_URL or SECRET_KEY can be set directly in your server’s environment or managed by a deployment platform like Heroku or AWS.

What most people don’t realize is how much power from_object grants you. You’re not limited to just classes. You could have a config.py with individual configuration variables directly at the module level, and from_object('config') would load them. Or, if you had a more complex setup, you could even have from_object('my_package.configs.production_settings') pointing to a specific module deep within your project. The flexibility is immense.

The next step is often integrating this configuration with external configuration files like .env files using libraries like python-dotenv for even easier local development setup.

Want structured learning?

Take the full Flask course →