Repository: phoenixframework/phoenix
Pull request #3380 (2019-04-19 18:31:40)

Modernize config/prod.secret.exs
We have historically stayed away from environment variables
because it was incompatible with releases but now with
Distillery 2 and `mix release` in Elixir v1.9 this is a
solved problem, so we can push towards those best practices
for new apps.


Patch:
diff --git a/guides/deployment/deployment.md b/guides/deployment/deployment.md
index aa6d417d7..644656ad8 100644
--- a/guides/deployment/deployment.md
+++ b/guides/deployment/deployment.md
@@ -16,38 +16,20 @@ Let's explore those steps above one by one.
 
 ## Handling of your application secrets
 
-All Phoenix applications have data that must be kept secure, for example, the username and password for your production database, and the secret Phoenix uses to sign and encrypt important information. This data is typically kept in `config/prod.secret.exs` and by default it is not checked into your version control system.
+All Phoenix applications have data that must be kept secure, for example, the username and password for your production database, and the secret Phoenix uses to sign and encrypt important information. The general recommendation is to keep those in environment variables and load them into your application. This is done in `config/prod.secret.exs`, which is responsible for loading secrets and configuration from environment variables.
 
-Therefore, the first step is to get this data into your production machine. Here is the template shipped with new applications:
+Therefore, you need to make sure the proper relevant variables are set in production:
 
-```elixir
-use Mix.Config
-
-# In this file, we keep production configuration that
-# you likely want to automate and keep it away from
-# your version control system.
-
-# You can generate a new secret by running:
-#
-#     mix phx.gen.secret
-config :foo, Foo.Endpoint,
-  secret_key_base: "A LONG SECRET"
-
-# Configure your database
-config :foo, Foo.Repo,
-  username: "postgres",
-  password: "postgres",
-  database: "foo_prod",
-  size: 20 # The amount of database connections in the pool
+```console
+$ mix phx.gen.secret
+REALLY_LONG_SECRET
+$ export SECRET_KEY_BASE=REALLY_LONG_SECRET
+$ export DATABASE_URL=ecto://USER:PASS@HOST/database
 ```
 
-There are different ways to get this data into production. One option is to replace the data above by environment variables and set those environment variables in your production machine. This is the step that we follow [in the Heroku guides](heroku.html).
-
-Another approach is to configure the file above and place it in your production machines apart from your code checkout, for example, at "/var/config.prod.exs". After doing so, you will have to import it from `config/prod.exs`. Search for the `import_config` line and replace it by the proper path:
+Do not copy those values directly, set `SECRET_KEY_BASE` according to the result of `mix phx.gen.secret` and `DATABASE_URL` according to your database address.
 
-```elixir
-import_config "/var/config.prod.exs"
-```
+If for some reason you do not want to rely on environment variables, you can hard code the secrets in your `config/prod.secret.exs`, but make sure to check the file into your version control system.
 
 With your secret information properly secured, it is time to configure assets!
 Before taking this step, we need to do one bit of preparation.
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index 9907d9a8c..f03499c01 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -120,26 +120,9 @@ This phoenix static buildpack pack can be configured to change the node version
 
 ## Making our Project ready for Heroku
 
-Every new Phoenix project ships with a config file `config/prod.secret.exs` which stores configuration that should not be committed along with our source code. By default Phoenix adds it to our `.gitignore` file.
+Every new Phoenix project ships with a config file `config/prod.secret.exs` which loads configuration and secrets from [environment variables](https://devcenter.heroku.com/articles/config-vars). This aligns well with Heroku best practices, so most the only work left for us to do is to configure URLs and SSL.
 
-This works great except Heroku uses [environment variables](https://devcenter.heroku.com/articles/config-vars) to pass sensitive informations to our application. It means we need to make some changes to our config before we can deploy.
-
-First, let's make sure our secret key is loaded from Heroku's environment variables instead of `config/prod.secret.exs` by adding a `secret_key_base` line  in `config/prod.exs` (remember to add a comma to the end of the preceding line):
-
-```elixir
-config :hello, HelloWeb.Endpoint,
-  url: [host: "example.com", port: 80],
-  cache_static_manifest: "priv/static/cache_manifest.json",
-  secret_key_base: System.get_env("SECRET_KEY_BASE") || raise("missing SECRET_KEY_BASE env var")
-```
-
-Next let's tell the Phoenix application to bind to the PORT environment variable provided by Heroku's [dyno networking](https://devcenter.heroku.com/articles/dynos#common-runtime-networking) so that it can accept incoming web traffic. Add this anywhere within your endpoint configuration:
-
-```elixir
-http: [port: System.get_env("PORT")]
-```
-
-Now, let's tell Phoenix to use our Heroku URL and enforce we only use the SSL version of the website. If we don't override the `host` variable, then when using plugs like `Routes.page_url` (`Routes.page_path` is unaffected as it uses relative URL's) the URL will generate incorrectly. Find the url line:
+First let's tell Phoenix to use our Heroku URL and enforce we only use the SSL version of the website. Find the url line in your `config/prod.exs`:
 
 ```elixir
 url: [host: "example.com", port: 80],
@@ -152,44 +135,13 @@ url: [scheme: "https", host: "mysterious-meadow-6277", port: 443],
 force_ssl: [rewrite_on: [:x_forwarded_proto]],
 ```
 
-Then, we'll add the production database configuration to `config/prod.exs`:
+Then open up your `config/prod.secret.exs` and uncomment the `# ssl: true,` line in your repository configuration. It will look like this:
 
 ```elixir
-# Configure your database
 config :hello, Hello.Repo,
-  url: System.get_env("DATABASE_URL"),
-  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
-  ssl: true
-```
-
-Since our configuration is now handled using Heroku's environment variables, we don't need to import the `config/prod.secret.exs` file in `/config/prod.exs` any longer, so we can delete the following line:
-
-```elixir
-import_config "prod.secret.exs"
-```
-
-Our `config/prod.exs` now looks like this:
-
-```elixir
-use Mix.Config
-
-...
-
-config :hello, HelloWeb.Endpoint,
-  http: [port: System.get_env("PORT")],
-  url: [scheme: "https", host: "mysterious-meadow-6277.herokuapp.com", port: 443],
-  force_ssl: [rewrite_on: [:x_forwarded_proto]],
-  cache_static_manifest: "priv/static/cache_manifest.json",
-  secret_key_base: System.get_env("SECRET_KEY_BASE") || raise "missing SECRET_KEY_BASE env var"
-
-# Do not print debug messages in production
-config :logger, level: :info
-
-# Configure your database
-config :hello, Hello.Repo,
-  url: System.get_env("DATABASE_URL"),
-  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
-  ssl: true
+  ssl: true,
+  url: database_url,
+  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
 ```
 
 Finally, if you plan on using websockets, then we will need to decrease the timeout for the websocket transport in `lib/hello_web/endpoint.ex`. If you do not plan on using websockets, then leaving it set to false is fine. You can find further explanation of the options available at the [documentation](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3-websocket-configuration).
@@ -227,6 +179,7 @@ $ heroku config:set POOL_SIZE=18
 This value should be just under the number of available connections, leaving a couple open for migrations and mix tasks. The hobby-dev database allows 20 connections, so we set this number to 18. If additional dynos will share the database, reduce the `POOL_SIZE` to give each dyno an equal share.
 
 When running a mix task later (after we have pushed the project to Heroku) you will also want to limit its pool size like so:
+
 ```console
 $ heroku run "POOL_SIZE=2 mix hello.task"
 ```
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index bb652bd8c..07b9d6a9b 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -141,7 +141,6 @@ defmodule Phx.New.Generator do
       phoenix_static_path: phoenix_static_path(phoenix_path),
       pubsub_server: pubsub_server,
       secret_key_base: random_string(64),
-      prod_secret_key_base: random_string(64),
       signing_salt: random_string(8),
       in_umbrella: project.in_umbrella?,
       webpack: webpack,
@@ -180,9 +179,17 @@ defmodule Phx.New.Generator do
     """
 
     config_inject project_path, "config/prod.secret.exs", """
-    # Configure your database
-    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config adapter_config[:prod]},
-      pool_size: 15
+    database_url =
+      System.get_env("DATABASE_URL") ||
+        raise \"""
+        environment variable DATABASE_URL is missing.
+        For example: ecto://USER:PASS@HOST/DATABASE
+        \"""
+
+    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo,
+      # ssl: true,
+      url: database_url,
+      pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
     """
   end
 
@@ -208,7 +215,6 @@ defmodule Phx.New.Generator do
             show_sensitive_data_on_connection_error: true],
      test: [username: user, password: pass, database: "#{app}_test", hostname: "localhost",
             pool: Ecto.Adapters.SQL.Sandbox],
-     prod: [username: user, password: pass, database: "#{app}_prod"],
      test_setup_all: "Ecto.Adapters.SQL.Sandbox.mode(#{inspect module}.Repo, :manual)",
      test_setup: ":ok = Ecto.Adapters.SQL.Sandbox.checkout(#{inspect module}.Repo)",
      test_async: "Ecto.Adapters.SQL.Sandbox.mode(#{inspect module}.Repo, {:shared, self()})"]
diff --git a/installer/templates/phx_single/config/prod.exs b/installer/templates/phx_single/config/prod.exs
index d767c2675..7b122462e 100644
--- a/installer/templates/phx_single/config/prod.exs
+++ b/installer/templates/phx_single/config/prod.exs
@@ -10,7 +10,6 @@ use Mix.Config
 # which you should run after static files are built and
 # before starting your production server.
 config :<%= web_app_name %>, <%= endpoint_module %>,
-  http: [:inet6, port: System.get_env("PORT") || 4000],
   url: [host: "example.com", port: 80],
   cache_static_manifest: "priv/static/cache_manifest.json"
 
@@ -51,21 +50,16 @@ config :logger, level: :info
 #
 # Check `Plug.SSL` for all available options in `force_ssl`.
 
-# ## Using releases (distillery)
+# ## Using releases (Elixir v1.9+)
 #
 # If you are doing OTP releases, you need to instruct Phoenix
-# to start the server for all endpoints:
-#
-#     config :phoenix, :serve_endpoints, true
-#
-# Alternatively, you can configure exactly which server to
-# start per endpoint:
+# to start each relevant endpoint:
 #
 #     config :<%= web_app_name %>, <%= endpoint_module %>, server: true
 #
-# Note you can't rely on `System.get_env/1` when using releases.
-# See the releases documentation accordingly.
+# Then you can assemble a release by caling `mix release`.
+# See `mix help release` for more information.
 
-# Finally import the config/prod.secret.exs which should be versioned
-# separately.
+# Finally import the config/prod.secret.exs which loads secrets
+# and configuration from environment variables.
 import_config "prod.secret.exs"
diff --git a/installer/templates/phx_single/config/prod.secret.exs b/installer/templates/phx_single/config/prod.secret.exs
index e1ea3d1fc..e58f01b7b 100644
--- a/installer/templates/phx_single/config/prod.secret.exs
+++ b/installer/templates/phx_single/config/prod.secret.exs
@@ -1,12 +1,17 @@
+# In this file, we load production configuration and
+# secrets from environment variables. You can also
+# hardcode secrets, although such is generally not
+# recommended and you have to remember to add this
+# file to your .gitignore.
 use Mix.Config
 
-# In this file, we keep production configuration that
-# you'll likely want to automate and keep away from
-# your version control system.
-#
-# You should document the content of this
-# file or create a script for recreating it, since it's
-# kept out of version control and might be hard to recover
-# or recreate for your teammates (or yourself later on).
+secret_key_base =
+  System.get_env("SECRET_KEY_BASE") ||
+    raise """
+    environment variable SECRET_KEY_BASE is missing.
+    You can generate one by calling: mix phx.gen.secret
+    """
+
 config :<%= app_name %>, <%= endpoint_module %>,
-  secret_key_base: "<%= prod_secret_key_base %>"
+  http: [:inet6, port: String.to_integer(System.get_env("PORT") || "4000")],
+  secret_key_base: secret_key_base
diff --git a/installer/templates/phx_single/gitignore b/installer/templates/phx_single/gitignore
index 1fae1498f..85ed70f12 100644
--- a/installer/templates/phx_single/gitignore
+++ b/installer/templates/phx_single/gitignore
@@ -32,11 +32,3 @@ npm-debug.log
 # we ignore priv/static. You may want to comment
 # this depending on your deployment strategy.
 /priv/static/
-
-# Files matching config/*.secret.exs pattern contain sensitive
-# data and you should not commit them into version control.
-#
-# Alternatively, you may comment the line below and commit the
-# secrets files as long as you replace their contents by environment
-# variables.
-/config/*.secret.exs
diff --git a/installer/templates/phx_umbrella/apps/app_name/gitignore b/installer/templates/phx_umbrella/apps/app_name/gitignore
index 8b885a323..38759a41a 100644
--- a/installer/templates/phx_umbrella/apps/app_name/gitignore
+++ b/installer/templates/phx_umbrella/apps/app_name/gitignore
@@ -21,11 +21,3 @@ erl_crash.dump
 
 # Ignore package tarball (built via "mix hex.build").
 <%= app_name %>-*.tar
-
-# Files matching config/*.secret.exs pattern contain sensitive
-# data and you should not commit them into version control.
-#
-# Alternatively, you may comment the line below and commit the
-# secrets files as long as you replace their contents by environment
-# variables.
-/config/*.secret.exs
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs
index 4dc5b3366..d3d09701b 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs
@@ -8,7 +8,6 @@
 # which you should run after static files are built and
 # before starting your production server.
 config :<%= web_app_name %>, <%= endpoint_module %>,
-  http: [:inet6, port: System.get_env("PORT") || 4000],
   url: [host: "example.com", port: 80],
   cache_static_manifest: "priv/static/cache_manifest.json"
 
@@ -46,17 +45,12 @@ config :<%= web_app_name %>, <%= endpoint_module %>,
 #
 # Check `Plug.SSL` for all available options in `force_ssl`.
 
-# ## Using releases (distillery)
+# ## Using releases (Elixir v1.9+)
 #
 # If you are doing OTP releases, you need to instruct Phoenix
-# to start the server for all endpoints:
-#
-#     config :phoenix, :serve_endpoints, true
-#
-# Alternatively, you can configure exactly which server to
-# start per endpoint:
+# to start each relevant endpoint:
 #
 #     config :<%= web_app_name %>, <%= endpoint_module %>, server: true
 #
-# Note you can't rely on `System.get_env/1` when using releases.
-# See the releases documentation accordingly.
+# Then you can assemble a release by caling `mix release`.
+# See `mix help release` for more information.
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
index fce3afa4b..6aca7b17f 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
@@ -1,2 +1,10 @@
+secret_key_base =
+  System.get_env("SECRET_KEY_BASE") ||
+    raise """
+    environment variable SECRET_KEY_BASE is missing.
+    You can generate one by calling: mix phx.gen.secret
+    """
+
 config :<%= web_app_name %>, <%= endpoint_module %>,
-  secret_key_base: "<%= prod_secret_key_base %>"
+  http: [:inet6, port: String.to_integer(System.get_env("PORT") || "4000")],
+  secret_key_base: secret_key_base
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/gitignore b/installer/templates/phx_umbrella/apps/app_name_web/gitignore
index 7037fe263..a9beba71e 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/gitignore
+++ b/installer/templates/phx_umbrella/apps/app_name_web/gitignore
@@ -32,11 +32,3 @@ npm-debug.log
 # we ignore priv/static. You may want to comment
 # this depending on your deployment strategy.
 /priv/static/
-
-# Files matching config/*.secret.exs pattern contain sensitive
-# data and you should not commit them into version control.
-#
-# Alternatively, you may comment the line below and commit the
-# secrets files as long as you replace their contents by environment
-# variables.
-/config/*.secret.exs
diff --git a/installer/templates/phx_umbrella/config/prod.secret.exs b/installer/templates/phx_umbrella/config/prod.secret.exs
index e2d45c783..29970b64e 100644
--- a/installer/templates/phx_umbrella/config/prod.secret.exs
+++ b/installer/templates/phx_umbrella/config/prod.secret.exs
@@ -1,12 +1,6 @@
-# In this file, we keep production configuration that
-# you'll likely want to automate and keep away from
-# your version control system.
-#
-# You should document the content of this file or create
-# a script for recreating it, since it's kept out of version
-# control and might be hard to recover or recreate for your
-# teammates (or yourself later on).
-#
-# Alternatively, you can keep this file versioned and use
-# environment variables instead.
+# In this file, we load production configuration and
+# secrets from environment variables. You can also
+# hardcode secrets, although such is generally not
+# recommended and you have to remember to add this
+# file to your .gitignore.
 use Mix.Config


Test patch:
diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs
index cf1af402b..bfd73d23d 100644
--- a/installer/test/phx_new_test.exs
+++ b/installer/test/phx_new_test.exs
@@ -307,7 +307,7 @@ defmodule Mix.Tasks.Phx.NewTest do
       assert_file "custom_path/mix.exs", ":postgrex"
       assert_file "custom_path/config/dev.exs", [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
       assert_file "custom_path/config/test.exs", [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
-      assert_file "custom_path/config/prod.secret.exs", [~r/username: "postgres"/, ~r/password: "postgres"/]
+      assert_file "custom_path/config/prod.secret.exs", [~r/url: database_url/]
       assert_file "custom_path/lib/custom_path/repo.ex", "Ecto.Adapters.Postgres"
 
       assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.checkout"
@@ -324,7 +324,7 @@ defmodule Mix.Tasks.Phx.NewTest do
       assert_file "custom_path/mix.exs", ":myxql"
       assert_file "custom_path/config/dev.exs", [~r/username: "root"/, ~r/password: ""/]
       assert_file "custom_path/config/test.exs", [~r/username: "root"/, ~r/password: ""/]
-      assert_file "custom_path/config/prod.secret.exs", [~r/username: "root"/, ~r/password: ""/]
+      assert_file "custom_path/config/prod.secret.exs", [~r/url: database_url/]
       assert_file "custom_path/lib/custom_path/repo.ex", "Ecto.Adapters.MyXQL"
 
       assert_file "custom_path/test/support/conn_case.ex", "Ecto.Adapters.SQL.Sandbox.mode"
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index 7fc5c667d..e802ef3a1 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -349,7 +349,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
 
       assert_file root_path(app, "config/dev.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
       assert_file root_path(app, "config/test.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
-      assert_file root_path(app, "config/prod.secret.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/]
+      assert_file root_path(app, "config/prod.secret.exs"), [~r/url: database_url/]
 
       assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
       assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
@@ -367,7 +367,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
 
       assert_file root_path(app, "config/dev.exs"), [~r/username: "root"/, ~r/password: ""/]
       assert_file root_path(app, "config/test.exs"), [~r/username: "root"/, ~r/password: ""/]
-      assert_file root_path(app, "config/prod.secret.exs"), [~r/username: "root"/, ~r/password: ""/]
+      assert_file root_path(app, "config/prod.secret.exs"), [~r/url: database_url/]
 
       assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
       assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"