Deploying an Angular App on EC2

Deploy an Angular App in Production with Nginx & Ubuntu on EC2

This purpose of this tutorial is to show you how to configure Nginx to serve an Angular app on AWS EC2. This tutorial assumes the following:

  • You already have an Amazon Web Services account (if not you can make one here) and have basic knowledge of security groups
  • You already have a purchased a domain name and have associated it with a publicly accessible IP address
  • You have an existing Angular Project hosted on a code repository (Github, GitLab, BitBucket, etc.) and have some basic knowledge of Angular

Summary of technologies used

  • Ubuntu 16.04 LTS
  • Nginx 1.10.3
  • AWS EC2
  • Angular 6.1.3

Step overview

  • Step 1 – Open an EC2 instance
  • Step 2 – Install packages and dependencies
  • Step 3 – Configure Nginx
  • Step 4 – Deployment

Step 1 – Open an EC2 instance

Log into your AWS account, navigate to the EC2 Dashboard, and select “Launch Instance”. From the list of AMIs select Ubuntu Server 16.04 LTS (HVM).

While setting up the instance, you will be brought to a page where you select the instance type. Select t2.micro (free tier eligible). Once selected, click “Review and Launch” at the bottom of the page. On the next page click “Launch” on the bottom of the page as well.

Once launched it will take a few minutes for AWS to boot up your very own EC2 instance. Store your generated key pair in a safe place on your local computer. (Here’s documentation from AWS on how to handle your key pair)

Note: You may have to adjust the security group on your EC2 instance so that your website is publicly available. There’s plenty of resources and stack overflow questions solving this potential issue.


Step 2 – Install packages and dependencies

Time to ssh into your Ubuntu server with:

ssh -i ~/path-to-your-key-pair ubuntu@ec2-your-public-ip.compute-1.amazonaws.com

Once you have successfully logged into your public server it’s time to install some packages. Execute these commands from within your Ubuntu machine:

# update Ubuntu's local package index 
$ sudo apt-get update 

# install git 
$ sudo apt-get install -y git 

# install Nginx 
$ sudo apt-get install -y nginx 

# clear out the local repository of retrieved package files 
$ sudo apt-get -s clean 

Step 3 – Configure Nginx

If you are new to Nginx I highly recommend reading the following to understand what these next steps actually do.

Common Pitfalls using Nginx

Nginx Quick Start

Nginx Configuration

Navigate the sites-available directory.

$ cd /etc/nginx/sites-available

and use your preferred editor (I use vim) to create a new file. In this case we’re storing our nginx configuration as example.com.

$ sudo vim example.com

Enter the following in your new file:

server {     
    listen 80;      
    listen [::]:80;            
    root /var/www/your-angular-app-name/dist;   
    server_tokens off;   
    index index.html index.htm;     
 
    location / {         
        # First attempt to server request as file, then         
        # as directory, then fall back to displaying a 404.          
        try_files $uri $uri/ /index.html =404;      
    } 
}

***The line root /var/www/your-angular-app-name/dist will tell Nginx where the index.html file is for your compiled Angular app.

Save your new file and symlink it to the sites-enabled folder.

$ cd /etc/nginx/sites-enabled 
$ sudo ln -s ../sites-available/example.com 
$ ls -l

Remove the default file that comes with Nginx in the sites-enabled directory. This default file was placed here by Nginx when it was installed.

$ sudo rm default

For good practice run a test on your Nginx configuration to correct any configuration or syntax errors (syntax always gets the best of us).

$ sudo nginx -t

• Restart Nginx:

$ sudo nginx -s reload

Step 4 – Deployment

Before deploying an angular application we must use the Angular CLI to create the production build that will be served on our EC2. You have the option of building the production bundle right on your local or on your live server. If you are working by yourself on a project, it’s okay to build and add the generated dist folder to git source control. Otherwise you should avoid adding the compiled dist bundle to git when working with others to avoid merge conflicts from everyone’s own build.

Angular CLI documentation on deployments

Below I will show you two ways to deploy your app. Both methods below are ok to “test the waters” to build small applications to understand how to deploy an Angular app but I don’t recommend you keep deploying Angular in the ways detailed below. It is well worth your time to learn how to build an automation pipeline that takes care of this process for you. See my article building a simple CI/CD pipeline from GitLab to AWS. (You can build and deploy your Angular app virtually free!)

Path 1: Building the production bundle locally

In the case that you are building the production build on your local you will need to run:

$ ng build --prod

If there are no errors when Angular is building the production bundle then a dist directory will be generated in the root directory (/) of your project.

Check in the new dist dist folder to source control so that you can pull it into your server.

You will then have to ssh into your EC2 and clone the git repository into the /var/www/ directory.

$ cd /var/www
var/www $ git clone https://your-project-name

Note: You may need to use the $ sudo flag if your user inside the server does not have the right permissions to run these commands.

Restart Nginx:

$ sudo nginx -s reload

Navigate to your URL or Elastic IP address and your site should be live! If all you see is an Nginx 404 page then double check the directory on the root ...; line in your Nginx sites-available file is pointing to the right directory.

Path 2: Building on the server

DISCLAIMER: Depending on how large your application is this following method can run up your cloud provider bill.

In the case that you are building the production build on your server, you will then have to ssh into your EC2 and clone the git repository into the /var/www/ directory:

$ cd /var/www
var/www $ git clone https://your-project-name

Note: You may need to use the $ sudo flag if your user inside the server does not have the right permissions to run these commands.

Now it’s time to create the production build right on the server by making sure you currently are in your source code directory. First we must install some additional dependencies on the EC2 instance with the following commands:

# install node package manager 
$ sudo apt-get install -y npm 

# install curl 
$ sudo apt-get install -y curl  
  
# install node.js $ curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - 
 

# update node.js 
$ sudo apt-get install -y nodejs 
 
# install the Angular CLI 
$ sudo npm install -g @angular/cli  

Next we navigate to the directory where we cloned the repository

$ cd /var/www/the-project-you-just-cloned

Once in the project directory, you will run:

$ ng build --prod

If there is an issue with the production build then your website will display the generic Nginx 404 page. To avoid this I highly recommend testing the production build process by running the $ ng build --prod command on your local first for testing.

Once the dist folder has been built we will restart Nginx. Nginx has already been configured to look at the var/www/your-project/dist from our configuration above.

Restart Nginx:

$ sudo nginx -s reload

Navigate to your URL or Elastic IP address and your site should be live! If all you see is an Nginx 404 page then double check the directory on the root ...; line in your Nginx sites-available file is pointing to the right directory.


If you have this process down pat and want more of a challenge then check out my next post on building a simple CI/CD pipeline from GitLab to AWS.

How to preserve your static assets in the production bundle

If you want your static assets (images, svgs, etc.) to show up in production you’ll have to do the following:

  1. In your angular.json make sure that your assets folder holding your static assets is included in the assets block like so:
“tsconfig”:…
“assets”: [
  “src/favicon.ico”,
  “src/assets”,
  “src/robots.txt”,
  “src/sitemap.xml”
],
“styles”:…

With this addition to angular.json, you’re telling angular where to find the assets when you reference them throughout your code in the build process.

2. Make sure that you’re referencing the correct directory path to your assets in your HTML blocks. In the configuration above, each asset in the assets folder should be accessed like the following:

<img src="/assets/images/my_cool_image.jpg" /> 

Now when you build and deploy, the assets should show up because Angular knows where to find them now.

You’re done!

If you’ve followed all these steps, you’ll be able to navigate to your site domain in any browser and see your basic angular app up and running. If not, remember it can take up to 48 hours for any DNS changes to fully propagate if you’ve just associated your Elastic IP to your domain name manager! Thanks for reading!

23 thoughts on “Deploy an Angular App in Production with Nginx & Ubuntu on EC2”

  1. Hi,Thanks for your efforts, I have deployed the app successfully and then I have an issue with assets loading, some assets don’t display correctly. Any explanation for that ?

    Liked by 1 person

    1. Hi Aymen. Glad to hear that you have deployed succsefully! If all your images show up on localhost:4200 but not in the production build then we’ll have to check the following:

      1. First, in your angular.json make sure that your assets folder holding your static assets is included in the assets block like so:
      “tsconfig”:…
      “assets”: [
      “src/favicon.ico”,
      “src/assets”,
      “src/robots.txt”,
      “src/sitemap.xml”
      ],
      “styles”:…

      with this addition to angular.json, you’re telling angular where to find the assets when you reference them throughout your code in the build process.

      2. Make sure that you’re referencing the correct directory path to your assets in your HTML blocks. In the configuration above, each asset in the assets folder should be accessed like the following:

      Now when you build and deploy, the assets should show up because Angular knows where to find them now.

      Did this help? If so I’ll update this post.

      Like

    1. Great question! S3 is a better service to use in certain cases. If I was to host a static site that I know will never need a custom web server configuration then S3 is the cheaper and better way to go. The deployment process on S3 is also much simpler since you don’t need to open and provision an EC2 instance – you can just drag and drop the files into the bucket to deploy.

      In short, hosting on EC2 with a custom configured web server (Nginx, Apache2, etc.) provides much more flexibility in what you can do. For example: Since SPA’s aren’t good with SEO, we can install a CMS ( e.g. WordPress) at the root directory, and move our SPA behind an authentication wall (where we don’t need SEO) so that you can still provide that seamless SPA UI/UX while also being SEO friendly. A web server will direct the user to the correct application (CMS or SPA) depending on the request URL. You can get very granular on what you can do with EC2 and a web server.

      Like

  2. Thank you so much, this is super helpful! the only thing is that is missing a braket in the nginx configuration step, hehe it took us some time to realize.

    Liked by 1 person

    1. Julio, I’m glad you found this super helpful and thank you for pointing out the missing closing bracket on the nginx configuration file. I just updated this post with the correction. Hopefully, that didn’t cause you guys too much trouble.

      Like

  3. I cannot deploy it successfully, and please help me…

    First, I install (https://angular.io/guide/setup-local) angular (my-apps) on ec2-instance (linux), and the path is (var/www/html/my-apps).

    Then, I follow the above guidelines to install nginx,

    but I don’t understand what is {your-site-name}, http://your-site-name.com; and /var/www/your-angular-app-name/dist; (suppose the path is var/www/html/my-apps, http://localhost:4200/)

    Liked by 1 person

    1. To avoid confusion I removed the “server_name” directive from the nginx configuration file as you don’t need it to start serving the Angular app. You’re correct in that “/var/www/your-angular-app-name/dist” in my example should be “var/www/html/my-apps/dist” in your nginx file.

      Since we’re serving the compiled build of Angular, via the CLI, we won’t be starting any live node servers on port 4200. We’ll be serving the bundled version of the static app from “index.html” generated in the “dist” folder when you run “ng build –prod”. Your nginx configuration file should look like this:

      server {
      listen 80;
      listen [::]:80;
      root /var/www/html/my-apps/dist;
      server_tokens off;
      index index.html index.htm;

      location / {
      # First attempt to server request as file, then
      # as directory, then fall back to displaying a 404.
      try_files $uri $uri/ /index.html =404;
      }
      }

      Please let me know if this wasn’t clear or if you need me to expand on this.

      Like

  4. Very good tutorial, I have achieved the implementation of my angular project in amazon ec2, but I have a problem making requests for restful web services, how can I solve this. thanks

    Like

  5. Very good tutorial, I have achieved the implementation of my angular project in amazon ec2, but I have a problem making requests for restful web services, how can I solve this. thanks

    Liked by 1 person

    1. I need to have more context on your problem before I can help you.

      Can you show me your nginx configuration?

      Are you serving the static Angular production files with nginx?

      What are the ingress and egress rules of your security group on the ec2 instance?

      Like

      1. 664/5000
        Thanks for your quick response.

        I have developed a java server that is in the same instance of amazon ec2, there are my web services published, the angular application is a client that must consume these services, I have performed tests in the postman to verify that the web services are working And they work normally.
        The web services work in the angular project locally, but when uploading them to the Amazon server they no longer work, they give me an error, I think I am missing some configuration in the nginx file.
        The angular project forced me to create a proxy.conf.json file so that web services can be consulted.

        My configuration file is similar to yours
        server {
        listen 80;
        listen [::]:80;
        root /var/www/webmonitoreo/dist/web-firebase;
        server_tokens off;
        index index.html index.htm;

        location / {
        # First attempt to server request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ /index.html =404;
        }

        The rules of entry and exit of the security group allow connections port 80: for the web page, port 8080: java server and port 22 for the SSH connection.

        Like

      2. If you’re connecting to the API from angular inside the instance I would first check that the base URL for API requests is http://localhost:8080.

        

If you’re connecting to the API from outside the instance then you must also configure a proxy pass in your nginx configuration for a specific URL like so:
        


        location /api {
        proxy_pass http://localhost:8080/;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Connection “”;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }



        This location block above may not work for you the first time, but it’s a good starting point. Also check out nginx’s documentation on proxy passing.



        While debugging I recommend looking at /var/log/nginx/error.log and /var/log/nginx/access.log for more insight on what’s actually wrong.

        Like

  6. Hi,

    I created dist folder in my local and copied that into location var/www and given the nginx configuration like below
    server {
    listen 80;
    listen [::]:80;
    root /var/www/webmonitoreo/dist/web-firebase;
    server_tokens off;
    index index.html index.htm;

    location / {
    # First attempt to server request as file, then
    # as directory, then fall back to displaying a 404.
    try_files $uri $uri/ /index.html =404;
    }

    but for my rest calls I am getting 403 and 415 errors. Please suggest how can I solve this.

    Regards,
    Vijay

    Liked by 1 person

    1. Hi Vijay,

      I need some more information before I can help you. Where are you making the restful calls from and what end point are you hitting?

      Do the /var/log/nginx/error.log and /var/log/nginx/access.log give you any more information on what the error is?

      Like

      1. Hi Joseph,

        Below is the restend point I am trying to hit
        http://192.187.114.202:8080/rapidviewmain/api/loginUser

        Below is the information of error log
        2020/03/10 13:25:49 [notice] 487#487: signal process started
        2020/03/10 13:36:26 [notice] 845#845: signal process started
        2020/03/10 13:39:25 [notice] 892#892: signal process started
        2020/03/10 13:51:12 [notice] 1210#1210: signal process started
        2020/03/10 13:59:19 [notice] 1362#1362: signal process started
        2020/03/10 14:17:55 [notice] 1721#1721: signal process started
        2020/03/10 14:17:55 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:17:55 [emerg] 329#329: still could not bind()
        2020/03/10 14:23:11 [notice] 1793#1793: signal process started
        2020/03/10 14:23:11 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to 0.0.0.0:8080 failed (98: Address already in use)
        2020/03/10 14:23:11 [emerg] 329#329: bind() to [::]:8080 failed (98: Address already in use)

        Like

  7. Hi Joeshph
    This info is regarding Vijay query.
    Both UI And sever side code are running on same same machine in a tomcat.
    Server side is war file deployed in webapps of tomcat
    UI code is angular and its dist folder is also deployed in tomcat webapps

    Server side code runs on port number 8080 as it is the default tomcat port no.
    server side code is a rest end point exposed to UI

    Like

  8. Hi Joesph
    This info is regarding VIjay’s Comment

    Server code(war file) and Ui code(dist file) both are running on same machine and deployed in tomcat server in webapps folder.

    UI is trying to access a rest end point which was provided by backend code.

    http://192.187.114.202:8080/rapidviewmain/api/loginUser?

    process running on 8080 is the tomcat server and back end code is running on the same port.

    Like

    1. Hey,

      Got it. So the flow of traffic for a request would be:

      User -> nginx serves static Angular app -> Angular makes a request to the Tomcat server on localhost -> response makes it way back to the user

      The error logs tell me you have another service running on port 8080 which could be interfering with the Tomcat server. Either fix that and keep Tomcat running on port 8080 or avoid the problem completely and run the Tomcat server on a different port.

      Since Angular and the Tomcat server are on the same ec2 instance, Angular should be calling the API with a base URL of 127.0.0.1 or localhost (e.g. http://127.0.0.1:8080/rapidviewmain/api/loginUser or http://localhost:8080/rapidviewmain/api/loginUser).

      Does this help at all?

      Like

  9. Hi Joseph

    Server code(war file) and Ui code(dist file) both are running on same machine and deployed in tomcat serve.

    Server code (WAR file) is in webapps folder and UI code (dist folder) is in /var/www/rapidView/dist.

    UI is trying to access a rest end point which was provided by backend code.

    http://192.187.114.202:8080/rapidviewmain/api/loginUser

    process running on 8080 is the tomcat server and back end code is running on the same port.

    process running on 8080 is the tomcat server and back end code is running on the same port.

    Like

Leave a Reply to paulquito23 Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.