elixir-config

Guide for proper application configuration in Elixir, with emphasis on understanding and correctly using runtime vs compile-time configuration.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "elixir-config" with this command: npx skills add vinnie357/claude-skills/vinnie357-claude-skills-elixir-config

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.exs and runtime.exs

  • Deciding between Application.compile_env and Application.get_env

  • Debugging configuration-related issues

  • Working with releases or deployment configuration

  • Migrating from use Mix.Config to import 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 (not use Mix.Config )

  • Can use config_env() and config_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_env and System.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.Config with import Config

  • Replace Mix.env() with config_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/3 in 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!/2 in application start for required values

  • Provide Sensible Defaults: Use get_env/3 with 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

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.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

nushell

No summary provided by upstream source.

Repository SourceNeeds Review
General

material-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

anti-fabrication

No summary provided by upstream source.

Repository SourceNeeds Review
General

act

No summary provided by upstream source.

Repository SourceNeeds Review