Guide for Elixir application configuration focusing on runtime vs compile-time config, config.exs, runtime.exs, Application.compile_env, and Application.get_env best practices
Guides on Elixir runtime vs compile-time configuration, using config.exs and runtime.exs. Triggers when setting up application config, choosing between Application.compile_env and get_env, or debugging config issues.
/plugin marketplace add vinnie357/claude-skills/plugin install all-skills@vinnie357This skill inherits all available tools. When active, it can use any tool Claude has access to.
Guide for proper application configuration in Elixir, with emphasis on understanding and correctly using runtime vs compile-time configuration.
Use this skill when:
config.exs and runtime.exsApplication.compile_env and Application.get_envuse Mix.Config to import ConfigRuntime configuration is the preferred approach. Only use compile-time configuration when values must affect compilation itself.
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:
import Config (not use Mix.Config)config_env() and config_target()import_config/1Evaluated 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:
import_config/1System.get_env and System.fetch_env!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"
Use in function bodies to read configuration at runtime:
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:
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:
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:
Use only when configuration must affect compilation:
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:
Warning: Mix tracks compile-time config and raises errors if values diverge between compile and runtime.
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:
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
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
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
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
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
# 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
# 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
# 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
# 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
| 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 |
| 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 |
use Mix.Config to import ConfigOld (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:
use Mix.Config with import ConfigMix.env() with config_env()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")
Application.get_env/3 in function bodiesconfig.exsconfig_env() in config files, store resultfetch_env!/2 in application start for required valuesget_env/3 with defaults for optional configconfig_env() outside config files# In IEx
Application.get_all_env(:my_app)
# Check specific key
Application.fetch_env(:my_app, :some_key)
# See all applications
Application.loaded_applications()
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
"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.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.