Publish same Docker image with Angular application to any environments

In this story I would share how to deliver same docker image with angular application inside to any environments.

First of all, we have to create a new application or get an existing one.

By default angular use environment.ts to store configurations for different environment, such as url of backend api. But if we use this approach we have to do different build for each environment (like testing, staging, prod, etc). To avoid multiple build we can use the approach explained in this article.

As mention before, we can’t use environment.ts to store different values per environments, so let’s create a new file under assets folder called config.json (you can call it whatever you want, but must be a json):

{
"api": "https://local-api.com"
}

Then angular have to get this configuration, to do that I used ReplaySubject in a service. So create new service called config.service.ts and a new model for config property:

Now let’s call the loadConfig() method on app component and show the model on view:

Now if we run the application we will see the api url on page:

app component

Now we have to create multiple configurations, one for each environment that you want to manage. For example I created under assets a folder named _environments:

config.json files must have the proper configuration of corresponding environment.

Example for Staging:

{
"api": "https://staging-api.com"
}

Example for Production:

{
"api": "https://production-api.com"
}

First of all we have to create a new bash file that replace the config file under assets with proper one, based on environment variable (called ENVIRONMENT).

So create new file called entrypoint.sh

cp /usr/share/nginx/html/assets/_environments/${ENVIRONMENT}/config.json /usr/share/nginx/html/assets//usr/sbin/nginx -g "daemon off;"

If you are running on windows machine, pay attention of format of file on bottom right of vscode editor:

After that, we can create new Dockerfile file on the root as follow:

### STAGE 1: Build ###
FROM
node:lts-stretch-slim as builder
COPY package.json package-lock.json ./
## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
RUN
npm ci && mkdir /app && cp -R ./node_modules ./app
WORKDIR /app
COPY . .
## Build the angular app in production mode and store the artifacts in dist folder
RUN
$(npm bin)/ng build --prod
### STAGE 2: Setup ###
FROM
nginx:stable
## Copy our default nginx config
COPY
nginx.conf /etc/nginx/conf.d/default.conf
## Remove default nginx website
RUN
rm -rf /usr/share/nginx/html/*
## From 'builder' stage copy over the artifacts in dist folder to default nginx public folder
COPY
--from=builder /app/dist/angular-docker /usr/share/nginx/html
COPY ["entrypoint.sh", "/entrypoint.sh"]
CMD ["sh", "/entrypoint.sh"]

Let me explain that lines:

  1. STAGE 1: get image of node and name it as builder;
  2. Copy package.json package-lock.json on the root of docker image;
  3. Run npm ci to install dependencies using package-lock.json file;
  4. Copy all source files to app folder inside docker;
  5. Run angular command to build the application in production mode;
  6. STAGE 2: get image from dockerhub nginx:stable;
  7. Copy nginx configuration in default configuration inside nginx server folder;
  8. Copy from previous image (alias builder) the compiled files and put it in root folder of nginx server;
  9. Copy entrypoint.sh to root folder inside docker;
  10. Tell docker to run entrypoint.sh during startup.

In terminal run this command:

docker build -t angular-docker .

to build the image and name it as angular-docker.

Finally, you can run this command to test the application:

docker run -e ENVIRONMENT=Staging -p 5555:80 -it angular-docker:latest

and if you now go to http://localhost:5555 you will see the result:

Now if try another command:

docker run -e ENVIRONMENT=Production -p 5556:80 -it angular-docker:latest

you will see correct result:

So now you are able to deploy your docker image in any environment, you just have to set the environment variable ENVIRONMENT and the docker image take care of replacing the proper file of configuration.

If you want to check this example, this is the link of repository:

Thanks for reading! 😃

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store