3

To be brief, I want to build a container in docker with a web project which configuration is modifiable depending on a parameter that is passed to it later when running the image in Docker.

This project tries to read a file call "environment.json" with the custom properties.

My Dockerfile is this:

# NodeJS
FROM node:13.12.0-alpine as build

WORKDIR /app

ENV PATH /app/node_modules/.bin:$PATH

COPY dist /app/dist
COPY define-env.js /app
COPY environments.json /app

# I have changed this with a lot of possible solutions 
# that I have found in Internet, I tried everything.
# ARG APP_ENV
# ENV ENVIRONMENT $APP_ENV
RUN node define-env.js ${APP_ENV}

# NGINX
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

What I am doing is copying both: built basic web project, a node script responsible of writing the correct environment and a JSON file with all the possible configuration environments.

The Node script is this:

#!/usr/bin/env node

// we will need file system module to write the values
var fs = require("fs");
// this script is going to receive one parameter (see DockerFile)
var option = process.argv[2];
// taking all the possible configurations
var json = require("./environments.json");
// taking the chosen option (by default dev)
var environmentCfg = json[option] || json.dev;

// writing... It is important to do this task sync,
// because we need to be sure is finished before any step
fs.writeFileSync("./dist/environment.json", JSON.stringify(environmentCfg));

And the environments.json is something like this:

{
  "dev": {
    "title": "This is the dev environment",
    "anotherAttribute": "Hello dev, I was configured with Docker!"
  },
  "uat": {
    "title": "This is the uat environment",
    "anotherAttribute": "Hello uat, I was configured with Docker!"
  },
  "prod": {
    "title": "This is the prod environment",
    "anotherAttribute": "Hello prod, I was configured with Docker!"
  }
}

I do not know how to pass the variable when I run my Docker image, I am trying this:

docker build . -t docker-env

and then, once I have created my image I try to run it using this command:

docker run -e APP_ENV=uat -it --rm -p 1337:80 docker-env

When I go to the project I see the "dev" configuration always.

I have checked removing from DockerFile the NGINX configuration and I can see its working fine when I put the parameters.

I think something weird (or I do not know) is happening when I change the baseImage from Node to Nginx.

EDIT: as @babakabadkheir suggested in the comments, I have tried this following approach, but it is still failing:

FROM hoosin/alpine-nginx-nodejs:latest
WORKDIR /app

ENV PATH /app/node_modules/.bin:$PATH

COPY dist /app/dist
COPY define-env.js /app
COPY environments.json /app

RUN node define-env.js ${APP_ENV}
RUN mv ./dist/* /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

If I take a look to the mounted image shell I can see it has put the environment.json as well:

enter image description here

EDIT 2:

I have modified my Dockerfile like this but it is still not working:

# I am taking a base image with NodeJS and Nginx
FROM hoosin/alpine-nginx-nodejs:latest
# Telling Docker I like to work in the app workdir
WORKDIR /app
# Copying all files I need
COPY dist /app/dist
COPY define-env.js /app
COPY environments.json /app

# My environment variable for runtime purposes (dev by devfault)
ENV APP_ENV dev
# This instruction I think is failing, but when I swap it with
# CMD instead RUN and put a console.log works without troubles
RUN node define-env.js 

# Once I have all the files I move it to the Nginx workspace
RUN mv ./dist /usr/share/nginx/html

# Setup Nginx server
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Now my Node script looks like:

#!/usr/bin/env node

var fs = require("fs");
// NOTE: this line is already taken via environment 
var option = process.env.APP_ENV;

var json = require("./environments.json");
var environmentCfg = json[option] || json.dev;
fs.writeFileSync("./dist/environment.json", JSON.stringify(environmentCfg));

LAST EDIT! Finally I have it!

the solution was put all my needs in the CMD Docker instruction:

CMD node define-env && mv ./dist/* /usr/share/nginx/html && nginx -g 'daemon off;'
mryuso92
  • 267
  • 4
  • 21

2 Answers2

1

You made two mistakes: mixed ENV with ARGS and a syntax error.

When you define an ARG you can pass a value when build the image, if not specified the default value is used; then read the value on Dockerfile without the braces: (Note: I used slightly different docker images)

Dockerfile

FROM node:12-alpine as build

# Note: I specified a default value
ARG APP_ENV=dev
WORKDIR /app

ENV PATH /app/node_modules/.bin:$PATH

COPY dist /app/dist
COPY define-env.js /app
COPY environments.json /app

# Note $APP_ENV and NOT ${APP_ENV} without braces
RUN node define-env.js $APP_ENV

# NGINX
FROM nginx:1.19
COPY --from=build /app/dist /usr/share/nginx/html
# Not necessary to specify port and CMD

Now for build the image you must specify the APP_ENV argument value

Build

docker build --build-arg APP_ENV=uat -t docker-uat .

Run

docker run -d -p 8888:80 docker-uat

;TL,DR

Your request is to specify the configuration at runtime, this problem has two solutions:

  1. Use ENV var when running container

  2. Use ARGS when building container

First solution has a drawback of security but the advantage of distributing a single image: you copy the entire file from host to container and when running the container you passing the correct ENV value.

Second solution is slightly more complex do not has the security problem but you must build separate images each for each config.

The second solution is the already written answer, for the first solution you must simply read the env variable at runtime

Dockerfile

# NodeJS
FROM node:12-alpine as build

WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH

COPY dist /app/dist
COPY environments.json /app/dist

# NGINX
FROM nginx:1.19

# Define the environment variable
ENV APP_ENV=dev
COPY --from=build /app/dist /usr/share/nginx/html

Build

docker build -t docker-generic .

Run

docker run -d -p 8888:80 -e APP_ENV=uat docker-generic

Code

# On your nodejs code you simply read the ENV value
// ....
var env = process.env.APP_ENV

References

Dockerfile ARG
Docker build ARG

Dockerfile ENV
Docker run ENV

Max
  • 6,821
  • 3
  • 43
  • 59
  • I beg to differ, but this is not mandatory `When you define an ARG you must pass the value when build the image`. You can use the default value – Vivek Bani Dec 07 '20 at 01:48
  • 2
    @Max I have upvoted your answer because you explain pretty well the differences between ARG and ENV, but your solution does not work for me (look the edit 2 solution I have developed) – mryuso92 Dec 10 '20 at 16:49
1

The solution for this it is to put all the things that are modifiable at runtime in the CMD instruction.

As I have experienced, RUN only works first time during build time, but CMD does not.

So I have put in my DockerFile:

CMD node define-env && mv ./dist/* /usr/share/nginx/html && nginx -g 'daemon off;'

I run my script, I move all the files and then serve my application.

I hope help other developers with this, because for me has been a pain.

TL;DR

In summary, the Dockerfile should be something like this:

# I am taking a base image with NodeJS and Nginx
FROM hoosin/alpine-nginx-nodejs:latest
# Telling Docker I like to work in the app workdir
WORKDIR /app
# Copying all files I need
COPY dist /app/dist
COPY define-env.js /app
COPY environments.json /app

# My environment variable for runtime purposes (dev by devfault)
ENV APP_ENV dev

# Setup Nginx server
EXPOSE 80
CMD node define-env && mv ./dist/* /usr/share/nginx/html && nginx -g 'daemon off;'
mryuso92
  • 267
  • 4
  • 21