Finally, a Solution to Google App Engine’s Environment Variables

For the last couple of years, deploying a service to GAE meant jumping through ridiculous hoops to deal with secrets in environment variables, but it looks like a solution is finally here!

Igor Belagorudsky
FastCTO
4 min readMay 4, 2020

--

App Engine Services

I love GAE. When clients ask where they should deploy their projects, my default answer (especially if it’s a startup client) is “wherever you get the most credits unless you’re doing something esoteric enough that you’re going to use a lot of formation-specific services — Snowflake, DocumentDB, DialogFlow, etc.

But the truth is, I almost always host my personal projects on Google (and related services). It’s got a generous free tier (but on par with AWS and Azure), great CLI tools (gcloud, etc), and a UI that’s actually usable (looking at you, AWS) when you need to go in and do something. Plus, working with Firebase (auth, functions, hosting) is a pleasure.

The Big Downside 👎

For all its greatness, GAE has had a huge blind spot for the last couple of years — in order to deploy a service, you have to provide an app.yaml file and if you want to use any environment variables, they have to be in the file. There was literally no place to go and just specify environment variables per service. AWS has this, Azure has this, literally everyone has this except GAE. 🤦‍♂

So, obviously you don’t want your secrets in a file that you have to check in (and yes, you want to check in your app.yaml because it has other configurations in there — just like a pipeline file) so developers came up with a number of clever solutions, none of which should need to exist.

The most popular, and the one I’ve been using, is to inject variables into app.yaml during the build process in CI. This usually looks like this:

in app.yaml:

runtime: nodejs12
instance_class: F2
# this will get augmented by CI/CD with proper service name and env vars for deployment

and then in your CircleCI, Travis, etc file (this will look different between CI tools but always the same idea — this example is CircleCI syntax):

command: |
echo "
env_variables:
SOME_VARIABLE: $SOME_VARIABLE
SOME_OTHER_VARIABLE: $SOME_OTHER_VARIABLE
...
service: $GCP_SERVICE_NAME
" >> app.yaml

And then you define SOME_VARIABLE and SOME_OTHER_VARIABLE in your CI tool’s environment variables. Here’s someone’s similar approach and reasoning for Travis — https://www.dontpanicblog.co.uk/2019/04/27/secrets-in-google-app-engine/

It works, but how ridiculous is it that you have to do this?

Finally, A Ray of Sunshine ☀️

Photo by Pablo Heimplatz on Unsplash

Last night, I wanted to deploy another side project that needed some secret environment variables (Stripe, etc) and I was gearing up for having to go through this dance again but then I found… Google Secret Manager!

It’s not exactly fully baked (as of this writing) and it looks like it’s going to cost a little bit— https://cloud.google.com/secret-manager/pricing (which it really shouldn’t but at ~$0.10/month for my case, I’ll bite the bullet).

The documentation isn’t too great yet so it took me an hour or two to throw something together but hopefully this guide helps.

Not all secrets are “secret”

Not all environment variables need to be hidden so I split them up into ones that are secret and ones that aren’t. The ones that are OK to check in stay in app.yaml:

runtime: nodejs10
instance_class: F2
env_variables:
NODE_ENV: "development"
SOME_NON_SECRET_THING: "project name or something"

Real secrets go to Secret Manager

I used the GCP UI and added a secret with the name STRIPE_SECRET_KEY (for example) and the secret value.

Access them from your app

I’m using node/typescript here, but you can use whatever you want.

yarn add @google-cloud/secret-manager

Then make a little secret service (heh, heh):

And access them like this:

import { getSecret } from './secrets';getSecret("SOME_ENV_VAR").then(k => ...);

I considered making this more generic so all environment variables go through this, not only real secrets, but I actually decided to keep it separate because I want developers to critically think about whether they’re using something secret or not. So if they want to access a variable that’s not secret, they can still use process.env. I may change my mind on this later.

Things to keep in mind

Keep in mind that you’re fetching this from a remote location so it’s going to be a Promise rather than a const like process.env and that you’re going to pay for access (see pricing link above) so be smart about caching.

Good luck! 🎉

--

--

Igor Belagorudsky
FastCTO
Editor for

Igor is an entrepreneur, angel investor, CTO, and startup mentor and advisor living in Boston, MA. He’s also the President of FastCTO.