serverless-next.js icon indicating copy to clipboard operation
serverless-next.js copied to clipboard

Use environment variables

Open dhmacs opened this issue 6 years ago • 37 comments
trafficstars

How can I pass environment variables? I would like to pass some api key and secrets both to api and frontend lambdas, how can I do that?

I tried like this but it doesn't work:

# serverless.yml
myApp:
  component: serverless-next.js
  inputs:
    env:
      API_KEY: demo

dhmacs avatar Oct 07 '19 12:10 dhmacs

@macs91 Use next's build time configuration . This is the recommended approach for managing environment config. in your pages.

danielcondemarin avatar Oct 07 '19 12:10 danielcondemarin

@danielcondemarin I tried it, but I see that it leaks env variables to the client... Given that I intend to store secrets I can't use that 😬

dhmacs avatar Oct 07 '19 12:10 dhmacs

@danielcondemarin I tried it, but I see that it leaks env variables to the client... Given that I intend to store secrets I can't use that 😬

Ohh I see. Sorry I wasn't entirely clear how build time env. works. Sounds like there is scope then for injecting env. variables via serverless.yaml. Reopening ...

danielcondemarin avatar Oct 07 '19 13:10 danielcondemarin

@macs91 Could you share how you're accessing the env. config. in the page? i.e. on getInitialProps I'm guessing?

danielcondemarin avatar Oct 07 '19 13:10 danielcondemarin

Yes, the secret will be accessed from getInitialProps.. I dug a bit more, and it seems that only the env variables that are explicitly referenced on client side code are visible in the bundle, so basically env variables are not exposed unless they are explicitly included in client side code?

So If I don't want the env variable to get included in the bundle maybe it's enough to use it under this condition:

if (!process.browser) {
   console.log("my secret", process.env.SECRET);
}

Ok so maybe this way it can work, but we have to be careful to make sure the env variable are not referenced inside the client bundle.

dhmacs avatar Oct 07 '19 14:10 dhmacs

@macs91 Sounds incredibly easy to leak the secure config. by accident., so I'll be adding support for runtime only env. configuration via a serverless.yml input. Stay tuned 😄

danielcondemarin avatar Oct 07 '19 14:10 danielcondemarin

Yep I thought that too 😅 When env variables are set only on the lambda it's much harder to leak secrets by mistake. Thanks 👍

dhmacs avatar Oct 07 '19 14:10 dhmacs

@macs91 Found out today Lambda@Edge doesn't support environment variables :/

Hopefully this will be supported / available soon! But for now you'll have to workaround it by using the build time config. with care. I'll keep the issue opened.

danielcondemarin avatar Oct 07 '19 20:10 danielcondemarin

Bummer! I didn't know that.. Hopefully they will announce support at re:Invent 😁

dhmacs avatar Oct 07 '19 20:10 dhmacs

Bummer! I didn't know that.. Hopefully they will announce support at re:Invent 😁

That's what I'm hoping. All I can do for now is ask for it 🙂

danielcondemarin avatar Oct 07 '19 20:10 danielcondemarin

Oh no... is there still no way to do this? I used NextJS and this component to handle a simple contact form but I'd like to not leak the email address the form sends the submission to.... Anyway to handle this? If not then this has practically killed my app with this dead in the water :(

gbwashleybrown avatar Feb 25 '20 11:02 gbwashleybrown

@gbwashleybrown Did you have a look at build.env ? https://github.com/danielcondemarin/serverless-next.js#inputs It uses next's build time env variables as documented in https://nextjs.org/docs/api-reference/next.config.js/environment-variables

danielcondemarin avatar Feb 25 '20 11:02 danielcondemarin

@danielcondemarin According to the conversation further up this post, doing that will leak values into the client? So I would prefer not to do that as I don't need to access them in the client.

My serverless function loops trough an array of email addresses and picks one to send the contact form submission depending on some stuff. So being able to access all the email addresses on the server side only without exposing them to the client is key for me to be able to use this in a production environment.😕

gbwashleybrown avatar Feb 25 '20 11:02 gbwashleybrown

@gbwashleybrown The safest way to do this is to create a handler in a /api route then POST/GET to that handler to loop your array etc, /api routes are not exposed to the client as they are a simple request/response and always run on the server side, you may find using this that you don't need process.env vars at all,

Take a look at https://nextjs.org/docs/api-routes/introduction

And also a rest example here: https://github.com/zeit/next.js/tree/canary/examples/api-routes-rest

Podders avatar Feb 25 '20 15:02 Podders

@Podders Thank you.

I think I may have misunderstood how Next is bundled.

The email array and sendmail method is within /pages/api/sendmail.js and I was under the impression that /api/* is exposed to the client.

For as long as server-side functions (/pages/api/*) files aren't exposed then I should be ok. 😁

gbwashleybrown avatar Feb 25 '20 15:02 gbwashleybrown

I found this issue when I had a lambda function (created with '@serverless/backend') that would be deployed along side my next.js app and I wanted to expose an environment variable to the next.js app during build time that was equal to the url of the newly deployed lambda function.

my solution was as follows

// serverless.yml
api:
  component: '@serverless/backend'
  inputs:
    code:
      src: api/src
    env:
      dbName: ${database.name}
      dbRegion: ${database.region}

frontend:
  component: "serverless-next.js"
  inputs:
    domain: "yourdomain.com"
    build:
      env:
        API_URL: ${api.url}

// next.config.js
module.exports = {
    target: "serverless",
    env: {
        API_URL: process.env.API_URL
    }
}
// pages/index.js
export async function getStaticProps() {
  const API_URL = process.env.API_URL
  return {
    props: {
      API_URL
    }
  }
}

bobrown101 avatar Mar 11 '20 18:03 bobrown101

[CORRECTION] Adding my env variables in the next.config.js file suffuced:

module.exports = {
  env: {
    //env variables here
  }
}

godson-ikhokha avatar Apr 22 '20 12:04 godson-ikhokha

What we ended up doing was using AWS Secrets Manager, since you can request from Lambda@Edge. Downside is having to make a region-specific request to get the secrets from the Lambda, but works as a stop-gap for now.

nicovalencia avatar Jul 23 '20 18:07 nicovalencia

@gbwashleybrown Did you have a look at build.env ? https://github.com/danielcondemarin/serverless-next.js#inputs It uses next's build time env variables as documented in https://nextjs.org/docs/api-reference/next.config.js/environment-variables

Is this working? I have been trying this method and it doesn't seem like build.env does anything at all...

andrew310 avatar Sep 02 '20 19:09 andrew310

Maybe I am misunderstanding how this works, so far in order to set a env variable I used:

appName:
  component: '@sls-next/[email protected]'
  build:
    env:
      NEXTAUTH_URL: https://example.cloudfront.net

Say NEXTAUTH_URL thought is not being set at all, as in for this specific example is not fetched by next-auth, what am I missing?

EDIT:

Of course the following would work:

// next.config.js
module.exports = {
  env: {
      NEXTAUTH_URL: https://example.cloudfront.net
  }
}

But wouldn't that both expose the env variable to the bundle and also to VCS? How can we avoid that?

MelMacaluso avatar Sep 04 '20 10:09 MelMacaluso

@MelMacaluso I had the same problem. I am also using next-auth. Look at the solution by @bobrown101. That worked for me. build.env must go under inputs. So the documentation is slightly off.

andrew310 avatar Sep 04 '20 20:09 andrew310

@andrew310 thanks mate! Will try out and let you know 🙏

MelMacaluso avatar Sep 05 '20 07:09 MelMacaluso

Actually the only thing that works for me is defining the envs in the next.config.js which is not ideal as is commited...if anyone has some other solutions would be greatly appreciated 🙏

MelMacaluso avatar Sep 08 '20 09:09 MelMacaluso

Actually I am facing issue to maintain the deployment process with the same build which I created for my DEV environment. As you know we need to deploy the same code on different environments(DEV, UAT & PROD), and have different endpoints for each environment. So I want to deploy the build which I created for DEV environment to UAT & PROD, but how can I change the end-point without making the new build of nextjs code?

Diljeetsinghsuri avatar Sep 22 '20 10:09 Diljeetsinghsuri

Below is how I was able to include environment variables without needing to commit them to source (assumes you're using GitHub actions to deploy your app).

Tradeoffs of this approach:

  • ❗❗❗ As others have mentioned, there's nothing to stop you from sending secret environment variables down to the client, so you have to be careful
  • You have to remember to add all of your secrets to multiple places 😐 (GitHub secrets, GitHub workflow file, serverless yaml, next.config.js)
  • This will quickly get annoying when working with different environments that need different values. In fact, you can only have 100 secrets per GitHub workflow, so this approach doesn't scale well with multiple environments

1. Store environment variables as secrets in your GitHub project's "Secrets" section

This will be at https://github.com/<username>/<project>/settings/secrets

image

2. Have your GitHub workflow include the variables during the build process

Assuming your GitHub workflow looks something like this one, include your desired environment variables in the step where the app is built and deployed

      - name: Deploy to AWS
        run: npx serverless
        env:
          MY_SECRET_API_KEY: ${{ secrets.MY_SECRET_API_KEY }}

3. In your serverless yml, include the variables under build.env (again assumes a structure somewhat like this one))

myApp:
  component: serverless-next.js
  inputs:
    ...
    build:
      env:
        MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}

4. In your next.config.js, add the variables you want included

module.exports = {
  target: 'serverless',
  env: {
    MY_SECRET_API_KEY: process.env.MY_SECRET_API_KEY
  }
};

... and that's pretty much it. This isn't a great solution because of the tradeoffs already mentioned, and if you have the time to sink into figuring out how to get AWS Secret Manager working, that would probably be a better workaround. It's unfortunate that there's no easy way to configure serverless next.js without committing your env vars to source as that's an anti-pattern.

murtyjones avatar Oct 30 '20 02:10 murtyjones

Just a note from me. I was also struggling with the same issue and @murtyjones solution just did not work for me. The problem seems to be step 3 where I needed to change

myApp:
  component: '@sls-next/[email protected]'
  inputs:
    ...
    build:
      env:
        MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}

to

myApp:
  component: '@sls-next/[email protected]'
  inputs:
    ...
    env:
      MY_SECRET_API_KEY: ${env.MY_SECRET_API_KEY}

So I needed to remove build nesting for env in serverless.yml file.

Best regards

PaulKushch avatar Nov 17 '20 08:11 PaulKushch

@murtyjones Your comment "figuring out how to get AWS Secret Manager working, that would probably be a better workaround," -- Makes me believe I am pushing into a rope trying to use AWS Secrets Manager with nextjs to handle both oAuth2 secrets and DB passwords. I think I should stop trying an concede to the anti-pattern...

nocturnalcodehack avatar Nov 24 '20 20:11 nocturnalcodehack

@murtyjones Your comment "figuring out how to get AWS Secret Manager working, that would probably be a better workaround," -- Makes me believe I am pushing into a rope trying to use AWS Secrets Manager with nextjs to handle both oAuth2 secrets and DB passwords. I think I should stop trying an concede to the anti-pattern...

yeah, it's definitely doable but it takes some effort. i've also played around with AWS Systems Manager, which looks like it might be easier to use with AWS Lambda than the Secrets Manager, but I haven't tried that yet so not sure if there are any gotchas.

this is one of those things where someone who publishes a blog post guide will rake in the traffic 😄

murtyjones avatar Nov 24 '20 20:11 murtyjones

Environment variables are almost always needed with the backend (secret keys specifically), so everyone is going to be facing this same problem - which I suspect is a heck of a lot people? Why can't we just make this none Lambda@Edge and just use normal Lambda instead? I would to help, if I can, but I'm not particularly great at writing things like this and don't really know the codebase too well.

With that said, I'm having to store secrets in /api/* files directly, rather than an .env file. This is fine for now, but obviously not ideal as it gets saved in VCS and as you know, it's just a really insecure way of storing your code.

gbwashleybrown avatar Dec 01 '20 05:12 gbwashleybrown

The approach I've gone with for a multi-env i18n Next.js site is to set a BUILD_ENV variable which is used to determine which environment variables to load in. So my serverless.yml file looks like:

myNextApp:
    component: '@sls-next/[email protected]'
    build:
        env:
            BUILD_ENV: ${env.BUILD_ENV}
    inputs:
        timeout: 30
        build:
            postBuildCommands: ['node serverless-post-build.js', 'node sitemap.xml.js']
    memory: 1024

Then in the root of the repo I've got a environments folder containing .env.localhost, .env.develop and .env.main:

These files look something like:

NEXT_PUBLIC_BUILD_ENV=develop
NEXT_PUBLIC_AUTH_DOMAIN=auth.staging.yoursite.com
NEXT_PUBLIC_SITE_URL=https://yoursite.cloudfront.net/

In my next.config.js file I include the necessaray environment variables with dotenv:

require('dotenv').config({
	path: `environments/.env.${process.env.BUILD_ENV || 'localhost'}`,
});

If you've got additional post-build scripts as I have then you'll need to include the above snippet to i.e in sitemap.xml.js

Then all that is left to do is run:

BUILD_ENV=develop npx serverless

kylekirkby avatar Dec 08 '20 12:12 kylekirkby