kuon

kuon OP

I am opening this thread to discuss the global problems and solutions around the current mix config implementation.

As seen in multiple github issues and in multiple places around this forum, the current configuration behaviour via mix config.exs has it’s issues.

The most common ones being:

  • Confusion as config being compile time, including reading the environment
  • {system, "MYENV"} being somewhat present but considered bad practice by @josevalim
  • Difficulties around secret handling
  • No standard way of defining config requirements/schema for libraries

I’d like this thread to be used to acknowledge that this is an issue we need to solve, find at what level and how it should be (in mix, with a DSL…).

First 10 of 23 Posts Switch mode

cdegroot

cdegroot

I think the question we want answered first is whether the general advise should be to run your stuff in production as mix run (where Mix configurations work just fine) or as Erlang distributions (where some ugliness crops up that probably could be documented better). I invested a ton of time to have clean multi-environment distributions using some scripting on top of Distillery, but although the thing works, I’m less than happy with it and maybe the answer is just “don’t bother, use mix run”..

santif

santif

Hello, this is how we organize configuration of our Elixir/Phoenix app:

  1. Software/deps:

    • ansible: provisioning / app install and initial config.
    • distillery: releases
    • edeliver: deploy
  2. All default app config (convention over configuration approach) is in mix.exs, in env tuple of OTP application:

  def application do
    [mod: {AppName, []},
    env: env(),
    applications: [.....]]
  end

  defp env() do
    [
      some_config: 1,
      another_conf: "value"
    ]
  end
  1. Config overrides are only in config/dev.exs and config/test.exs. File config/prod.exs only contains configuration of external apps.

  2. Ansible (jinja2) templates for /etc/appname/sys.config and
    /etc/appname/vm.args on production servers. Both files are owned by root:appname and mode=640. Then symlink these files from /opt/appname/sys.config and /opt/appname/vm.args during application installation process. Note: since all properties have a default value in the “env” tuple of OTP application, you only need to include in sys.config properties that change their value in production.

  3. Ansible group_vars and/or host_vars with specific configuration values (encrypted with ansible-vault and shared in VCS). These values includes database password, API keys, etc.

  4. Never use module attributes with external configuration, like:

@value Application.get_env(:appname, :value, 1)

Instead, use this:

@default_value 1
...
def ... do
  value = Application.get_env(:appname, :value, @default_value)
end
  1. This approach does not require config/prod.secret.exs.

Pending: consider to use conform.

I would like to hear about experiences and approaches about configuration in other real world apps.

cdegroot

cdegroot

Can we put that in boldface and blink? In fact, it should trigger a compiler warning or linter error.

kuon

kuon OP

I think we need some kind of standard solution that addresses the following:

  • Multiple environments
  • Runtime configuration
  • Discoverable (documentation and schema)
  • Supporting different delivery scenario (mix run, distillery…)

My opinion is that it should be handled at the lowest level possible to have a common configuration mechanism for all elixir apps.

josevalim

josevalim

Creator of Elixir

This is a good discussion! Can you please elaborate those two points:

I want to make sure I understand all concerns before giving a reply. :slight_smile:

kuon

kuon OP

Secret handling

It has been proven too many times that it is really easy to leak secrets for an app. So people came up with tons of different ways of handling it. Like adding a config.secrets.exs that should not be committed to git or env var (things like direnv help). I think we should integrate this in our thinking of a configuration system. We need to be able to inject external secrets (like with env var) and we need to avoid running bad “default/empty” values, like having “change me” as session secret. This would be covered by the next point.

Configuration requirements and schema

The configurations requirements would be the minimal configuration that should be done for an app or library.
For example, if I add a redis adapter library to my app and do no configuration, I should get a message like:
Configuration error: the key redis.hostname has no value.

This should come for free after defining a schema, like:

schema(:redis) do
    key :hostname, :string, required: true
end

The idea of the schema is to do correct type conversion (if I put a port number in an env var, I want an int in my app, not a string), handle missing/empty values.

The general idea is to avoid missing and even dangerous configuration and help debug configuration errors, I know it’s erlang “policy” to crash is something is wrong, but sometimes it can be really time consuming to pinpoint a missing configuration in production because there is some typo in an env var or that you forgot to commit some change. I also encountered super hard to pinpoint errors because some config was “FOO=false” and it was not converted to a boolean, evaluating to true… you get the idea.

It would also avoid replicating configuration validation code and type conversion across all possible libraries. The idea is that a library (or an app) could “trust” the configuration it is given.

josevalim

josevalim

Creator of Elixir

I believe a lot of the confusion comes from being unclear on what should be configured via config/config.exs. Some of this is due to the ecosystem being new and clearer guidelines will come as we go.

For example, no configuration library that will solve the compile-time issues we have today. The solution is for libraries to rely less on compile-time configuration and document when it happens. We have also tried to make {:system, env} work but, at this point, it is clear that runtime configuration should be moved to runtime. It doesn’t work in Elixir nor did it work on Erlang. Phoenix v1.3 and Ecto v2.1 are pushing to this new direction.

Hopefully drawing a line on what works with Mix config will allow others to work on a unified approach for configuration that could support multiple sources (system env, database, json files, etc). Looking at what other communities do to tackle this can be helpful. However I would also be careful with putting all responsibilities on the config system. For example, a schema for configuration could be useful, but libraries should also be validating whatever they get from external sources.

sasajuric

sasajuric

Author of Elixir In Action

I’ve said it elsewhere, but I want to repeat again, that I believe that libs should in most cases not be prescriptive about configuration. This tweet mostly mirrors my way of thinking:

https://twitter.com/timperrett/status/841163004968239104

I wouldn’t be as harsh and say that there are no such scenarios, but I do feel that in most typical cases libs should just take their options at runtime, either through function parameters, or through module callbacks. I believe that this will simplify many deployment/config challenges

Let’s see how runtime configuration would address your original concerns:

Confusion as config being compile time, including reading the environment

If a library takes options at runtime, then you need to pass the value at runtime, so there’s no confusion.

{system, “MYENV”} being somewhat present but considered bad practice by @josevalim

If a library takes options at runtime, then we don’t need {:system, ...} or any similar improvisation.

Difficulties around secret handling

If a library takes options at runtime, it’s up to developer to read the secret at runtime from an arbitrary safe place.

No standard way of defining config requirements/schema for libraries

If a library takes options at runtime, then requirements can be listed as mandatory parameters, while schema can be specified with typespecs.

Therefore I believe that the best solution is to educate lib authors to seriously consider whether their libraries really need to be configured through config.exs.

kuon

kuon OP

I’m fine with libs not being configured via config.exs, but then, how do you configure a library that is an application being under the supervisor? Such app would get start_link automatically and it might require configuration at this point.

sasajuric

sasajuric

Author of Elixir In Action

I believe in most cases the same ideas hold.

Preferably, an OTP app would start only the minimum part of its subtree, usually some “singleton” (aka locally registered) processes, while everything else should be started on demand, and receive options at the latest possible moment.

A good examples of this are phoenix and ecto. Both require us to provide options through app env (which I dislike). However, they will only read those options when we start the endpoint or the repo (which I like). Thus, for the most part both apps could in fact accept options at runtime, although they are OTP apps with a supervision tree.

There will likely always be some cases where configuration is indeed required at the startup. The best example I can think of is logger. However, I’m pretty certain that in the vast majority of cases libs can defer taking their options to the latest possible moment.

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 91561 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
mudasobwa
While I am working on the Language Agnostic Code Audit SaaS, which uses MetaAST (spoiler: I am expecting it to be in a good shape for ann...
New

We're in Beta

About us Mission Statement