How to dockerize an Angular app for different environments
Tags: docker, angular
Published: 2020-11-30 16:16:15

Docker is an open-source tool designed to help programmers in development and deployment. It makes use of a structure called "container" which wraps the application and its dependencies together so that it can be executed on any machine. This is particularly important when have to deal with different servers - testing, integration, production - where the applications must run without any error or compatibility issue.

In this tutorial I will explain how to dockerize an Angular application for different target environments.

# Set up custom environments in Angular
By default, Angular provides two different environment files, that can be found in the `environments` folder:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/oeqkvmlmyepqsu26wlrw.png)

Let's add a couple more! Create two new files named `environment.test.ts` and `environment.int.ts` and replace in there your urls with the ones you want to use for the target environment. Here is my `environment.int.ts`:

```
export const environment = {
    production: false,
    name: 'int',
    functionUrls: {
        data1: 'my-data1-int-url',
        data2: 'my-data2-int-url'
    }
};
```

Then we need to tell Angular that we have new build configurations: edit the `angular.json` file and create new entries under `architect → build → configuration`. As you can see, the production and local (serve) entries are already present.

When inserting a custom environment we only need to notify the framework to replace the default `environment.ts` with the one we want at build-time, so in principle it is sufficient to add the following:

```
"int": {
  "fileReplacements": [
     {
      "replace": "src/environments/environment.ts",
      "with": "src/environments/environment.int.ts"
     }
  ],
},
```

We can customize the build options by adding extra settings. Refer to the [official documentation](https://angular.io/guide/build) for further details.

As a last step, let's edit the `package.json` and write some commands to build the app more easily:

```
"scripts": {
   "ng": "ng",
   "start": "ng serve",
   "build": "ng build",
   "build-test": "ng build --configuration=test",
   "build-int": "ng build --configuration=int",
   "build-prod": "ng build --prod",
   "test": "ng test",
   "lint": "ng lint",
   "e2e": "ng e2e"
},
```

Let's build our app!

Open a terminal and from the main project folder run `npm build-int`. This will build the application for the int environment. Your output is located into the `dist` folder in your project, ready to be deployed on a server.

If you want, you can test it using `http-server`. Install it with:

```
npm install http-server -g
```

Run it with:

```
http-server ./dist
```

It will start serving your project from the `dist` folder.

# Integrate Docker

Now that we can build a project for different environments, it's time to integrate Docker and run the application in a container. First, create a `docker` folder inside the project, which will contain all the docker-related files. Then create a `docker-compose` file for each environment: `docker-compose.int.yml` for integration , `docker-compose.test.yml` for testing and so on. These files look like the following:

```
version: '3.3'

services:
  myapp:
    image: myapp
    build:
      context: ../
      dockerfile: docker/Dockerfile
      args:
        PROFILE: int
    ports:
      - "8080:80"
```

What's important to notice here is the line

```
PROFILE: int
```

were we define the environment we want to use: we'll inject this variable in the `Dockerfile` in a moment.

Let's create the `Dockerfile`:

```
FROM node:12.16.1-alpine As builder

### STAGE 1: Build ###
WORKDIR /usr/src/app
COPY package.json package-lock.json ./

RUN npm install

COPY . .

ARG PROFILE
ENV PROFILE $PROFILE

RUN echo "Environment: ${PROFILE}"
RUN npm run build-${PROFILE}

### STAGE 2: Run ###
FROM nginx:1.15.8-alpine

COPY --from=builder /usr/src/app/dist/myapp/ /usr/share/nginx/html
COPY --from=builder /usr/src/app/docker/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
```

In stage one we build the application and restore the `node_modules`. As you can see, we use the `PROFILE` variable we defined above to retrieve the correct environment.

In stage two we run the application using nginx, which is an http and reverse proxy server. By default, nginx http server listens for incoming connection on port 80, which represents the standard web port. In the `docker-compose` we match that port with 8080.

In addition, note that here

```
COPY --from=builder /usr/src/app/docker/nginx.conf /etc/nginx/conf.d/default.conf
```

we replace the default nginx configuration with ours. To do so, we need to create one last file in our `docker` folder, called `nginx.conf`:

```
server {
  listen 80;
  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html =404;
  }
}
```

All games are done! Let's try to make this work.

If we want to build and run the configuration for integration, we only need to run the following commands in a terminal:

```
docker-compose -f docker/docker-compose-int.yml build
docker-compose -f docker/docker-compose-int.yml up
```

The `up` command will append the console at a message saying `Attaching to...`, then it goes to print logs.
Navigate to `localhost:8080` to check the outcome.