/RecipeBook

Primary LanguageTypeScript

To deplloy app with expressJS change package.json to start from it

MyFirstApp

This project was generated with Angular CLI version 6.0.0.

Development server

Run ng serve for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.

Code scaffolding

Run ng generate component component-name to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module.

Build

Run ng build to build the project. The build artifacts will be stored in the dist/ directory. Use the --prod flag for a production build.

Running unit tests

Run ng test to execute the unit tests via Karma.

Running end-to-end tests

Run ng e2e to execute the end-to-end tests via Protractor.

Further help

To get more help on the Angular CLI use ng help or go check out the Angular CLI README.

====================ORIGINAL ARTICLE===================

https://github.com/tiangolo/medium-posts/blob/master/angular-in-docker/README.md

Angular in Docker with Nginx, supporting configurations / environments, built with multi-stage Docker builds and testing with Chrome Headless Note: There's an equivalent article for React in Medium and GitHub.

Update: Updated on 2018-06-29, a reader asked if there was any pre-built Docker image to simplify the process (especially for testing). And as this article got quite popular, I just built a Docker image to simplify everyone's process: tiangolo/node-frontend. So, I just updated this article to simplify the process using that image, but kept the sections about doing everything by hand as "optional".

Update: Updated on 2018-06-25, to match Angular 6 and Angular CLI 6. Updating parameters, configuration files, and versions of Node.js and Nginx. And a new section with testing using Pupeteer and Chrome headless inside Docker.

...that's a very long title. But that's what I'm covering, so let's dive in!

Here's how to deploy an Angular app with Docker, building it with Node.js as you would do locally, but end up with a thin and efficient Nginx image, with just the compiled code. Ready for production.

To achieve that, you can use Docker "multi-stage builds". That will allow you to first build your Angular app inside a (possibly huge) Node JS Docker container that is later discarded in favor of a thin Nginx image with just your compiled app. And your final image will be as thin as the latest layer (Nginx).

Here you will also see how to use that technique but still support Angular CLI environments / configurations.

All this removes the need for complex building scripts or the need to add your built app to git (your dist directory).

Also, optionally, you can run your Karma tests in Chrome Headless inside the Docker container. All the information is in the last section.

This could also be adapted to any compiled frontend framework like React. But the main tricks you need are all below.

TL;DR: Create a new Angular project, e.g. "my-angular-project": ng new my-angular-project Enter into your project directory: cd my-angular-project While developing, use ng serve as you normally would, to be able to use live-reload and other features. But for production deployment with Docker, and to test the "production deployment" locally, do the following. Add a .dockerignore for node_modules with: node_modules Add a Dockerfile to your directory with:

Stage 0, "build-stage", based on Node.js, to build and compile the frontend

FROM tiangolo/node-frontend:10 as build-stage

WORKDIR /app

COPY package*.json /app/

RUN npm install

COPY ./ /app/

ARG configuration=production

RUN npm run build -- --output-path=./dist/out --configuration $configuration

Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx

FROM nginx:1.15

COPY --from=build-stage /app/dist/out/ /usr/share/nginx/html

Copy the default nginx.conf provided by tiangolo/node-frontend

COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf Build your image using the production configuration (the default), e.g.: docker build -t my-angular-project:prod . Build your image using the development environment (no configuration), e.g.: docker build -t my-angular-project:dev --build-arg configuration="" . Test your image for the production environment (production configuration) with: docker run -p 80:80 my-angular-project:prod Open your browser in http://localhost. Test your image for the development environment (no configuration) with: docker run -p 80:80 my-angular-project:dev Open your browser in http://localhost. If you are using testing and want to integrate it, read the last section. Details Everything above shows the actual code you need. If you're in a hurry, you could probably just copy all that. If you want to know all the details, continue reading...

When you build an Angular front-end app, you most commonly write it in TypeScript and then compile it to JavaScript. But you need to have Node.js and several packages to do that.

After you compile your app, you end up with a set of files, normally in a directory with the name of your app inside a ./dist directory. Those are the compiled files that you actually use to serve your app. And those files have all the optimizations that you use, for example, AOT (Ahead Of Time compilation).

And those files are the final product that you serve to your users. Your users don't need your source TypeScript files nor any of the packages you use during development, just the compiled files.

So, you need to "deploy to production" only those files.

These are the options we have, take them as the "motivation" for what we will do next:

Option 1: One way to do it is to compile the app locally and add the compiled files to your Git repository. And then, when you are deploying, you just clone your repository and use those compiled files. As we are talking about Docker, you would create an image that copies and uses those compiled files. You would probably use an Nginx base image for that.

But you never edit those compiled files directly, they are generated from your source files. They are constantly changing, so your Git repository will grow large just because you are adding compiled code. And if someone in your team works on a feature branch and has one version of those files and he wants to merge the feature to your main branch (probably master), you might have lots conflicts in those files, even when the source code doesn't have conflicts. So you would have to be fixing "virtual" conflicts. There are probably other disadvantages too. So, the compiled files don't really belong in your Git repository.

Also, if you forget to compile and commit everything right before deploying, you'll get an old version of the deployed app.

Option 2: Another way to do it would be to do not add your compiled code to your Git repository and build your app every time you are going to deploy.

But this would require all your deployment servers or wherever it is that you build your Docker image to have all the tooling to deploy your app. Node.js, TypeScript, all the Angular packages, all the dependencies, etc.

And again, if you forget to compile everything right before deploying, you'll get an old version of the app deployed.

But all the problems that you would have with dependencies by needing to have all the tooling in a server is what Docker is supposed to solve, right?

Option 3: You could build the whole app inside your Docker image, and then serve it from the same image.

That would imply that you would start from an official Node.js base image to compile everything and then you would have to setup a server with Node.js or install Nginx manually on top of Node.js (or something similar).

If you install Nginx by hand you lose the option to use the official Nginx base image that is already fine tuned.

Also, you would end up with huge images that all your deployment servers have to download (if you are concerned about size) and a complex environment, with all the Node.js / Angular packages and dependencies.

Option 4: You could write a complex script that builds your app in one Docker image using Node.js, then extract the compiled files, and then build a second image based on Nginx with just your compiled files.

We are getting close, but you would have another complex script to debug. Copying files from a Docker container and to another Docker image, etc. Not that promising...

Option 5: Use Docker multi-stage builds. With that, you can have a Node.js base image that installs, builds and compiles everything, and then, "discard" all those Node.js specific Docker image layers, and end up with a Nginx image with just your compiled code.

You would have an efficient Nginx server with great performance for your static final (compiled) files. You wouldn't have to add your compiled code to Git, you always get the latest compiled version of your code, you don't have to deal with dependencies outside Docker, etc.

That's great! Docker multi-stage builds solve several problems in one shot. And this option is what is described in this article: "Create efficient Angular Docker images with Multi Stage Builds". But...

When building your Angular code with Angular CLI you have the option to use "configurations" to set the environment while building the Angular app. That alows you to, for example, use a production back end API while using the "production" environment / configuration and use a testing back end API while using the unmodified configuration (development environment).

But with just the method described above we would have no way to use that feature.

So, let's create an Option 6 that combines all of the above and lets us use Angular CLI configurations / environments.

Requirements Angular CLI (you need to have Node.js first) Docker Angular If you don't know Angular (Angular 2+) yet, go and do their tutorials first.

If you are building an Angular front-end web app, you probably should create your code with Angular CLI.

That will help you setting everything up, compiling the app with AOT (Ahead Of Time compilation), serving the app during development with automatic live-reload, etc.

So, go ahead and install Angular CLI. To create a new Angular project, e.g. "my-angular-project", run: ng new my-angular-project Enter into your project directory: cd my-angular-project To be able to see the differences between the Angular CLI environments in the final app, edit your component code in src/app/app.component.ts, import the environment: import { environment } from '../environments/environment';

... Then, add that environment's production property as a component property, so that you can use it in your template: ...

export class AppComponent {

...

production = environment.production; } Your final code might look like: import { Component } from '@angular/core';

import { environment } from '../environments/environment';

@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app';

production = environment.production; } Then, edit your template to show your production value, for example, add the following: ...

We are running in production!

We are running in development...

... Your final template might look like:

Welcome to {{title}}!

We are running in production!

We are running in development...

Note: I'm omitting some of the default Angular CLI code for brevity.

Now, if you run your code locally with the defaults, e.g.: ng serve And open your browser in http://localhost:4200 ...you will see your code using the default development environment (no configuration). So you might probably see something like:

But if you run it passing the --prod flag (or the more specific --configuration=production), e.g.: ng serve --prod And open your browser in http://localhost:4200 ...you will probably see something like:

notice the "running in production!".

To learn more about Angular CLI environments and configurations, check the official Angular CLI documentation. This is a bare-bones Angular project. Just the basic parts to show the point. There are many ways to improve Angular using modules, etc. But we'll just stick to the basics for now.

Nginx Nowadays, Nginx is more or less the "de facto standard" for static content serving. You can search about it and read about its performance. The web is full of articles about it.

So, for our final Docker image, we will need to have a Nginx configuration. You don't really need to know much more about it for now. As the official Docker image will do all the heavy lifting for you.

Update: This article is now based on the image tiangolo/node-frontend and it already provides a default Nginx configuration, you just have to copy it in the last stage (don't worry, I'll cover it below). That configuration file makes sure that all the routes go to index.html, so that you can use your frontend router and it will work even if your users type the URL directly in the browser. If you don't want to use that image but build it form scratch based on node, do the following steps.

Optional read

If you want to build your image based on Node.js directly, you need to create an Nginx config file.

Add a Nginx configuration inside your project directory, named nginx-custom.conf, with: server { listen 80; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html =404; } } The most important part is the: try_files $uri $uri/ /index.html =404; ...that tells Nginx to try to match the files it is requested in the URL (URI) with files it has available. That will make it, for example, match the /main.bundle.js and return the JavaScript file. But when there's no match, it will default to index.html.

That will make it return the index.html for any other URL it receives, so, all the Angular router URLs will work, even when going to the URL directly in the browser (not being redirected from an internal section of the app).

Without that, if you had a route like /app/dashbaord/main, when you tried to open that URL, Nginx wouldn't find the file app/dashboard/main and would return a 404 Not found, instead of returning the index.html and letting the Angular router handle it.

The last part, =404 tells Nginx to return a 404 if it doesn't even find an index.html file. That's the default case. But you will normally always have an index.html file.

Save that file, we will use it soon.

This is the minimum Nginx configuration. You could finetune it more, depending on your case. If you want to explore more, read the Nginx Beginner's Guide.

End optional read

Docker If you don't know Docker yet, and you do at least some back-end stuff (deploying a front-end app counts), it might change your developer life. So, go ahead, install Docker and follow the Get Started guide.

Now, let's assume you already know enough Docker to use it. Let's go to our details.

Here, we'll see how we can use Docker multi-stage builds Docker build-time ARGs. That will allow us to pass build-time variables during image creation. And with that, we'll be able to build different images for the different Angular CLI environments, by just passing an argument to docker. But we need to do a couple things first.

When you build your image, Docker normally "sends" all the files in the directory to the component of Docker that builds the image. If you have a node_modules directory, it will take some time sending that as node_modules directories tend to be huge and with lots of files. But you don't need node_modules to be copied to your Docker image, you will install everything inside and create a node_modules inside your container, so, sending all your node_modules is a waste of time.

The same way that you would add node_modules to your .gitignore file, you can use a .dockerignore file to tell Docker that it shouldn't "send" that directory.

Add a .dockerignore for node_modules with: node_modules Now, let's build our Docker image.

Add a file specifically named Dockerfile in your directory, with:

Stage 0, "build-stage", based on Node.js, to build and compile Angular

FROM tiangolo/node-frontend:10 as build-stage

WORKDIR /app

COPY package*.json /app/

RUN npm install

COPY ./ /app/

ARG configuration=production

RUN npm run build -- --output-path=./dist/out --configuration $configuration

Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx

FROM nginx:1.15

COPY --from=build-stage /app/dist/out/ /usr/share/nginx/html

COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf ...now, let's check what all that is doing.

This will tell Docker that we will start with a base image tiangolo/node-frontend which in turn is based on the Node.js official image, notice that you won't have to install and configure Node.js inside the Linux container or anything, Docker does that for you: FROM tiangolo/node-frontend:10 as build-stage ...we also "named" this stage build-stage, with the as build-stage. We will use this name later.

Our working directory will be /app. This Docker "instruction" will create that directory and go inside of it, all the next steps will "be" in that directory: WORKDIR /app Now, this instruction copies all the files that start with package and end with .json from your source to inside the container. With the package*.json it will include the package.json file and also the package-lock.json if you have one, but it won't fail if you don't have it. Just that file (or those 2 files), before the rest of the source code, because we want to install everything the first time, but not every time we change our source code. The next time we change our code and build the image, Docker will use the cached "layers" with everything installed (because the package.json hasn't changed) and will only compile our source code: COPY package*.json /app/ Install all the dependencies, this will be cached until we change the package.json file (changing our dependencies). So it won't take very long installing everything every time we iterate in our source code and try to test (or deploy) the production Docker image, just the first time and when we update the dependencies (installed packages): RUN npm install Now, after installing all the dependencies, we can copy our source code. This section will not be cached that much, because we'll be changing our source code constantly, but we already took advantage of Docker caching for all the package install steps in the commands above. So, let's copy our source code: COPY ./ /app/ And here's the trick that will allow us to use Angular CLI "configurations" and enable different environments. We create an ARG that we will pass at build time with a default value of production (the default value for Angular CLI). This will be done at "build time", while building the image, in contrast to at "run time", which is while "running" or starting the image. ARG configuration=production Inside our container we don't have a global Angular CLI installation, so we cannot just use ng build, because it won't find ng. But as Angular CLI creates an NPM script that builds everything, we can just use it, with npm run build. But we need to pass parameters to ng and we cannot pass them directly because npm would try to interpret them. So first we add -- to let npm know that the next parameters are not for him, but for ng. Then, we pass the --prod parameter, the equivalent of --configuration=production, that sets several things at once. That will take care of optimizing everything for production. And lastly, we pass the --configuration $configuration parameter. See that we are using the "configuration variable" $configuration that we created right above. This environment variable won't persist after building (as an environment variable), but we can use it to pass parameters to Docker at "build time". RUN npm run build -- --output-path=./dist/out --configuration $configuration ...that will build our app, to the directory ./dist/out. Inside the container will be in /app/dist/out. We are specifying the specific --output-path=./dist/out because otherwise by default Angular CLI would use the name of the project, for example ./dist/my-angular-project. But we need to have a specific path to use it later. So, by fixing it to ./dist/out you can copy-paste it directly to your project without worrying about modifying paths in several places.

In the same Dockerfile file, we start another section (another "stage"), like if 2 Dockerfiles were concatenated. That's Docker multi-stage building. It almost just looks like concatenating Dockerfiles. So, let's start with an official Nginx base image for this "stage": FROM nginx:1.15 ...if you were very concerned about disk space (and you didn't have any other image that probably shares the same base layers), or if, for some reason, you are a fan of Alpine Linux, you could change that line and use an Alpine version.

Here's the Docker multi-stage trick. This is a normal COPY, but it has a --from=build-stage. That build-stage refers to the name we specified above in the as build-stage. Here, although we are in a Nginx image, starting from scratch, we can copy files from a previous stage. So, we can copy the compiled fresh version of our app. That compiled version is based on the latest source code, and that latest compiled version only lives in the previous Docker "stage", for now. But we'll copy it to the Nginx directory, just as static files: COPY --from=build-stage /app/dist/out/ /usr/share/nginx/html Now, we'll override the default.conf file in Nginx with the custom nginx.conf provided in the image tiangolo/node-frontend. This configuration file directs everything to index.html, so that the Angular router can take care of it's routes: COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf ...that's it for the Dockerfile! Doing that with scripts or any other method would be a lot more cumbersome.

Optional read

If you don't want to use the image tiangolo/node-frontend but build yours from scratch based on node you can do it, you just have to create the Nignx config file descriibed in the Nginx section above and modify the line:

COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf to:

COPY ./nginx-custom.conf /etc/nginx/conf.d/default.conf ...have in mind that if you don't use tiangolo/node-frontend you will also have to install Puppeteer and all its dependencies by hand too.

End optional read

Build it Now we can build our image, doing it will compile everything and create a Nginx image ready for serving our app.

If we just build it normally, it will use the production configuration (production) and environment.

Build your image using the production configuration / environment (the default), e.g.: docker build -t my-angular-project:prod . But we can build an image for each environment we have, by just passing the configuration name as an argument to the build process. The same as we would pass it to ng build --configuration. We just have to use Docker's --build-arg parameter.

Build your image passing an empty string as the configuration build arg, so that Angular CLI doesn't use a configuration and runs with the development environment, e.g.: docker build -t my-angular-project:dev --build-arg configuration="" . If you had a staging configuration and environment, you could build an image for that environment with: docker build -t my-angular-project:dev --build-arg configuration="staging" . All these additional builds, and all the next builds (including the prod one) will be very fast, as all the NPM packages will already be installed and Docker will re-use the cached layers. It will know it just has to copy your source code and compile it, etc. It won't install everything every time. And when you add a new dependency, your package.json will be modified, Docker will notice it and re-install the dependencies. Test it To check that your new Docker images are working, you can start a container based on them and see the results.

Test your image for the production environment with: docker run -p 80:80 my-angular-project:prod ...you won't see any logs, just your terminal hanging there.

Open your browser in http://localhost. You should see something very similar to:

...notice that it is served by Docker and not by Angular CLI (not in port 4200). And notice that it says that you are running the "production" version of your frontend App.

Test your image for the development environment with: docker run -p 80:80 my-angular-project:dev Open your browser in http://localhost. You should see something very similar to:

...again, served by Docker and not by Angular CLI (not in port 4200). And now you are using your "development" environment version.

Add testing If you are using testing with Jasmine and Karma, you can run your tests using Chrome headless and integrate that in your Dockerfile.

That will allow it to run in a Docker container, and you will be able to test your application before building it and integrate it with CI systems (e.g. GitLab CI).

First, you need to add Puppeteer as a dependency following Karma’s official documentation:

Go to your application directory and install Puppeteer: npm install puppeteer --save-dev Then install karma-chrome-launcher: npm install karma-chrome-launcher --save-dev Modify your src/karma.conf.js file, add this on the top: process.env.CHROME_BIN = require('puppeteer').executablePath() Modify your src/karma.conf.js file, there's a line with: browsers: ['Chrome'], replace that line with:

browsers: ['Chrome', 'ChromeHeadlessNoSandbox'],
captureTimeout: 210000,
browserDisconnectTolerance: 3,
browserDisconnectTimeout: 210000,
browserNoActivityTimeout: 210000,
customLaunchers: {
  ChromeHeadlessNoSandbox: {
    base: 'ChromeHeadless',
    flags: [
      '--no-sandbox',
      // Without a remote debugging port, Google Chrome exits immediately.
      '--remote-debugging-port=9222',
    ]
  }
},

your final src/karma.conf.js might look like:

// Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html

process.env.CHROME_BIN = require('puppeteer').executablePath()

module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome', 'ChromeHeadlessNoSandbox'], captureTimeout: 210000, browserDisconnectTolerance: 3, browserDisconnectTimeout: 210000, browserNoActivityTimeout: 210000, customLaunchers: { ChromeHeadlessNoSandbox: { base: 'ChromeHeadless', flags: [ '--no-sandbox', // Without a remote debugging port, Google Chrome exits immediately. '--remote-debugging-port=9222', ] } }, singleRun: false }); }; Normally you would test your app with: ng test You can try and run that. It will run the tests in your local browser. And that's how you should test it during development.

Note: as of now, 2018-06-29, a new, freshly generated Angular CLI project will fail 1 test, looking for a string 'Welcome to my-frontend-project!', you can change the string to 'Welcome to app!' to fix the test.

In a Docker container, in your "integration" or "production" server, the tests will run in Chrome headless. So, let's first make sure that Chrome headless runs locally.

Run a single (not "watch") test locally, in headless mode:

ng test --browsers ChromeHeadlessNoSandbox --watch=false It should show that the tests are run in the console, and that they are passing (or not), but most importantly, it shouldn't open a Chrome browser window, as it will be running in Chrome headless mode.

The image tiangolo/node-frontend already includes all the dependencies for Puppeteer, which is what is used to run your tests in Chrome Headless. So you don't have to add anything to your Dockerfile appart from running your tests.

Optional read

If you don't want to use tiangolo/node-frontend you will have to install Puppeteer and it's dependencies by hand, here's how.

Right below the line:

FROM node:10.5 as build-stage ...add the installation of Puppeteer's dependencies, the start of your Dockerfile should contain:

Stage 0, "build-stage", based on Node.js, to build and compile Angular

FROM node:10.5 as build-stage

RUN apt-get update && apt-get install -yq libgconf-2-4

Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)

Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer

installs, work.

RUN apt-get update && apt-get install -y wget --no-install-recommends
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
&& apt-get update
&& apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont
--no-install-recommends
&& rm -rf /var/lib/apt/lists/*
&& apt-get purge --auto-remove -y curl
&& rm -rf /src/*.deb

... End optional read

Then you can run your tests, after the section with: COPY ./ /app/ ...add

RUN npm run test -- --browsers ChromeHeadlessNoSandbox --watch=false ...that command will run the tests in headless mode, at build time. So, you can now integrate that with your CI systems and have your frontend tested every time the image is built. If a test fails, the build fails. So if you set up continuous integration and deployment (automatic deployments), you can be safe knowing that you won't deploy code with broken tests.

Your final Dockerfile would look like:

Stage 0, "build-stage", based on Node.js, to build and compile the frontend

FROM tiangolo/node-frontend:10 as build-stage

WORKDIR /app

COPY package*.json /app/

RUN npm install

COPY ./ /app/

ARG configuration=production

RUN npm run build -- --output-path=./dist/out --configuration $configuration

Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx

FROM nginx:1.15

COPY --from=build-stage /app/dist/out/ /usr/share/nginx/html

Copy the default nginx.conf provided by tiangolo/node-frontend

COPY --from=build-stage /nginx.conf /etc/nginx/conf.d/default.conf Optional read

If you didn't use the image tiangolo/node-frontend your final complete Dockerfile would look something like:

Stage 0, "build-stage", based on Node.js, to build and compile Angular

FROM node:10.5 as build-stage

RUN apt-get update && apt-get install -yq libgconf-2-4

Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)

Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer

installs, work.

RUN apt-get update && apt-get install -y wget --no-install-recommends
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
&& apt-get update
&& apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont
--no-install-recommends
&& rm -rf /var/lib/apt/lists/*
&& apt-get purge --auto-remove -y curl
&& rm -rf /src/*.deb

WORKDIR /app

COPY package*.json /app/

RUN npm install

COPY ./ /app/

RUN npm run test -- --browsers ChromeHeadlessNoSandbox --watch=false

ARG configuration=production

RUN npm run build -- --output-path=./dist/out --configuration $configuration

Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx

FROM nginx:1.15

COPY --from=build-stage /app/dist/out/ /usr/share/nginx/html

COPY ./nginx-custom.conf /etc/nginx/conf.d/default.conf End optional read

To test that your new Docker image with tests works, just build it again, you should see the results in the console. Check the section above ...or just run: docker build -t my-angular-project:prod . Done! That's it! Angular in Docker, ready for production with great performance (thanks to Nginx). A lot less error prone (thanks to Docker multi-stage builds). Supporting Angular CLI configurations and environments, thanks to Docker's ARG and --build-arg. And with integrated tests thanks to Puppeteer and Chrome headless (integrated in tiangolo/node-frontend).

You can also automatize that in a continuous integration/delivery environment or whatever you want very easily with those tricks (I always do that).

There's a demo project with all what I described here in GitHub. If you have any problem replicating this, clone the repo and compare it to your code to see what could be happening.

Also, if you find something wrong in this article, please post an issue in that repo, or better, a pull request.

GitHub version of the article

Medium version of the article

And let me know if this was helpful to you!

About me You can follow me, contact me, ask questions, see what I do, or use my open source code:

GitHub Twitter Linkedin Medium