Elixir releases on read-only file system.. Use confex instead of releases.exs?

There’s a bit of a trend in deployments these days to deploy containers with read-only root filesystems.

All the storage is off into (NO)SQL databases or cloud storage.

I was quite keen on Confex because it’s templating mechanism (interpolating environment variables at runtime via special app configuration syntax like {:system, "ENV_NAME", default}) allowed one to deploy on a read-only filesystem.

But, for some reason, the community didn’t adopt Confex - and everyone used the default release mechanism (releases.exs).

Now I find myself in the situation that many customer security teams are demanding apps are runnable with the security constraint that they can run on read-only file systems.

The average application reads in about 10~15 values from environment variables and I want to have one place that developers can consult to ensure all values are set rather than arbitrarily loading env values throughout the codebase.

Can anyone suggest a workaround - or should I just rewrite everything to use Confex?

Maybe waiting for 1.11 is an option:

Given that config/runtime.exs will be loaded at runtime (no restart as with releases.exs) it shouldn’t need to persist anything to disk. Also runtime.exs is not file in your release, which people can look at.

3 Likes

I love this community. Yeah, that will do nicely.

1 Like

What does confex give you that using releases.exs doesn’t? Or I guess my question is why don’t releases work with a read only file system ?

1 Like

Finally, in order for runtime configuration to work properly (as well as any other “Config provider” as defined next), it needs to be able to persist the newly computed configuration to disk. The computed config file will be written to “tmp” directory inside the release every time the system boots.

https://hexdocs.pm/mix/Mix.Tasks.Release.html

Edit:
releases.exs works by starting the beam in a minimal setup, apply the releases.exs and write the config back in an erlang config format to disk, stopping and another beam process is then fully starting up using the erlang config written to disk.

runtime.exs will work the same as releases.exs in the sense that it will also need to write to disk. However, there is a release option on v1.10 that disables the writing to disk:

reboot_system_after_config: false

The nerves team needed this option for similar reasons as @bryanhuntesl, so we added it. The only downside is that you can’t configure kernel/stdlib via config/releases.exs if you use this option. You should still be able to configure them via vm.args though.

7 Likes

Interesting. This sounds like it’s actually preferable to the default of the rebooting system unless actually needed.

1 Like

Interesting. If I don’t use releases.exs at all, does a release work on a read only file system?

That should work out of the box. If there’s no runtime config there’s nothing to write.

Well, there is always possibility to just create tmpfs and mount it where you need write data during startup. In that way you will achieve what you need. IIRC there is possibility to do so automatically with systemd if needed.

2 Likes

That sounds a bit complicated for the happy path - a Java, Golang, Ruby application doesn’t need a writable filesystem in order to read it’s configuration at boot.

Afaik the problem is not “reading config”, but that the configuration file a release loads must be an erlang formatted file and everything being able to read .exs files using the Config module of elixir it not yet available early enough in the boot sequence to configure things like :kernel. This creates a chicken/egg type problem, where one way of solving it is booting twice and persisting the read config from the first boot, so the second one can read the result in erlang terms.

1 Like

Well, if you provide sys.config directly instead of using any provider, then you will not need writeable FS. But as Elixir developers rarely do so, and prefer “easier” solutions - this is how @LostKobrakai described.

It will be the case in Elixir v1.11 if using config/runtime.exs. The reboot will have to be opt-in.

3 Likes

I just tried this out on a system of mine, but I’m hitting a few of Cannot configure {app} because :reboot_after_config has been set to false and {app} has already been loaded errors (which are neither kernel nor stdlib, but dependencies / my app itself)

How does your config file look like? Are you starting any applications within it?

If you go to your release and then in releases/VERSION/start.script, what are the applications listed before {apply,{'Elixir.Config.Provider',boot,[]}}?

If you do add a IO.puts "hello" to your “config/releases.exs”, does it print once or twice when you boot?

Finally, can you isolate this issue somehow?

Thanks!

My releases.exs file is basically just a bunch of config … calls setting data from System.fetch_env!. Nothing special, not starting anything. The start.script does only :start_boot :kernel and :stdlib before the config provider, which however looks like this for me: {apply,{'Elixir.Config.Provider',boot,[elixir,config_providers]}}. I’ll try to see if I can replicate this in a fresh project.

Seems like this was quite easy to reproduce: https://github.com/LostKobrakai/elixir_release_repro
I’ve used a commit per logical step, so it should be easy to follow how I got there.

Edit:

It can be as simple as mix new project and adding the following:

# releases.exs
import Config

config :project, key: "something"

and

# mix.exs project/0 
releases: [
      other: [
        reboot_system_after_config: false
      ]
    ]

To support read only file systems along with the relx start scripts that replace os vars in sys.config and vm.args I use an in memory volume, like running docker with --read-only and --tmpfs /tmp, and set the output (in this case $RELX_OUT_FILE_PATH) to go to /tmp.

It is discussed https://adoptingerlang.org/docs/production/docker – I now feel it needs to be a section to better highlight this, right now it is just sort of intertwined with the rest of the chapter.

1 Like

Thank you! I could reproduce the issue and it has been fixed in Elixir master. We should have a RC out shortly.

2 Likes