Search code examples
dockernginxgatsbytraefik

Gatsby: Environment variables .env return undefined


I am working on a static website using Gatsby for the development and Nginx for serving the static files.

I am also using Docker for the deployment to test and production and Traefik for routing traffic to the docker container of the application.

I have an environment variable which I defined in the application file, and that environment variable is called from a .env file in the root folder of the application.

However, when that environment variable is invoked in the application, it throws an error:

undefined

Here's the code:

Dockerfile

# Set base image
FROM node:latest AS builder

# Set working directory
WORKDIR /app

# Copy package.json and install packages
COPY package.json .
RUN npm install

# Copy other project files and build
COPY . ./
RUN npm run build

# Set nginx image
FROM nginx:latest

# Nginx config
RUN rm -rf /etc/nginx/conf.d/default.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

# Static build
COPY --from=builder /app/public /usr/share/nginx/html

# Set working directory
WORKDIR /usr/share/nginx/html

# Start Nginx server
CMD ["/bin/bash", "-c", "nginx -g \"daemon off;\""]

.env

GATSBY_API_URL=https://myapi.mywebsite.com

docker-compose.yml

version: "3"

services:
  web:
    image: my-website
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      GATSBY_API_URL: ${GATSBY_API_URL}
    expose:
      - "80"
    labels:
      - traefik.enable=true
      - traefik.http.routers.my-website.rule=Host(`my-website.com`)
    restart: always
    volumes:
      - .:/app
networks:
  default:
    external:
      name: traefik-proxy

index.js

const onSubmit = async (values) => {
        try {
            const res = await axios.post(`${process.env.GATSBY_API_URL}/api/EmployeeDetail/verify`, values)
            // console.log(res, 'verify endpoint');
            if( res.data.requestSuccessful === true ) {
                dispatchVerifyData({ 
                    type : 'UPDATE_VERIFY_DATA', 
                    verifyData: {
                        res: res.data.responseData,
                        loanType: values.loanType
                    }
                })
                handleNext()
            } else {
                setIsSuccessful({
                    status: false,
                    message: res.data.message
                })
            }            

        } catch (error) {
            //error state Unsuccessful 
            console.log(error, 'error')
            setIsSuccessful({
                status: false,
            })
        }

    }

.dockerignore

node_modules
npm-debug.log
.DS_Store
.bin
.git
.gitignore
.bundleignore
.bundle
.byebug_history
.rspec
tmp
log
test
config/deploy
public/packs
public/packs-test
yarn-error.log
coverage/
.env
.env.production

Nginx default.conf

server {
  listen 80;
  add_header Cache-Control no-cache;
  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
    expires -1;
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

I can't still seem to tell what is the cause of the issue that causes the application to return an undefined error whenever the environment variable is invoked. Any form of help will be highly appreciated.


Solution

  • I finally figured it after some long hours of debugging with my colleagues.

    Here are a few things I learnt:

    Firstly, by default, Gatsby supports 2 environments:

    1. Development. If you run gatsby develop, then you will be in the development environment.
    2. Production. If you run gatsby build or gatsby serve, then you will be in the production environment.

    If you note, however, we are running npm run build in our Dockerfile which is equivalent to gatsby build, so this informs the application that we are running in the production. environment.

    Secondly, defining Environment Variables for Client-side JavaScript

    For Project Env Vars that you want to access in client-side browser JavaScript, you can define an environment config file, .env.development and/or .env.production, in your root folder. Depending on your active environment, the correct one will be found and its values embedded as environment variables in the browser JavaScript.

    In otherwords, we will need to rename our environment config file from .env to .env.production to allow the Gatsby application to recognize it in our production environment.

    Thirdly, defining Environment Variables using prefixes

    In addition to these Project Environment Variables defined in .env.* files, you could also define OS Env Vars. OS Env Vars which are prefixed with GATSBY_ will become available in browser JavaScript.

    If you note very well we are already defining this in our .env config file as - GATSBY_API_URL=https://myapi.mywebsite.com, so we have no issues with that.

    Fourthly, removing the env. config files from .dockerignore

    If we observe clearly how the values of environment variables are embedded in the browser Javascript for Client-side JavaScript, you will see that it is done during build time and not run time.

    Therefore, we need to remove the .env.* config files from .dockerignore and also remove the environment option in the docker-compose.yml file, since it is unnecessary anymore because we don't embed the values of the environment variables during the run time.

    So our code will look like this now:

    Dockerfile

    # Set base image
    FROM node:latest AS builder
    
    # Set working directory
    WORKDIR /app
    
    # Copy package.json and install packages
    COPY package.json .
    RUN npm install
    
    # Copy other project files and build
    COPY . ./
    RUN npm run build
    
    # Set nginx image
    FROM nginx:latest
    
    # Nginx config
    RUN rm -rf /etc/nginx/conf.d/default.conf
    COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
    
    # Static build
    COPY --from=builder /app/public /usr/share/nginx/html
    
    # Set working directory
    WORKDIR /usr/share/nginx/html
    
    # Start Nginx server
    CMD ["/bin/bash", "-c", "nginx -g \"daemon off;\""]
    

    .env.production

    GATSBY_API_URL=https://myapi.mywebsite.com
    

    docker-compose.yml

    version: "3"
    
    services:
      web:
        image: my-website
        build:
          context: .
          dockerfile: Dockerfile
        expose:
          - "80"
        labels:
          - traefik.enable=true
          - traefik.http.routers.my-website.rule=Host(`my-website.com`)
        restart: always
        volumes:
          - .:/app
    networks:
      default:
        external:
          name: traefik-proxy
    

    index.js

    const onSubmit = async (values) => {
            try {
                const res = await axios.post(`${process.env.GATSBY_API_URL}/api/EmployeeDetail/verify`, values)
                // console.log(res, 'verify endpoint');
                if( res.data.requestSuccessful === true ) {
                    dispatchVerifyData({ 
                        type : 'UPDATE_VERIFY_DATA', 
                        verifyData: {
                            res: res.data.responseData,
                            loanType: values.loanType
                        }
                    })
                    handleNext()
                } else {
                    setIsSuccessful({
                        status: false,
                        message: res.data.message
                    })
                }            
    
            } catch (error) {
                //error state Unsuccessful 
                console.log(error, 'error')
                setIsSuccessful({
                    status: false,
                })
            }
    
        }
    

    .dockerignore

    node_modules
    npm-debug.log
    .DS_Store
    .bin
    .git
    .gitignore
    .bundleignore
    .bundle
    .byebug_history
    .rspec
    tmp
    log
    test
    config/deploy
    public/packs
    public/packs-test
    yarn-error.log
    coverage/
    

    Nginx default.conf

    server {
      listen 80;
      add_header Cache-Control no-cache;
      location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
        expires -1;
      }
      error_page   500 502 503 504  /50x.html;
      location = /50x.html {
        root   /usr/share/nginx/html;
      }
    }
    

    That's all.

    I hope this helps