Setting Up Local Env Secrets With Elixir Phoenix

Thomas McGoey-Smith's Blog

Loading secrets is pretty much a guaranteed task to ship your app in production.

Right now I’m building busytone, and wondered - what is the best practice for loading secrets in Phoenix.

Coming from Rails, specifically Shopify, we’re lucky enough that this is pretty much a solved problem… ejson (rails gem). EJSON, allows you to easily load encrypted secrets for your rails app.

But for elixir there isn’t a direct go too, gem that can just load up your env secrets.

Loading Secrets

Phoenix uses a few different ways to config your app depending on the environment.

config/config.exs ## used for all environments

config/dev.exs 
config/test.exs
config/prod.exs

# Runtime is relatively new, these config variables are run at *runtime*, meaning that they aren't built when compiling your app
config/runtime.exs

Out of the box, most of my configuration changes aren’t different from the normal phoenix app.

Though, for anything related to the actual app that require injecting environment variables when running the app in production, I’ve found defaulting to use config/runtime.exs the best place to go.

Here’s what configuring slack secrets would end up being:

config :busytone,
  slack_app_id: slack_app_id,
  slack_client_id: slack_client_id,
  slack_client_secret: slack_client_secret,
  slack_signing_secret: slack_signing_secret

Here’s what each variable would end up being:

slack_app_id =
  System.get_env("SLACK_APP_ID") ||
    raise """
    environment variable SLACK_APP_ID is missing.
    """

In dev (and test), my environment variables are set automatically with direnv. To make things easier to work with, I’ve used dotenv with direnv.

dotenv
[ -f ".env.${APP_ENV}" ] && dotenv ".env.${APP_ENV}"
dotenv .env.local

There’s two .env files, public and private.

# Copy to .env.local, replace all REPLACE_ME 

export SECRET_KEY_BASE="something_secret"
export LIVE_VIEW_SIGNING_SALT="another_secret"
export HONEYBADGER_API_KEY="not_set"

# SLACK
export SLACK_APP_ID="REPLACE_ME"
export SLACK_CLIENT_ID="REPLACE_ME"
export SLACK_CLIENT_SECRET="REPLACE_ME"
export SLACK_SIGNING_SECRET="REPLACE_ME"

And the private one is something like:

# Copy to .env.local, replace all REPLACE_ME 

export SECRET_KEY_BASE="3r38r8sk28"
export LIVE_VIEW_SIGNING_SALT="dod92m92"

# SLACK
export SLACK_APP_ID="AB1313AS"

*make sure to add .env.local to your .gitignore

So far this setup of using direnv and the new Elixir runtime config has worked out nicely. No new dependencies, it’s simple without making a problem hard to debug.

Published on: