Rails config/local_env.yml vs .env vs bashrc environment variables - ruby-on-rails

I was reading from this article that you can create a config/local_env.yml with environment variables and then use config/application.rb to read it to replace/take priority before the environment variables that you export in your .bashrc. What then is the .env used for then? Does it serve the same purpose as the config/local_env.yml?

All of these methods are used to feed environment variables to your rails application. So, from an app's point of view, it serves the same purpose whether you export it from .env or .bashrc or config/local_env.yml files.
The differences in these methods are really a matter of personal choice among the team members involved in maintaining the app's development and deployment environments. However, here are few things to consider while opting for either of these choices.
.bashrc - Use this if you manually configure deployment servers and really comfortable with linux/unix command line system administration. This configuration file is specific to bash shell (Bourne Again Shell). You need to configure different file if your server uses different shell (for example: .zshrc if it uses Z Shell)
.env - Use this if you want to keep your app centric environment variables within the app itself while maintaining different variations of environment variables for different runtime environments of your rails app. For example: .env.development and .env.test files with specific values of the environment variables for your development and test environments respectively. This gives you more control of your app's environment variables and do not have to rely on the platform (terminal shell) you want to deploy your app.
config/local_env.yml - This is similar to .env approach, which is provided out of the box by rails gem that allows you to configure environment variables for your app in yml format. This method also keeps your app's configuration within the app irrespective of the shell you are using to run your app.

In addition to the previous answer, another downside of using .bashrc is that it is specific to only one user, so if you're e.g. starting your app server as a systemd service then I believe it won't see your variables.
Meanwhile, .env's Github readme says that it is not the most recommended thing to use outside of the development environment, although it is OK for that purpose.
Another two options to consider are:
Rails secrets. The benefit of this approach is that you get to commit it to git since it's encrypted, so when working in a team you will all have access to the same file. The downside is that it is coupled to the RAILS_ENV variable, so you can't use it to set that e.g. to production on your production app (but you can manually pass it every time it's invoked). Another downside is that if you have a staging environment, then apparently Heroku discourages using RAILS_ENV=staging, which you sometimes really need, so if you need it then you can either do it anyway, or you'll need to set the differing variables via a different mechanism - for example my app has a variable which points to the URL of another part of my app, this URL needs to point to its staging variant on staging, and to its production variant on production, so it needs to differ between my production and staging environment.
Using an /etc/environment file - note that systemd services don't have access to it by default so you'd have to add the line EnvironmentFile=/etc/environment. Also if you're running a shell script from a non-login shell (which happens sometimes), they it won't load them either, but the solution is to just include in your script this: set -a; source /etc/environment; set +a. You should be careful not to commit this to git. If you're working in a team and you need to manage this file then it gets a little complicated since it's not committed to git, but maybe there's a way to have it encrypted. (systemd services note: you might optionally use LoadCredential= for sensitive variables such as private keys, so that other processes won't have access to them; if you do this then you can also commit to git /etc/environment which should now contain non-sensitive variables only).
In my opinion both of those are valid and it's fine to pick whatever is easier to do in your app.

Related

How to manage credentials with a multi-stages, single-environment Rails app?

TL; DR
How to use production.yml.enc and staging.yml.enc credential files in two production- and staging- Rails apps, while the app has only the regular development, test and production environments?
I'm using Heroku and refer to it in this question. Yet this is not specific to this vendor.
In detail
An application is often deployed multiple times. An instance serves as the production, while another is the staging app, expected to be put in production. Rails facilitates this pattern, since creating new environments is easy.
However, Heroku suggests not to do this, with good reasons. For example, one can be tempted to put some if Rails.env.production? here and there, paving the way for some "but it works in staging!?" on Friday evening. It is better to have a single production environment, with different sets of parameters to actually differentiate the stages (eg. a different AWS S3 bucket name, a different API key, etc.). To achieve this, Heroku's advice is to rely on environment variables.
Since Rails 5.2 and later in Rails 6, credentials are conveniently handled via encrypted Yaml files in config/credentials. This is typically where one would like to store all these variables that change from an environment to another, instead of using messy environment variables. This mechanism can be used in Heroku thanks to a single RAILS_MASTER_KEY environment variable that contains the key used to decrypt the credentials file.
But these pieces do not fit well. How can we have a single production environment, whereas credentials files are per-environment?
This can be implemented with a staging credentials file and key, and a switch in application.rb based on an environment variable:
Create and populate a staging credentials file and key with EDITOR=vi rails credentials:edit --environment staging.
In application.rb, add:
# Introduce the environment variable RAILS_CREDENTIALS_ENVIRONMENT to specify a custom
# environment name of which to use the credentials file. Specifically aimed to use in staging,
# where the environment is set to "production", but we need to use the "staging" environment variables.
# This environment variable is also used to select the corresponding key file or -if that does not exist-
# the corresponding environment variable.
if ENV['RAILS_CREDENTIALS_ENVIRONMENT'].present?
new_filename_no_ext = Rails.root.join 'config', 'credentials', ENV['RAILS_CREDENTIALS_ENVIRONMENT']
config.credentials.content_path = "#{new_filename_no_ext}.yml.enc"
if File.exist? "#{new_filename_no_ext}.key"
config.credentials.key_path = "#{new_filename_no_ext}.key"
end
end
Set an environment variable RAILS_CREDENTIALS_ENVIRONMENT to production or staging, depending on the case. For example, Heroku does this with:
heroku config:set -a theapp-staging RAILS_CREDENTIALS_ENVIRONMENT=staging
If on staging or produciton you want to store your key in an environment variable instead of a key file, then simply assign the key to the RAILS_MASTER_KEY environment variable. As documented, this takes precendence over keys stored in files. Note that on your development machine you wouldn't want to have a RAILS_MASTER_KEY set, otherwise the credential files for all environments still get the same key and are thus accessible by everyone that needs to have access to (e.g.) only the development credentials.
Have several files one by each environment in your source code repository o local server disk, works but you will have several files with hardcoded values.
But, if you expect several teams with several requirements on your single app, you will need several development and test environments in order to keep an independent teams which are fully responsible for their Services:
Development
Release / Deployment
Ops (not platform/system administration)
An approach to management this is : externalize your configurations on platforms called: Configurations Manager
This platforms, must have the following features:
key-vaue pair creation by app. Like heroku web dashboard
expose http endpoint to get this variables from remote apps
security
Your rails app must get the variables at the startup stage or instantaneous if your language support hot reload variables.
Here some Configurations Managers:
Zookeeper : http://www.therore.net/java/2015/05/03/distributed-configuration-with-zookeeper-curator-and-spring-cloud-config.html
Consul : https://www.consul.io
Spring Cloud : https://www.baeldung.com/spring-cloud-configuration
Confignet: https://github.com/software-architect-tools/confignet
Whit this approach you will have a platform to management several apps for several environments like heroku web variables creation but more sophisticated.

Does dotenv contradict the Twelve-Factor App?

I have read about the Twelve Factor App's Config - Section III and searched for a way to do it in NodeJS. It seems that most people recommend dotenv to store configs in environment variables.
However, it seems that dotenv contradicts the Twelve-Factor App, as states:
Another approach to config is the use of config files which are not checked into revision control, such as config/database.yml in Rails. This is a huge improvement over using constants which are checked into the code repo, but still has weaknesses: it’s easy to mistakenly check in a config file to the repo; there is a tendency for config files to be scattered about in different places and different formats, making it hard to see and manage all the config in one place. Further, these formats tend to be language- or framework-specific.
The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.
Making sense of this statement, it seems that using dotenv, you will create a config file .env which will then export them as environment variables.
Wouldn't this violate the Twelve-Factor App, since the .env file can get accidentally checked into the code repo?
12factor is not violated until somebody actually commits and pushes the .env ;)
The .env file can also be stored outside the repo itself, since a library or app is still has to read the .env file and push the variables into the environment. Depending on your implementation, this can be as simple as changing the path from ".env" to "../.env".
Using .env files can be a good compromise to allow developers to manage environments easily, but still be compatible with better environment practices during deployment. I might have 30-40 12factor-flavored apps running in a virtual machine, and having to manage each environment separately is daunting without a "shim" like .env.
The OP asked a well-thought-out question.
I would say the dotenv contradict the 12-factor, section 3, for 2 reasons.
By definition, i.e. this paragraph: "Another approach to config is the use of config files which are not checked into revision control, ... still has weaknesses: it’s easy to mistakenly check in a config file to the repo; ... (therefore the 12-factor app uses a different approach as) stores config in environment variables", now you see, just because an .env file could/should be declared inside a .gitignore, does not make dotenv exempt from that "easy to mistakenly check in a config file to the repo" scrutiny.
An app could otherwise be fully complied with 12-factor, section 3, if it read config from and only from env var. But a dotenv feature is to allow the app to automatically pickup ./.env if it is available. In that sense, the .env file - despite its deceptive name - IS a config file, through and through. Again, this falls into the config approach category that the 12-factor explicitly avoids.
That being said, dotenv is still one of the viable option. Other options include: managing env vars in docker layer; or in a unix user's .*rc file; or in web server config; or in /etc/profile (quoted from this another SO post). dotenv offers one of the most universal file format (fwiw, docker env_file is equally straightforward although their specs are different), however dotenv is the only solution that "polutes" the target app's code base with one more dependency.
Bottom line, dotenv is fine, it is also funny that many dotenv implementation inherit the claim that it started from the 12-factor app tenet, but only few admits that its .env approach deviates from 12-factor app.
Most version control systems have ways of ignoring certain files.
Git has the .gitignore
SVN has "Special Ignores"
As a side note, these techniques are similarly used to ignore the node_modules directory to avoid needlessly copying files.

Pheonix Framework Environment Variables

I am just learning Phoenix and Elixir I am confused, what is the best way to handle environment variables for multiple machines and environments? I keep running into different approaches, from using System.get_env, .env files and mentions of Mix env's. I also keep reading about problems compiling env variables at deployment.
Does anyone have an explanation of how Mix variables, system environment variables and possible .env files or .secret files should be used for local development, stage and production servers?
I have been working mostly in Rails and Python recently so that maybe a helpful contextual piece.
Thanks for the help,
Cory
I personally stick with what Phoenix is giving you by default, e.g. using config files for different environments. Since config files are meant for application configuration, e.g. configuring database adapters, they are checked into source control. By default, these are controlled by the MIX_ENV environment variable. If you look at the bottom of your main config/config.exs file, you will notice this:
import_config "#{Mix.env}.exs"
From what I understand from the documentation, Mix.env is just a shorthand for getting the MIX_ENV value.
Phoenix comes with config files for "develop", "prod" and "test" (all in the config directory) which you can modify for your own use. You can also easily add more configurations — if you want to have a "staging"-specific configuration, for example, just set MIX_ENV=staging on the relevant server, and create config/staging.exs.
For sensitive information, like API keys, environment variables are better, since they're not checked into source control and can be easily changed. You can access those env variables from within your config files or from anywhere within your application.
Hope that helps!

Load environment variables in rails app without restarting server

How can I make my rails app aware of the new environment variables after I have edited my /etc/environment file on my remote EC2 instance?
I frequently add new (minor) things in my secrets.yml but I don't want to restart my server for it, nor do I want to use an existing secret.
In linux every process inherits envvars from its parent process and the values are passed by value, not by reference. Also, they don't behave like closures. So, child process (your rails/ruby app process) will not get any new environment variables of its parent (the shell process where you started your rails/ruby app).
That's why it is not possible. However, you can use gems like dotenv and figaro to watch some file with your environment variables and reload them when they are changed.
You should be able to add a line to your config/spring.rb:
Spring.watch "config/secrets.yml"
This will allow Spring to detect when changes have occurred in your secrets.yml file.
However, if you're actually asking about how to make your app aware that you've changed environment variables in a file, then it's not possible. Config values may be detected in files, but environment variables are detected in the shell environment. You have to load those into your shell for them to take any effect, and this would require stopping your server, sourcing the new changes into the environment, and starting the server again.
Understanding the difference between a config value in a file (.yml, .xml, .ini, etc) versus an environment variable in a shell script is important, because how it's applied and made usable is entirely different.

Managing config in 12-factor applications

I've enjoyed using Rails on Heroku, and like that I can adjust the configuration property of a Heroku app without having to commit a change to xyz.yml and redeploy.
It would be nice to completely do away with the Yaml config files in my Rails app, and rely as much as possible on storing configuration in ENV. This goes along with the 12-factor config principle.
However, there are some trade-offs in switching from a Yaml-based configuration management to a Heroku/12-factor-based one.
While it's true that a proliferation of deployments (qa, stage, prod, dev, demo, labs) can lead to a proliferation of Yaml files, it's very easy to copy-paste to create a new configuration profile. I don't see a way to 'copy' configuration profiles from one deployment to another in Heroku.
Storing configuration data in the repo means that, in the case of Heroku, deploying and configuring and application are accomplished in a single operation. If I were to move my configuration out of Yaml files and into ENV variables, I would have to configure my application in a separate step after deployment.
Would like to hear from people who have used 12-factor style configuration in their private applications, and how they have managed lots of configuration variables across lots of deployments.
How do you quickly configure a new deployment?
Where do you keep your authoritative source of configuration variables, if not the repo? How do you distribute it among developers?
Thanks!
What I typically use is Yaml using the ENV and provide defaults. For instance, YAML can be ERB'ed happily to include your ENV vars:
foo:
var: ENV["MY_CONFIG"] || "default_value"
You just need to make sure that you load the Yaml with ERB when you read it:
YAML.load(ERB.new(File.read("#{Rails.root}/config/app_config.yml")).result)
By doing this your code works fine in dev, but also allows you to set config vars in the environment as well.
You can accomplish this relatively easy with some simple shell scripts, iterate existing variables via heroku config or heroku release:info v99, and then set heroku config:set k=v --app
But if its a problem/pain/friction perhaps you have too much inside your env var configuration.
A bit of a late answer, but I believe this is what you are looking for.
I developed a gem called settei, allowing you to use YAML files to configure the app. However the gem will serialize the YAML file as one environment variable during deploy. This way one get the best of both worlds: YAML for ease of management/creating derivative environment, and ENV for 12-factor compliance.

Resources