elixir-config
About
This skill provides best practices for Elixir application configuration, focusing on when to use runtime vs compile-time settings. It covers configuration files (config.exs/runtime.exs) and environment functions (Application.get_env/compile_env) for proper setup. Use it when configuring Elixir applications, debugging config issues, or working with releases.
Documentation
Elixir Configuration
Guide for proper application configuration in Elixir, with emphasis on understanding and correctly using runtime vs compile-time configuration.
When to Activate
Use this skill when:
- Setting up or modifying application configuration
- Choosing between
config.exsandruntime.exs - Deciding between
Application.compile_envandApplication.get_env - Debugging configuration-related issues
- Working with releases or deployment configuration
- Migrating from
use Mix.Configtoimport Config - Writing libraries that need configuration
Critical Principle
Runtime configuration is the preferred approach. Only use compile-time configuration when values must affect compilation itself.
Configuration Files
config/config.exs (Compile-Time)
Evaluated during project compilation, before your application starts.
import Config
# Basic configuration
config :my_app, MyApp.Repo,
database: "my_app_dev",
username: "postgres",
password: "postgres",
hostname: "localhost"
# Environment-specific config
config :my_app,
environment: config_env()
# Import environment-specific config files
import_config "#{config_env()}.exs"
Key characteristics:
- Runs at compile time
- Uses
import Config(notuse Mix.Config) - Can use
config_env()andconfig_target() - Can import other config files with
import_config/1 - Deep-merges keyword lists
- Library config.exs is NOT evaluated when used as a dependency
config/runtime.exs (Runtime)
Evaluated right before applications start in both Mix and releases.
import Config
# Read from environment variables
config :my_app, MyApp.Repo,
database: System.get_env("DATABASE_NAME") || "my_app_dev",
username: System.get_env("DATABASE_USER") || "postgres",
password: System.get_env("DATABASE_PASSWORD") || "postgres",
hostname: System.get_env("DATABASE_HOST") || "localhost",
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
# Conditional runtime configuration
if config_env() == :prod do
config :my_app, MyAppWeb.Endpoint,
secret_key_base: System.fetch_env!("SECRET_KEY_BASE"),
http: [port: String.to_integer(System.fetch_env!("PORT"))]
end
Key characteristics:
- Runs at application startup (both dev and prod)
- Executes in both Mix projects and releases
- Perfect for environment variables and runtime values
- Does NOT support
import_config/1 - Can use
System.get_envandSystem.fetch_env!
config/dev.exs, config/test.exs, config/prod.exs
Environment-specific compile-time configuration, typically imported from config.exs:
# config/config.exs
import_config "#{config_env()}.exs"
# config/dev.exs
import Config
config :my_app, MyApp.Repo,
show_sensitive_data_on_connection_error: true,
pool_size: 10
# config/test.exs
import Config
config :my_app, MyApp.Repo,
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: 10
# config/prod.exs
import Config
# Production-specific compile-time config only
config :my_app, MyAppWeb.Endpoint,
cache_static_manifest: "priv/static/cache_manifest.json"
Accessing Configuration
Runtime Access (Preferred)
Use in function bodies to read configuration at runtime:
Application.get_env/3
defmodule MyApp.Service do
def start_link do
# Get with default value
timeout = Application.get_env(:my_app, :timeout, 5000)
GenServer.start_link(__MODULE__, timeout, name: __MODULE__)
end
end
When to use:
- Reading config in function bodies (most common)
- When a sensible default exists
- When config might change between environments
Application.fetch_env!/2
defmodule MyApp.Mailer do
def deliver(email) do
# Raise if not configured (for required config)
api_key = Application.fetch_env!(:my_app, :mailgun_api_key)
send_email(email, api_key)
end
end
When to use:
- Required configuration that must exist
- When you want explicit errors for missing config
- When no sensible default exists
Application.fetch_env/2
defmodule MyApp.Cache do
def get(key) do
case Application.fetch_env(:my_app, :cache_adapter) do
{:ok, adapter} -> adapter.get(key)
:error -> nil # No caching configured
end
end
end
When to use:
- Optional configuration
- When you need pattern matching on result
- When absence of config is a valid state
Compile-Time Access (Use Sparingly)
Use only when configuration must affect compilation:
Application.compile_env/3
defmodule MyApp.JSONEncoder do
# Only use compile_env when the value affects compilation
@json_library Application.compile_env(:my_app, :json_library, Jason)
def encode(data) do
# The specific library is compiled into the module
@json_library.encode(data)
end
end
When to use:
- Configuration affects which code gets compiled
- Performance-critical paths where indirection is costly
- Compile-time optimizations or code generation
Warning: Mix tracks compile-time config and raises errors if values diverge between compile and runtime.
Application.compile_env!/2
defmodule MyApp.Adapter do
# Raises at compile time if not configured
@adapter Application.compile_env!(:my_app, :storage_adapter)
def store(data) do
@adapter.put(data)
end
end
When to use:
- Required compile-time configuration
- Adapters or behaviors selected at compile time
Common Patterns
Pattern 1: Environment Variables in Runtime
Correct approach:
# config/runtime.exs
import Config
config :my_app,
api_url: System.get_env("API_URL") || "http://localhost:4000",
api_key: System.fetch_env!("API_KEY") # Required in production
Access in code:
defmodule MyApp.Client do
def call(endpoint) do
api_url = Application.fetch_env!(:my_app, :api_url)
api_key = Application.fetch_env!(:my_app, :api_key)
HTTPoison.get("#{api_url}/#{endpoint}", [{"Authorization", api_key}])
end
end
Pattern 2: Development vs Production Config
config/config.exs:
import Config
# Shared configuration for all environments
config :my_app, :shared_setting, "value"
# Import environment-specific config
import_config "#{config_env()}.exs"
config/dev.exs:
import Config
config :my_app, MyApp.Repo,
database: "my_app_dev",
show_sensitive_data_on_connection_error: true
config/runtime.exs:
import Config
# Runtime config for all environments
if config_env() == :prod do
# Production-specific runtime config
database_url = System.fetch_env!("DATABASE_URL")
config :my_app, MyApp.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
end
Pattern 3: Storing config_env() for Runtime Access
Problem: Can't call config_env() at runtime.
Solution: Store it in config:
# config/config.exs
import Config
config :my_app, :environment, config_env()
# Then in your code:
defmodule MyApp do
def environment do
Application.fetch_env!(:my_app, :environment)
end
def development? do
environment() == :dev
end
end
Pattern 4: Optional Features Based on Config
defmodule MyApp.Telemetry do
def setup do
case Application.fetch_env(:my_app, :telemetry_backend) do
{:ok, :datadog} -> setup_datadog()
{:ok, :prometheus} -> setup_prometheus()
:error -> :ok # Telemetry disabled
end
end
end
Pattern 5: Child Spec with Runtime Config
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
MyApp.Repo,
{MyApp.Worker, Application.fetch_env!(:my_app, :worker_opts)},
MyAppWeb.Endpoint
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Anti-Patterns to Avoid
❌ Using compile_env for Runtime Values
# DON'T: Using compile_env for environment variables
defmodule MyApp.Service do
@api_key Application.compile_env(:my_app, :api_key)
def call do
# This won't work correctly in releases!
HTTPoison.get(url, [{"Authorization", @api_key}])
end
end
Why it's wrong: Environment variables aren't available at compile time in releases.
Correct approach:
defmodule MyApp.Service do
def call do
# Read at runtime
api_key = Application.fetch_env!(:my_app, :api_key)
HTTPoison.get(url, [{"Authorization", api_key}])
end
end
❌ Reading Other Application's Config
# DON'T: Directly access other app's configuration
defmodule MyApp do
def logger_level do
Application.get_env(:logger, :level) # Fragile coupling
end
end
Why it's wrong: Creates tight coupling and breaks encapsulation.
Correct approach:
# Configure it in your own app
# config/config.exs
config :my_app, :log_level, :info
# Then read your own config
defmodule MyApp do
def log_level do
Application.get_env(:my_app, :log_level, :info)
end
end
❌ Using Application Config in Libraries
# DON'T: In a library
defmodule MyLibrary do
def process(data) do
# Library reading its own application environment
timeout = Application.get_env(:my_library, :timeout, 5000)
do_work(data, timeout)
end
end
Why it's wrong: Library config.exs is not evaluated when used as a dependency.
Correct approach:
# DO: Accept options as arguments
defmodule MyLibrary do
def process(data, opts \\ []) do
timeout = Keyword.get(opts, :timeout, 5000)
do_work(data, timeout)
end
end
# Users configure in their application
defmodule MyApp.Worker do
def run do
opts = Application.get_env(:my_app, :my_library_opts, [])
MyLibrary.process(data, opts)
end
end
❌ Using Mix Module in Application Code
# DON'T: Use Mix.env() in application code
defmodule MyApp do
def environment do
Mix.env() # Won't work in releases!
end
end
Why it's wrong: Mix is not available in production releases.
Correct approach:
# Store it in config
# config/config.exs
config :my_app, :environment, config_env()
# Access from application environment
defmodule MyApp do
def environment do
Application.fetch_env!(:my_app, :environment)
end
end
Config Functions Reference
In Configuration Files
| Function | Description | Where to Use |
|---|---|---|
config/2 | Configure app with keyword list | All config files |
config/3 | Configure app key with value | All config files |
config_env/0 | Get current environment (:dev, :test, :prod) | All config files |
config_target/0 | Get build target | All config files |
import_config/1 | Import other config files | Not in runtime.exs |
In Application Code
| Function | Return Type | Use Case |
|---|---|---|
Application.get_env/3 | value | default | Runtime with default |
Application.fetch_env/2 | {:ok, value} | :error | Runtime with pattern matching |
Application.fetch_env!/2 | value (raises if missing) | Required runtime config |
Application.compile_env/3 | value | Compile-time with default |
Application.compile_env!/2 | value (raises if missing) | Required compile-time config |
Migration Guide
From use Mix.Config to import Config
Old (deprecated):
use Mix.Config
config :my_app, :key, "value"
if Mix.env() == :prod do
config :my_app, :production, true
end
import_config "#{Mix.env()}.exs"
New:
import Config
config :my_app, :key, "value"
if config_env() == :prod do
config :my_app, :production, true
end
import_config "#{config_env()}.exs"
Changes:
- Replace
use Mix.Configwithimport Config - Replace
Mix.env()withconfig_env() - Remove wildcard imports (not supported)
Moving Runtime Config to runtime.exs
Before (all in config.exs):
# config/config.exs
import Config
config :my_app,
api_key: System.get_env("API_KEY"), # Wrong place!
static_value: "something"
After (split correctly):
# config/config.exs
import Config
config :my_app,
static_value: "something"
# config/runtime.exs
import Config
config :my_app,
api_key: System.get_env("API_KEY") || raise("API_KEY not set")
Best Practices Summary
- Default to Runtime Configuration: Use
Application.get_env/3in function bodies - Use runtime.exs for Environment Variables: Never read env vars in
config.exs - Use compile_env Only When Necessary: Only when config affects compilation
- Libraries Should Not Use Application Config: Accept options as function arguments
- Never Use Mix in Application Code: Use
config_env()in config files, store result - Validate Required Config Early: Use
fetch_env!/2in application start for required values - Provide Sensible Defaults: Use
get_env/3with defaults for optional config - Document Configuration: Add comments explaining what each config key does
- Use runtime.exs for Releases: Essential for Elixir releases and deployments
- Store config_env() for Runtime Use: Can't call
config_env()outside config files
Debugging Configuration
Check Current Configuration
# In IEx
Application.get_all_env(:my_app)
# Check specific key
Application.fetch_env(:my_app, :some_key)
# See all applications
Application.loaded_applications()
Common Issues
Problem: Config not available in tests
# config/test.exs
import Config
config :my_app, :test_value, "configured"
Problem: Different values in dev vs release
Check that runtime.exs is being used and environment variables are set correctly.
Problem: Compile-time config not updating
# Clean and recompile
mix clean
mix compile
Resources
- Config Module Docs: https://hexdocs.pm/elixir/Config.html
- Application Module Docs: https://hexdocs.pm/elixir/Application.html
- Runtime Configuration Guide: https://hexdocs.pm/mix/Mix.Tasks.Release.html#module-runtime-configuration
Key Insights
"Reading the application environment at runtime is the preferred approach."
"If you are writing a library to be used by other developers, it is generally recommended to avoid the application environment, as the application environment is effectively a global storage."
"config/config of a library is not evaluated when the library is used as a dependency, as configuration is always meant to configure the current project."
Configuration is a cross-cutting concern. Default to runtime configuration with Application.get_env/3, and only reach for compile-time configuration when you have a specific need for it that justifies the trade-offs.
Quick Install
/plugin add https://github.com/vinnie357/claude-skills/tree/main/configCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
Algorithmic Art Generation
MetaThis skill helps developers create algorithmic art using p5.js, focusing on generative art, computational aesthetics, and interactive visualizations. It automatically activates for topics like "generative art" or "p5.js visualization" and guides you through creating unique algorithms with features like seeded randomness, flow fields, and particle systems. Use it when you need to build reproducible, code-driven artistic patterns.
webapp-testing
TestingThis Claude Skill provides a Playwright-based toolkit for testing local web applications through Python scripts. It enables frontend verification, UI debugging, screenshot capture, and log viewing while managing server lifecycles. Use it for browser automation tasks but run scripts directly rather than reading their source code to avoid context pollution.
requesting-code-review
DesignThis skill dispatches a code-reviewer subagent to analyze code changes against requirements before proceeding. It should be used after completing tasks, implementing major features, or before merging to main. The review helps catch issues early by comparing the current implementation with the original plan.
