Are you using custom rails environments? - ruby-on-rails

ootb rails has the 3 environments
development
test
production
How do you handle your other environments for example staging ?
When I think about what I want from a staging environment I want most of the production settings, but typically server names and other specific values are different from production.
Do you create 1:N environment/*.rb for each custom environment you have? Or do you treat the environments more as like a profile and then use something like application.yml or secrets.yml for running the application with different server configurations?
What has worked well for you in the past and what has not?

It depends if your "staging" environment is a different thing or is actually a variation on the "production" environment as is usually the case.
Normally you just deploy in full production mode, using the production.rb file and everything, to a non-production server. This distinction is irrelevant to rails, and matters only to your deployment script. For example Capistrano would be concerned about this.
The only time you need another environment is if you need a different group of settings for Rails.

In my opinion, when I run the app in an staging environment I want it to have exactly the same settings as in production, because it's when I check if everything is alright before releasing the new code. So I create a environments/staging.rb that actually is a symbolic link to environments/production.rb. To deploy, I use Capistrano, which lets you define the server settings for every environment you want to. I really encourage you to take a look at it if you don't know it, as it makes deploying as easy as running a simple command, and it takes care of uploading the new code, precompiling the assets, restarting the server, etc.

Appropriate any way described above.
In some cases I am using 1:N way because it easy when you have fixed count of deployment environments (for example when I am deploying own pet-project to remote server).
But in other cases I am using default set of environments (for example when I am deploying project to different customers).
Just choose any way appropriate for you and your project.

I use require_relative to create a staging environment that is identical to production, and then override the parts that are different. Typically the only difference is hostnames.
Here's what my environments/staging.rb looks like:
# Staging configuration is identical to production, with some overrides
# for hostname, etc.
require_relative "./production"
Rails.application.configure do
config.action_mailer.default_url_options = {
:host => "staging.example.com",
:protocol => "https"
}
config.action_mailer.asset_host = "https://staging.example.com"
end

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.

Are there any reasons not to use RAILS_ENV=staging on Heroku?

The Heroku documentation at https://devcenter.heroku.com/articles/deploying-to-a-custom-rails-environment says I shouldn't use a staging.rb file to define my staging environment.
It may be tempting to create another custom environment such as “staging” and create a config/environments/staging.rb and deploy to a Heroku app with RAILS_ENV=staging.
This is not a good practice. Instead we recommend always running in production mode and modifying any behavior by setting your config vars.
I think this is terrible advice and conflicts with well-established Rails best practice. However, I'm not here to argue about best practices. I'm here to ask:
Are there any reasons not to use RAILS_ENV=staging on Heroku?
Is there anything that will break if I create a staging.rb file and set the xxx_ENV config vars like this?
heroku config:add RACK_ENV=staging --remote staging
heroku config:add RAILS_ENV=staging --remote staging
No, there isn't anything that will break if you do this.
However, I do think that Heroku is right here (note that I work at Heroku).
It introduces possible differences between your staging and production environments, when the only thing that changes between them should be configuration variables.
Heroku already provides a secure mean of setting configuration variables, with the config:set command.
Using 2 config files means you have to maintain the same configuration twice, and possible can have issues because you updated the configuration correctly in staging, but incorrectly in production.
As a side note, RACK_ENV should only ever have 3 values: production, development and none. See https://www.hezmatt.org/~mpalmer/blog/2013/10/13/rack_env-its-not-for-you.html
You'll get some warnings from Heroku when you deploy, but I can confirm I did run staging apps, with RAILS_ENV=staging, on Heroku. As long as you set the correct environment variables and Gemfile groups, it should just work.
My guess is that the reason they advise not to use custom environments is that they have some operational tooling that assumes your Rails app runs in production environment, but so far I didn't run into issues.

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.

Deploying a rails app to multiple locations

I'm trying to deploy the same rails app to two different locations with different app names, different logos, different stylesheets, etc.
I've got the code working based on an APP_NAME and a HOST_NAME variable I store in environments/production.rb. Now I need to actually deploy it, and I need a better solution than manually editing the environment file on the production machine.
The only way I can see to do it is to create a new production environment - e.g. production_app2 - and define APP_NAME and HOST_NAME differently in them. Is there a better way?
No no no! Don't edit the environment files. I mean, edit them as you need to for things that need to be configured the same for every deployment, but not for things that should be configurable between deployments.
For that, use configuration.
Throw a YAML file in config that looks something like this:
development:
:app_name: App 1
:host_name: something.com
test:
:app_name: App 1
:host_name: something.com
production:
:app_name: App 1
:host_name: something.com
Call it whatever makes sense. Let's say settings.yml.
Now load it with an initializer in config/initializers/settings.rb that looks like this:
SETTINGS = YAML.load_file("#{RAILS_ROOT}/config/settings.yml")[RAILS_ENV]
Now access your configuration like this:
SETTINGS[:app_name]
(If you don't want to change your existing code at all, inside config/initializers/settings.rb add lines that set your existing names like APP_NAME = SETTINGS[:app_name], etc.)
Note that this is one possible implementation of settings configuration, but even if another approach is taken it should be based on deployment-independent configuration. This can be much more easily and maintainably set up to persist between deployments and upgrades than mucking with environment files.
Again, to recap:
environment files are for configuration that is the same across all deployments
configuration files are for configuration that can change between deployments
Update
For Capistrano based deployments, this is what I use to symlink multiple configuration files in the new current from the shared directory (I think it originally came from an Ezra recipe from EngineYard):
after "deploy:update_code","deploy:symlink_configs"
namespace(:deploy) do
task :symlink_configs, :roles => :app, :except => {:no_symlink => true} do
configs = %w{ database settings }
configs.map! { |file| "ln -nfs #{shared_path}/config/#{file}.yml #{release_path}/config/#{file}.yml" }
run <<-CMD
cd #{release_path} && #{configs.join(' && ')}
CMD
end
end
I think that's a pretty good way.
Where we are we define different environments (e.g. 'staging', 'production', 'production_backup' - giving us a staging.rb, production.rb, production_backup.rb where you can define your specific APP_NAMEs and HOST_NAMEs) and can deploy to each of them using Capistrano. It works just fine.
This is a good link on it: http://www.egtheblog.com/?p=8
Because you are actually deploying to two different environments, it seems best to create two different environment files, each with their own settings. Make sure you pick descriptive names for your environment files, not just production2.
You could also store this information in the database, but I don't know if you're willing to accept such a dependency. I guess using a database would only make sense if the number of deployments is too large to manage easily with a few environment files.

How do I force Capistrano deployed app to use my development database?

I have a app that I'm deploying to a development server using Capistrano. I'd like to force this deployment to use the development database. So far the only way I've managed to do it is to make my production database info in database.yml equal to the development info. But this is a complete hack.
I've tried setting rails_env to development in deploy.rb but that hasn't worked.
Thoughts?
I ended up using the solution over here. Basically a recipe to replace a line in environment.rb after deploy but before restart.
The problems seems to be with DreamHost's Passenger config. It assumes you're running in production mode.
I'd use Capistrano Ext in order to define multiple deployment environments. I have used this in the past to deply staging and production installations of my apps, so I think it'd work well for you.
Jamis Buck has a writeup if you'd like an overview on how to use it.

Resources