104

I have multiple environments (dev, qa, prod) and I'm using .env files to store secrets etc... Now I'm switching to GitHub Actions, and I want to use my .env files and declare them into the env section of the github actions yml.

But from what I've seen so far, it seems that I can not set a file path and I have to manually re-declare all variables.

How should I proceed as best practice?

HRK44
  • 2,382
  • 2
  • 13
  • 30
  • 3
    Hi OP! Any luck on this? I'm facing the same issue with Github Actions as well -- trying to figure out a way to somehow store secret variables by different environments. – Vic May 22 '20 at 02:29
  • 1
    Basically you have to manually copy the content of the respective .env files (say `.env.stage`, `.env.production`) into the respective GitHub Actions secret variables (say `WEBSITE_ENV_STAGE`, `WEBSITE_ENV_PRODUCTION`). Then at your GitHub Actions workflow script create the `.env` file from the desired variable like this `echo "${{ secrets.WEBSITE_ENV_STAGE }}" > .env` and use it in the workflow. – Valentine Shi May 15 '21 at 03:40
  • I have 2 more ways for you to use `.env` files in your project. See [my answer below](https://stackoverflow.com/a/67543246/6597265). – Valentine Shi May 15 '21 at 04:43

12 Answers12

191

A quick solution here could be having a step to manually create the .env file before you need it.

      - name: 'Create env file'
        run: |
          touch .env
          echo API_ENDPOINT="https://xxx.execute-api.us-west-2.amazonaws.com" >> .env
          echo API_KEY=${{ secrets.API_KEY }} >> .env
          cat .env

Better method for multiple variables

If you have a lot of env variables simply paste the whole file into a github secret named ENV_FILE and just echo the whole file.:

      - name: 'Create env file'
        run: |
          echo "${{ secrets.ENV_FILE }}" > .env
DollarAkshay
  • 2,063
  • 1
  • 21
  • 39
sugarcane
  • 2,272
  • 1
  • 14
  • 21
  • it's more easy your solution. thank you so much – Jean-Paul Feb 28 '21 at 23:24
  • This is the simplest and the best solution. – zoran Aug 12 '21 at 20:44
  • 8
    We have the same solution but this is cumbersome, I have 36 env variables to edit. – aRtoo Aug 26 '21 at 04:04
  • 1
    Had to remove the extra > for it to work – wamae Sep 24 '21 at 12:57
  • 1
    When trying to use this solution I kept getting the following error: `line 3: unexpected EOF while looking for matching ``'. I was able to work around it by passing in the secrets separately. I posted the code below. – sgonzalez Feb 17 '22 at 23:16
  • Do I need to paste this step before 'Checkout the repo' or where? It seems for me the only solution was to upload my .env files to the repository - as even I created env file in this step it did not see in `npm run build` – Tivi Jun 10 '22 at 22:05
  • 2
    @aRtoo You can paste your entire env file in one secret named `ENV_FILE` and the just do `echo "${{ secrets.ENV_FILE }}" > .env`. I updated the answer with a slightly better approach – DollarAkshay Nov 20 '22 at 18:46
  • > Better method for multiple variables. -It's really practical for many variables. Thank you! – crazyoptimist Apr 19 '23 at 11:11
  • Can I put a yaml file in secrets and use the same echo to create the yaml file on runtime? – Victor Andres Aguirre Fernande May 25 '23 at 15:37
  • 2
    Caution: **Never use structured data as a secret**. Structured data can cause secret redaction within logs to fail, because redaction largely relies on finding an exact match for the specific secret value. For example, do not use a blob of JSON, XML, or YAML (or similar) to encapsulate a secret value, as this significantly reduces the probability the secrets will be properly redacted. Instead, create individual secrets for each sensitive value. ref: https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-secrets – The Fool Jun 10 '23 at 19:53
36

The easiest way to do this is to create the .env file as a github secret and then create the .env file in your action.
So step 1 is to create the .env files as a secret in github as a base64 encoded string:
openssl base64 -A -in qa.env -out qa.txt
or
cat qa.env | base64 -w 0 > qa.txt
Then in you action, you can do something like

- name: Do Something with env files
  env:
    QA_ENV_FILE: ${{ secrets.QA_ENV_FILE }}
    PROD_ENV_FILE: ${{ secrets.PROD_ENV_FILE }}
  run: |
    [ "$YOUR_ENVIRONMENT" = qa ] && echo $QA_ENV_FILE | base64 --decode > .env
    [ "$YOUR_ENVIRONMENT" = prod ] && echo $PROD_ENV_FILE | base64 --decode > .env

There are a number of ways for determining $YOUR_ENVIRONMENT but usually this can be extracted from the GITHUB_REF object. You applications should be able to read from the .env files as needed.

secure12
  • 153
  • 1
  • 6
  • Aren't Github secrets already encrypted? It seems like you're encrypting an encrypted file, and Github is already doing the decryption to read the secrets file. – Brettins Dec 22 '20 at 19:12
  • 7
    @Brettins the `.env` file here possibly contains many keys. The point of encoding them into a string is so you don't need to declare each and every one on github secrets. – munsu Aug 24 '21 at 00:46
  • 2
    @brian , this actually seems to be the perfect solution for my case, however im confused about the openssl command where do we need to run this command? – uneeb meer Dec 26 '21 at 19:23
  • @uneebmeer you can run that in your terminal to generate the base64 version string. – Zaffer Dec 28 '21 at 12:15
  • 1
    @munsu very nice solution. We don't need to set every env keys through your way. – Jeff Gu Kang Jun 24 '22 at 05:55
  • This may not be secure. Your secrets will be exposed in logs and such. *Redaction largely relies on finding an exact match for the specific secret value*. Meaning, if you decode it and use the values here and there, GitHub won't be able to detect tha. Because its will not match that encoded string. ref https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-secrets – The Fool Jun 10 '23 at 19:59
33

I would suggest 3 pretty simple ways to engage your .env file variables in the GitHub Actions workflow. They differ based on whether you store the file in your repository (the worst practice) or keep it out of it (the best practice).

  1. You keep your .env file in the repository:

  2. (simple, manual, annoying when update .env variables) You keep your file out of your repository:

    • You manually copy the content of the respective .env files (say .env.stage, .env.production) into the respective GitHub Actions secret variables (say WEBSITE_ENV_STAGE, WEBSITE_ENV_PRODUCTION).

    • Then at your GitHub Actions workflow script create the .env file from the desired variable like this echo "${{secrets.WEBSITE_ENV_STAGE }}" > .env and use it in the workflow.

  3. (a bit more involved though prepare it once, then change your .env variables at the local machine, then sync these at GitHub with one click) As in item 2 above, the file is out of the repository.

    • Now you use the GitHub Actions API to create or update the secrets. On your local machine in the dev environment you write the NodeJS script that calls the API endpoint and write the .env files to the desired GitHub Actions secret variable (say as above into WEBSITE_ENV_STAGE or to both stage and production variables at once);

This is pretty wide choice of ways to engage the .env files's variables in the workflow. Use any matching your preference and circumstances.

Just for information, there is the 4th way which engages some 3rd party services like Dotenv Vault or HasiCorp Vault (there are more of the kind) where you keep you secret variables to read these to create .env file at build time with your CI/CD pipeline. Read there for details.

Valentine Shi
  • 6,604
  • 4
  • 46
  • 46
  • 4
    Why was this down-votted? I find it a good answer. – julian Nov 28 '21 at 21:42
  • 2
    This is the best answer – Graham Billington Feb 15 '22 at 18:26
  • 5
    Keeping your .env in the repo is bad practice and a potential security issue, especially when having AWS credentials or any sort of cloud provider credentials. – Jeff Gruenbaum Jul 22 '22 at 13:46
  • 3
    @JeffGruenbaum I mentioned this explicitly in my answer. Despite putting `.env` files in VCS being the worst practce, it is used by many in reality. As well as many other worst practices. – Valentine Shi Jul 28 '22 at 12:23
  • 1
    Hi @ValentineShi, thanks for sharing this great answer with us. It inspired me to create my first action: https://github.com/marketplace/actions/next-env. It loads and provides .env variables using Next.js' @next/env package. This means it takes care of loading .env, .env.development, .env.development.local (and other files Next.js uses for environment variables) in your GitHub Action in the correct order (defined by Next.js). – natterstefan Aug 15 '22 at 10:23
  • How do I deploy to different environments from a release branch for production and main branch for dev? – chovy Feb 07 '23 at 11:52
  • can you provide the full workflow yaml? I donj't know where to put `echo "${{secrets.WEBSITE_ENV_STAGE }}" > .env` – chovy Feb 07 '23 at 12:35
  • @chovy You may use [`on`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) YAML command to run an Actions workflow on, say, some branch push. From there you can decide what steps your workflow should engage (where to deploy etc.). You can use multiple workflows from multiple git branches. To run bash scripts (e.g. to `echo` something somewhere) in the workflow use [`run`](https://docs.github.com/en/actions/learn-github-actions/essential-features-of-github-actions), Look at the [docs](https://docs.github.com/en/actions), they are great to my taste. – Valentine Shi Feb 07 '23 at 14:01
  • Please provide a full example. – chovy Feb 07 '23 at 14:07
  • @chovy I have no example handy. This is all pretty basic, you could infer what you need from the docs examples. You could find the examples in the other answers as well. – Valentine Shi Feb 07 '23 at 16:03
10

Edit: You were using Circleci Contexts, so with that you had a set of secrets of each env. I know they are working to bring secrets to org level, and maybe team level... there is no info if they will create sort of contexts like we have in CCI.

I have thought on adding the env as prefix of the secret name like STAGE_GITHUB_KEY or INTEGRATION_GITHUB_KEY using ${env}_GITHUB_KEY on the yml as a workaround for now... What do you think?

--- Original answer: If I understand you well, you already have the dotenv files stored somewhere and you want to inject all those secrets into the steps, without having to manually add them to github secrets and do the mapping in each workflow you migrate... right?

There is an action made by someone that reads a dotenv file and put its values into ouputs, so you can use them linked in further steps. Here is the link: https://github.com/marketplace/actions/dotenv-action

Whatever is present in the .env file will be converted into an output variable. For example .env file with content:

VERSION=1.0
AUTHOR=Mickey Mouse

You do:

id: dotenv
uses: ./.github/actions/dotenv-action

Then later you can refer to the alpine version like this ${{ steps.dotenv.outputs.version }}

Ayo
  • 101
  • 1
  • 7
  • 1
    where would be that .env file? Since it's not saved in the repo - which is the point of having a .env file – HRK44 May 11 '20 at 17:30
  • well, it looks like a common pattern (to have a .env file that is being read by a CI task -- see for example https://circleci.com/orbs/registry/orb/anilanar/dotenv). This answer looks like a good match for your question, IF you can supply a variable as the `path` of the task. – nilleb May 11 '20 at 19:40
  • @HRK44 With "and I want to use my .env files" I understand you had them already, I thought in S3 or something. If you have them and are looking for storing securely, you can store in S3 and use aws secret+key to download and add to the action. If not, you have to generate them printing github secrets into a file with sed replacing placeholders or doing a simple echo to the file, whatever you like. If not the case, please clarify your scenario. nilleb He is migrating from Circleci to Github, so the orb is not useful in this case – Ayo May 12 '20 at 08:21
  • @Ayo well in CircleCi we had the envs stored in CircleCi, we have multiple versions of the env (dev, qa, prod, etc...) but in GitHub actions we can store only one version of the secrets. Right now we are using Jenkins (which has the secrets stored) and depending on the build type, it will use dev/qa/prod secrets. How can we do this with Github Actions? – HRK44 May 12 '20 at 16:55
  • 1
    Ok I think I got you. You were using Circleci Contexts, so with that you had a set of secrets of each env. I know they are working to bring secrets to org level, and maybe team level... there is no info if they will create sort of contexts like we have in CCI. I have thought on adding the env as prefix of the secret name like STAGE_GITHUB_KEY or INTEGRATION_GITHUB_KEY using ${env}_GITHUB_KEY on the yml as a workaround for now... What do you think? – Ayo May 13 '20 at 18:19
  • @HRK44 If I helped you, do you mind selecting as correct answer? it would help me to start the profile here due to the "min reputation" limitations :-) thanks – Ayo May 20 '20 at 10:13
  • @Ayo brilliant!! thank you!! that workaround helped me :D – Vic May 22 '20 at 02:36
  • @Ayo update your answer with your comment and i'll accept it for now – HRK44 May 23 '20 at 11:49
  • @HRK44 done thank you :-) Im new in terms of posting answers – Ayo May 23 '20 at 15:13
8

You can also use a dedicated github action from github-marketplace to create .env files.

Example usage:

name: Create envfile

on: [push]

jobs:

  create-envfile:
 
    runs-on: ubuntu-18.04
 
    steps:
    - name: Make envfile
      uses: SpicyPizza/create-envfile@v1
      with:
        envkey_DEBUG: false
        envkey_SOME_API_KEY: "123456abcdef"
        envkey_SECRET_KEY: ${{ secrets.SECRET_KEY }}
        file_name: .env

Depending on your values defined for secrets in github repo, this will create a .env file like below:

DEBUG: false
SOME_API_KEY: "123456abcdef"
SECRET_KEY: password123

More info: https://github.com/marketplace/actions/create-env-file

ankur512512
  • 151
  • 1
  • 8
4

You can export all secrets to environment variables and do everything from a script.

I created an action exactly for that - takes all the secrets and exports them to environment variables.

An example would be:

- run: echo "Value of MY_SECRET1: $MY_SECRET1"
  env:
    MY_SECRET1: ${{ secrets.MY_SECRET1 }}
    MY_SECRET2: ${{ secrets.MY_SECRET2 }}
    MY_SECRET3: ${{ secrets.MY_SECRET3 }}
    MY_SECRET4: ${{ secrets.MY_SECRET4 }}
    MY_SECRET5: ${{ secrets.MY_SECRET5 }}
    MY_SECRET6: ${{ secrets.MY_SECRET6 }}
    ...

You could convert it to:

- uses: oNaiPs/secrets-to-env-action@v1
  with:
    secrets: ${{ toJSON(secrets) }}
- run: echo "Value of MY_SECRET1: $MY_SECRET1"

Link to the action, which contains more documentation about configuration: https://github.com/oNaiPs/secrets-to-env-action

oNaiPs
  • 551
  • 5
  • 14
  • Does this put the all the key:value pair from the env to json format? or this would return a "json: cannot unmarshal string into Go value of type kvflag.FlagJSON" – askb May 28 '23 at 02:16
  • works nicely for me, clean and simple – Courlu Aug 03 '23 at 10:54
3

Another alternative is to use the Environments feature from github. Although that isn't available on private repos in the free plan. You could have scoped variables, at repository, profile/organization level and environment. The configuration variables closer to the repository takes precedence over the others.

user3746240
  • 321
  • 2
  • 6
1

I tried using the accepted solution but GitHub actions were complaining about the shell commands. I kept getting this error: line 3: unexpected EOF while looking for matching ``'

Instead of referencing the secrets directly in the shell script, I had to pass them in separately.

  - name: Create env file
    run: |
      touch .env
      echo POSTGRES_USER=${POSTGRES_USER} >> .env
      echo POSTGRES_PASSWORD=${POSTGRES_PASSWORD} >> .env
      cat .env
    env: 
      POSTGRES_USER: ${{ secrets.POSTGRES_USER }} 
      POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} 
Salman
  • 892
  • 12
  • 13
sgonzalez
  • 741
  • 6
  • 20
0

I was having the same issue. What I wanted was to upload a .env file to my server instead of defining the env variables in my Github repo. Since I was not tracking my .env file so every time my workflow ran the .env file got deleted. So what I did was :

  1. Added the .env file in the project root directory in my server.
  2. Added clean: false under with key in my actions/checkout@v2 in my workflow

eg:

jobs:
  build:

    runs-on: self-hosted

    strategy:
      matrix:
        node-version: [14.x]
       
    - uses: actions/checkout@v2
      with: 
        clean: 'false'

This prevents git from deleting untracked files like .env. For more info see: actions/checkout

0

One more approach would be doing something as described in https://docs.github.com/en/actions/security-guides/encrypted-secrets#limits-for-secrets

So basically treating your .env file as a "large secret". In this case, the encrypted .env file is kept commited in your repo, which should be fine. Then in your action have a step to decrypt the .env file.

This removes the overhead of having to create each individual secret inside your .env as a Github secret. The only Github secret to maintain in this case, is one for the encryption password. If you have multiple .env files such as qa.env, prod.env, etc... I would strongly suggest using a different encryption password for each, and then store each encryption passwords as an "environment secret" in Github instead of "repo secret" (if using Github environments is your thing. See https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment).

If you don't want to commit the (encrypted) .env file in you repo, then I would go with the base64 approach described in https://stackoverflow.com/a/64452700/1806782 (which is simmilar to what's in https://docs.github.com/en/actions/security-guides/encrypted-secrets#storing-base64-binary-blobs-as-secrets) and then create a new Github secret to host the encoded contents.

For those like me with aversion to manual repetitive tasks, Github secret creation can these days easily be scripted with the Github CLI tool. See https://cli.github.com/manual/gh_secret_set . It also supports 'batch' creation of secrets from env files (see the -f, --env-file flags)

donhector
  • 875
  • 1
  • 10
  • 21
0

inspired by Valentine Shis answer above, I created a GitHub Action for this use-case and the one I had at the time while reading this thread.

GitHub Action: next-env

GitHub Action to read .env.[development|test|production][.local] files in Next.js (but also non Next.js) projects and add variables as secrets to GITHUB_ENV.

Despite the name, it also works in non-Next.js projects as it uses a decoupled package of the Next ecosystem.

natterstefan
  • 433
  • 4
  • 8
  • 21
-4

You need to define your environment variables in "Secrets" section of your repository. Then you can simply use your secrets in your workflow.

Example usage:

- uses: some-action@v1
  env:
    API_KEY: ${{ secrets.API_KEY }}
    SECRET_ID: ${{ secrets.SECRET_ID }}
  with:
    password: ${{ secrets.MY_PASSWORD }}

Here is the documentation:

https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets

fabasoad
  • 1,613
  • 18
  • 20
  • 3
    This doesn't answer my question, I'm talking about how to handle in the best way multiple environments with this system (dev, qa, stage, prod, etc...) ? – HRK44 Mar 26 '20 at 14:31