Handling multiple Angular environments with Docker and Nginx
Tags: docker, nginx, angular, environments
Published: 2021-11-29 13:16:35

#Introduction
Before we get started, here is a quick summary of what this post is about. Imagine you have an angular project with multiple environments, and for each environment you will need to have separate Nginx templates/configs, separate Dockerfiles and, on top of that, more often than not your environmental variables will be duplicated. If this is something you would like to improve, then you are in the right place. 🙂 Setting up dockerfiles and nginx configurations is not in the scope of this article, so keep in mind that some knowledge of docker and nginx is required, as we will not dive into these topics.


## Getting started

####Project structure
```text
angularapp
├── config
│   ├── nginx
│   │   ├── default.conf.template
│   │   └── start-nginx.sh
│   ├── ssl
│   │   ├── cert-name.crt
│   │   └── cert-name.key
│   ├── tools
│   │   └── set-env.ts
│   └── Dockerfile
├── docker-compose.yml
├── package.json
└── src
...
```

### - Dockerfile
Inside our dockerfile, beside installing dependencies and building the angular project, we will `RUN`:

```cmd
npm run set-env
```
which will substitute `environment.prod.ts` with a new file where we define **apiUrl** and **production** variables.

```cmd
sh -c usr/bin/start-nginx.sh
```
which will substitute from `default.conf.template` **NGINX_SSL_CERT_PATH** variable and replace the nginx default config.


```Dockerfile
#####################
### Angular/Nginx ###
#####################


# ---------------- ANGULAR SETUP START ----------------
FROM node:12 as builder

ARG api_url

ENV API_URL=${api_url}
ENV PRODUCTION=true

RUN mkdir /ng-app
WORKDIR /ng-app
COPY . .

RUN npm set progress=false && npm config set depth 0 && npm cache clean --force
RUN npm install
RUN npm run set-env
RUN node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng build --subresource-integrity --aot --output-hashing=all

# ---------------- ANGULAR SETUP END ----------------



# ---------------- NGINX SETUP START ----------------
FROM nginx:alpine
RUN apk add gettext

ARG ssl_cert_name

ENV NGINX_SSL_CERT_PATH="/app/ssl/${ssl_cert_name}"

COPY config/ssl/${ssl_cert_name}.crt config/ssl/${ssl_cert_name}.key /app/ssl/
COPY config/nginx/start-nginx.sh /usr/bin/start-nginx.sh
COPY config/nginx/default.conf.template /etc/nginx/nginx.conf.template

RUN chmod +x /usr/bin/start-nginx.sh
# ---------------- NGINX SETUP END ----------------



# 1. Copy build files to nginx html folder
# 2. Substitute ENV variables & Start nginx
COPY --from=builder /ng-app/dist/angularapp/ /usr/share/nginx/html/
CMD /bin/sh -c "usr/bin/start-nginx.sh"

```

#### - set-env.ts
```ts
const { writeFile } = require('fs');
const colors = require('colors');

const envConfigFile = `export const environment = {
   apiURL: '${process.env.API_URL}',
   production: '${process.env.PRODUCTION}',
};
`;

const targetPath = `./src/environments/environment${production === 'false' ? '' : '.prod'}.ts`;
console.log(colors.magenta(`The file ${targetPath} will be written with the following content: \n`));
console.log(colors.grey(envConfigFile));

writeFile(targetPath, envConfigFile, (err) => {
    if (err) {
        throw console.error(err);
    } else {
        console.log(colors.magenta(`Angular environment was generated correctly at ${targetPath} \n`));
    }
});

```

#### - default.conf.template
```template
server {
  listen 80;

  sendfile off;

  gzip              on;
  gzip_types        text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  location / {
      alias /usr/share/nginx/html/;
      try_files $uri /index.html;
  }
}

server {
  listen 443 ssl;

  sendfile off;

  ssl on;
  ssl_certificate ${NGINX_SSL_CERT_PATH}.crt;
  ssl_certificate_key ${NGINX_SSL_CERT_PATH}.key;


  gzip              on;
  gzip_types        text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  location / {
      alias /usr/share/nginx/html/;
      try_files $uri /index.html;
  }
}
```


#### - start-nginx.sh
Here we first replace the variables defined as a first argument `'$$VAR_1'` in our `/etc/nginx/nginx.conf.template` then we replace our default nginx default config template with the new config template.

```sh
#!/bin/sh

set -e
echo "NGINX_VARS: $NGINX_SSL_CERT_PATH"

envsubst '$$NGINX_SSL_CERT_PATH' < /etc/nginx/nginx.conf.template > /etc/nginx/conf.d/default.conf
nginx -g 'daemon off;'

```

#### - package.json
```json
{
  "name": "angularapp",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "set-env": "ts-node ./config/tools/set-env.ts",
    ...
  },
}
```

#### - docker-compose.web.yml
```yml
version: '3.6'

services:
  angularapp:
    container_name: angularapp
    build:
      context: .
      dockerfile: ./config/Dockerfile
      args:
        api_url: https://example.com
        ssl_cert_name: cert-name
    ports:
      - 4200:80
```

### Building the dockerfile
Now that we have created all the necessary files, we can build our docker container.
We can do this two ways:

#### docker-compose
```cmd
docker-compose -f .\docker-compose.web.yml up --build angularapp
```

#### docker build
```cmd
docker build -t angularapp --build-arg ssl_cert_name=cert-name --build-arg api_url=https://example.com ./config/Dockerfile

docker run -p 4200:80 angularapp
```

### Multiple environments

#### - docker-compose.web.yml
```yml
version: '3.6'

services:
  example:
    container_name: example
    build:
      context: .
      dockerfile: ./config/Dockerfile
      args:
        api_url: https://example.com
        ssl_cert_name: cert-name
    ports:
      - 4200:443

  example2:
    container_name: example2
    build:
      context: .
      dockerfile: ./config/Dockerfile
      args:
        api_url: https://example2.com
        ssl_cert_name: cert-name2
    ports:
      - 5200:443
```

**Ready**!
Your project can now host multiple environments. All you need to do is add a new container for each environment in your **docker-compose.web.yml** file.

Thank you for reading this post, feel free to leave a comment if you have any questions.