Logo
Published on

How to Serve an Angular Application with Nginx

Authors
  • Name
    Twitter
Photo by Kelvin Ang on Unsplash

Nginx is a popular HTTP and reverse proxy server that runs on Linux and is often used to serve Angular applications. A common way to run Nginx is within a Docker container.

This article covers the minimum steps required to get this running on a local machine. You will need to have Docker installed on your machine if you want to follow along.


Create an Angular Application

Create a new Angular application using the following command:

ng new nginx-example-app

Build the Angular app by running:

npm run build

The building of the application can be done inside of the Docker container but we’re going to do it outside of the container for now.

Configure Nginx

The Docker image we will use has Nginx installed and ready to go. All we have to do is configure it. To do that we create a file in the root of the solution (where angular.json is) named nginx.conf with this content (which is explained with comments):

# the events block is required
events{}

http {
    # include the default mime.types to map file extensions to MIME types
    include /etc/nginx/mime.types;

    server {
        # set the root directory for the server (we need to copy our
        # application files here)
        root /usr/share/nginx/html;

        # set the default index file for the server (Angular generates the
        # index.html file for us and it will be in the above directory)
        index index.html;

        # specify the configuration for the '/' location
        location / {
            # try to serve the requested URI. if that fails then try to
            # serve the URI with a trailing slash. if that fails, then
            # serve the index.html file; this is needed in order to serve
            # Angular routes--e.g.,'localhost:8080/customer' will serve
            # the index.html file
            try_files $uri $uri/ /index.html;
        }
    }
}

Configure Docker

Create a file in the root of the solution (where angular.json is) named Dockerfile (no extension) with this content (which is explained line-by-line with comments):

# use the latest version of the official nginx image as the base image
FROM nginx:latest
# copy the custom nginx configuration file to the container in the
# default location
COPY nginx.conf /etc/nginx/nginx.conf
# copy the built Angular app files to the default nginx html directory
COPY /dist/nginx-example-app /usr/share/nginx/html

# the paths are relative from the Docker file

Build the Docker Image

There are two basic entities in docker — images and containers. The container is like a server and the image contains instructions for building that server. To borrow OOP terminology, the image is like the class and the container is like the instantiation of that class.

VS Code has some helpful tools that allow you to build images and run containers using a GUI, but we’ll use the command line here. First, we build the image:

docker build -t nginx-example-app-docker-image .

This command simply builds the image.

The -t nginx-example-app-docker-image portion names the image.

The period at the end specifies the current directory (where the Docker file is) as the build context.

Run the Docker Container

Next run the container which will use the image, as follows:

docker run --name nginx-example-app-docker-container -d -p 8080:80 nginx-example-app-docker-image

The --name nginx-example-app-docker-container portion species the name of the container.

The -d flag tells docker to run in detached mode (in the background).

The -p 8080:80 flag maps port 8080 to 80. Inside the container, Nginx exposes the website on port 80 which is the default, and we did not have to specify that. Here we are saying that external clients can hit port 8080 on the container to access the website.

The last argument is the name of the image to run: nginx-example-app-docker-image. Remember, this is what we named the image in the previous section.

You should now be able to open a browser to http://localhost:8080 and see the default Angular scaffolding.


Build the Application in Docker

It’s common to let Docker build the Angular application. We can do that by modifying the Dockerfile with this content (which is explained line-by-line with comments):

# use a node image as the base image and name it 'build' for
# later reference
FROM node:18.18-alpine3.18 as build

# set the working directory to /app
WORKDIR /app
# copy the current directory contents into the container at /app
COPY . .
# install dependencies, matching package-lock.json
RUN npm ci
# build the app
RUN npm run build


# Use the latest version of the official Nginx image as the base image
FROM nginx:latest
# copy the custom nginx configuration file to the container in the default
# location
COPY nginx.conf /etc/nginx/nginx.conf
# copy the built application from the build stage to the nginx html
# directory
COPY --from=build /app/dist/nginx-example-app /usr/share/nginx/html

# The above commands build the Angular app and then configure and build a
# Docker image for serving it using the nginx web server.

Our Docker file now uses what are called stages. Building the application will be the first stage and we name it build so that we can reference it later.

Notice that for the build stage, we are starting from the node:18.18-alpine3.18 image. How did I choose this image?

We need an image that has node installed and I decided to use version 18. So, I went to the node page on the Docker hub and searched for 18-alpine. The alpine variant uses the Alpine Linux image, which is smaller and contains everything we need.

The search returned many images, and I chose the newest one:

Docker Hub search results for 18-alpine

The rest of the changes are documented in the updated Docker file.

There is one last thing to do though. Currently, the Docker file is copying all of our local files into the container, and we don’t need to do that. Let’s create a .dockerignore file in the root (where Dockerfile is) with the files we want to ignore, which will speed things up:

dist node_modules

Rebuilding and Rerunning

If you still have the container running, you will need to remove the container before rebuilding the image and running a new container. Let’s add some tasks to package.json to make this easier:

"build-image": "docker build -t nginx-example-app-docker-image .",
"remove-image": "docker rmi nginx-example-app-docker-image",
"run-container": "docker run --name nginx-example-app-docker-container -d -p 8080:80 nginx-example-app-docker-image",
"remove-container": "docker rm -f nginx-example-app-docker-container",
"containerize": "npm run build-image && npm run run-container",
"recontainerize": "npm run remove-container && npm run build-image && npm run run-container"

With these tasks in place, I can now do the following:

npm run recontainerize

This will remove the running container, rebuild the image (which now rebuilds the application), and start a new container using the updated image. Now you can refresh the browser to see the updated running application.

That’s it! I hope you found this useful.


Bibliography