Skip to content

Commit e7abece

Browse files
committed
new post about Elixir configuratio
1 parent 625ad66 commit e7abece

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
title: "Understanding Application Configuration in Elixir"
3+
date: 2024-04-18 22:19:00 +1000
4+
tags: Elixir configuration distillery release
5+
header:
6+
image: /assets/images/2024-04-19/banff_canada.jpg
7+
image_description: "Banff National Park"
8+
teaser: /assets/images/2024-04-19/banff_canada.jpg
9+
overlay_image: /assets/images/2024-04-19/banff_canada.jpg
10+
overlay_filter: 0.2
11+
caption: >
12+
Photo by [Luca Bravo](https://unsplash.com/@lucabravo)
13+
on [Unsplash](https://unsplash.com/photos/banff-national-park-canada-oV4bR3YoR_s)
14+
excerpt: Application configuration can get interesting in Elixir. Let's dig ...
15+
---
16+
17+
Recently I started working on an Elixir project, which was a breath of fresh
18+
air, especially after two years mainly working with Node. However, it didn't
19+
take long before I stumbled upon something that puzzled me.
20+
21+
In the production configuration files of this project, we have environment
22+
variable names interpolated in strings as follows:
23+
24+
```elixir
25+
# config/prod.exs
26+
config :my_app, MyApp.Repo,
27+
hostname: "${DATABASE_HOSTNAME}",
28+
username: "${DATABASE_USERNAME}",
29+
...
30+
```
31+
32+
Clearly this approach wouldn't work for the `:dev` environment, where we would
33+
typically see something like this:
34+
35+
```elixir
36+
# config/dev.exs
37+
config :my_app, MyApp.Repo,
38+
hostname: System.get_env("DATABASE_HOSTNAME"),
39+
username: System.get_env("DATABASE_USERNAME"),
40+
...
41+
```
42+
43+
However, I also knew that the interpolation syntax did work for us in
44+
production. This made me curious: How exactly does this work? It was time for
45+
some investigation.
46+
47+
## The How
48+
49+
After consulting with our old friend Google and chatting with our new friend
50+
GPT-4, I learnt that this peculiar method of setting configuration values from
51+
environment variables is facilitated by Distillery, the tool we use for building
52+
releases.
53+
54+
For this functionality to be enabled, the environment variable `REPLACE_OS_VARS`
55+
must be set to `true`. And then Distillery would replace environment variables
56+
with their actual values upon encountering interpolation syntax, such as
57+
`"${DATABASE_USERNAME}"`, in the configuration files.
58+
59+
As a matter of fact, when it comes to handling application configuration,
60+
Distillery supports more than simple variable interpolation. It also supports
61+
Config Providers, which are custom modules capable of dynamically generating
62+
configuration at the start of your application. For further details, please
63+
refer to the relevant section in [Distillery documentation][distillery-doc].
64+
65+
## The Why
66+
67+
While it's enlightening to learn how it works, I kept wondering: why does
68+
Distillery offer support for both interpolation and Config Providers? Isn't
69+
`System.get_env` good enough for this purpose? As it turns out, although
70+
`System.get_env` is quite effective for the `:dev` environment, it can be
71+
inadequate for the production environment in many cases.
72+
73+
To grasp this, we need to take a step back and have a quick review of how Elixir
74+
the language works. Elixir is a compiled language. When you execute `mix
75+
compile`, the Elixir compiler transforms your project into Erlang bytecode. This
76+
bytecode then runs on the Erlang Virtual Machine, commonly known as
77+
Bogdan/Björn's Erlang Abstract Machine (BEAM).
78+
79+
The configuration files and code that are outside of function bodies within a
80+
module are evaluated at compile-time. Thus, if you use
81+
`System.get_env("DATABASE_HOSTNAME")` within a configuration file, it's
82+
evaluated during the compilation process. This means the value of the
83+
environment variable `DATABASE_HOSTNAME` is then captured and embedded in the
84+
application as a static value.
85+
86+
For production configurations, the goal is generally to dynamically capture
87+
runtime environment variables, rather than to embedding those from the build
88+
server as static values. Apparently using `System.get_env` wouldn't achieve this
89+
purpose.
90+
91+
That is precisely why Distillery supports both the interpolation syntax and
92+
Config Providers, enabling dynamic configuration value setting from the runtime
93+
environment of the application.
94+
95+
It's noteworthy that with version 1.9, Elixir introduced built-in support for
96+
[releases][mix-release] as well, which includes a novel approach to runtime
97+
configuration. This was achieved through the introduction of a new configuration
98+
file `config/releases.exs`, which is executed every time a release starts. Then
99+
in Elixir 1.11, another configuration file `config/runtime.exs` was introduced,
100+
which is essentially a superior version of `config/releases.exs`. For more
101+
details on these developments, please consult to the [relevant
102+
documentation][elixir-1-11-change].
103+
104+
## Summary
105+
106+
In this post, I shared my initial confusion about the interpolation syntax
107+
`"${DATABASE_HOSTNAME}"` in our production configuration files, followed by an
108+
exploration into understanding how it works and why is it necessary for runtime
109+
configuration. As always, my aim is to provide a reference for my further self
110+
and perhaps assist others along their journey.
111+
112+
[distillery-doc]: https://hexdocs.pm/distillery/config/runtime.html
113+
[elixir-1-11-change]: https://hexdocs.pm/elixir/1.11.0/changelog.html#config-runtime-exs-and-mix-app-config
114+
[mix-release]: https://hexdocs.pm/mix/Mix.Tasks.Release.html
117 KB
Loading

0 commit comments

Comments
 (0)