Published: May 24 2023

Angular 15/16 Free Course #8 - Dockerize App with Nginx

Built and tested with Angular 15 and Angular 16

In this free step by step Angular course we'll be covering how to implement routing, authentication, registration and CRUD functionality in Angular.

Other parts available in this Angular course:


Angular Tutorial Part 8

In part 8 of this Angular tutorial series we're going to Dockerize our app to run in a production ready Docker container with Nginx.

What is Docker?

Docker is the de-facto platform for running apps in standalone containers. A Docker container is a software package that includes all the code and dependencies to run an app consistently and reliably on any machine with Docker installed. The same docker container can run seamlessly on your local development machine (Windows, Mac, Linux etc) or be deployed to any cloud provider (AWS, Azure, GCP etc).

Docker Images and Containers

A Docker image is a blueprint/template that contains all code, dependencies and instructions to create and run a Docker container instance. As a developer I think of an image like a class and a container like an object.

Docker images are defined as a set of instructions in a Dockerfile - a text file with an ordered list of commands used to build an image with the Docker CLI (docker build ...). After building a Docker image you can create and run a Docker container from that image with the Docker CLI (docker run ...).

Docker containers can be accessed and changed while they're running (e.g. from an interactive terminal), but this will only affect a single specific container instance, it won't change the underlying Docker image which is immutable (read-only). To change a Docker image you have to update the Dockerfile then build a new version of the image.

What is Nginx?

Nginx is a super fast web server that's perfect for hosting single-page apps (like Angular) in a production environment.

Code on GitHub

The complete source code for this part of the tutorial is available on GitHub at https://github.com/cornflourblue/angular-16-tutorial in the part-8 folder. If you haven't completed Part 7 (Migrate to Standalone Components and Functional Interceptors) but want to follow the steps in this part of the course you can start with the code in the part-7 folder of the GitHub repo.


Tutorial Steps

  1. Install Docker Desktop
  2. Create Nginx Config for Angular
  3. Create Dockerfile
  4. Create .dockerignore file
  5. Build Docker Image for Angular App on Nginx
  6. Run Docker Container with Angular App on Nginx
  7. Open Angular App in Browser


Install Docker Desktop

Docker Desktop is an easy to install app for building and running containerized applications on your local machine. It includes a GUI for managing Docker images and containers, the Docker Engine (runtime), Docker CLI and a bunch of other features.

Download and install Docker Desktop from https://www.docker.com/products/docker-desktop/. It's compatible with Windows, Mac or Linux.


Create Nginx Config for Angular

Below is the Nginx config for our Angular application. It configures the root folder for Nginx and prevents 404 Not Found errors by setting the default file to the base page of the Angular app (/index.html).

Create a file named nginx.conf in the project root folder (where the package.json is located) and copy the below code into it:

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


Nginx Config Reference

server { ... } defines a server block which contains the configuration for a virtual server within Nginx.

location / { ... } defines a location block which contains the configuration for requests that have a URI beginning with a forward slash (/).

root /usr/share/nginx/html; uses the root directive to set the root directory to /usr/share/nginx/html for requests matching this location block.

try_files $uri $uri/ /index.html; uses the try_files directive to look for a file that matches the request URI ($uri or $uri/) and return it if there is one. If no file matches the URI the default file (/index.html) is returned. This allows you to request a deep link from a single-page app (e.g. Angular, React, Vue etc) without getting a 404 Not Found response from the server.


Create Dockerfile

A Dockerfile defines all the instructions used to build a Docker image. The below Dockerfile builds an image of our Angular application running on Nginx, it uses a multi-stage build by including multiple FROM statements. Each FROM statement begins a new build stage starting from the specified base image (e.g. nginx), significantly reducing the size of the final image.

Create a file named Dockerfile in the project root folder and copy the below instructions into it:

# ----------------------------
# build from source
# ----------------------------
FROM node:18 AS build

WORKDIR /app

COPY package*.json .
RUN npm install

COPY . .
RUN npm run build

# ----------------------------
# run with nginx
# ----------------------------
FROM nginx

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
COPY --from=build /app/dist/angular-tutorial /usr/share/nginx/html

EXPOSE 80


Dockerfile Reference

FROM node:18 AS build - sets the base image of the stage to node version 18, and sets the name of the stage to build so it can be referenced in the next stage.

WORKDIR /app - sets the working directory on the image to /app so the following instructions will be relative to this directory. If the directory doesn't exist it will be automatically created.

COPY package*.json . - copies the package.json and package-lock.json from the host machine to the working directory of the Docker image (.).

RUN npm install - installs all package dependencies defined in the package.json file copied in the last step. The packages are installed before copying the rest of the project (in the next step) to take advantage of Docker caching. Each step in a Dockerfile builds a new layer in the Docker image, and each layer in the stack is cached as long as it (and earlier layers) remain unchanged. So splitting these layers (install packages and copy project files) means the packages won't need to be re-installed every time you change some project source code.

COPY . . - copies the contents of the current directory (project files) on the host machine to the working directory (/app) on the Docker image, except files defined in .dockerignore.

RUN npm run build - executes npm run build on the Docker image to build/compile the Angular app into the output directory /app/dist/angular-tutorial.

FROM nginx - sets the base image for the next stage in this multi-stage Docker build to nginx.

RUN rm /etc/nginx/conf.d/default.conf - removes the default nginx config file from the Docker image as we'll be using the nginx.conf file created above.

COPY nginx.conf /etc/nginx/conf.d - copies the nginx.conf file from the host machine to the Docker image to configure Nginx to host the Angular application. When the Nginx container starts it automatically loads all *.conf files from the /etc/nginx/conf.d folder.

COPY --from=build /app/dist/angular-tutorial /usr/share/nginx/html - copies the compiled Angular app dist folder (/app/dist/angular-tutorial) from the previous build stage (--from=build) to the Nginx root folder (/usr/share/nginx/html) of the current stage.

EXPOSE 80 - informs Docker that the container listens on port 80. This doesn't actually publish the port to the outside world, it functions more as developer documentation on which ports are intended to be published. To actually publish the port use the -p flag with the docker run command and specify the port mapping, for example docker run -p 8080:80 ... would map host port 8080 to container port 80.


Create .dockerignore file

A .dockerignore file works just like a .gitignore file but for paths/patterns you want to exclude from your built Docker image.

The below paths were simply copied from the .gitignore file that was generated by the Angular CLI when the project was created (ng new ...).

Create a file named .dockerignore in the project root folder and copy the below rules into it:

# copied from .gitignore

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db


Build Docker Image for Angular App on Nginx

Run the following command from the root folder of the Angular app (where the Dockerfile is located). It will build a new Docker image with the tag angular-app.

docker build -t angular-app .


Docker Build Output

The output after running the docker build command should look something like this:

PS C:\Projects\angular-tutorial> docker build -t angular-app .
[+] Building 51.5s (18/18) FINISHED
 => [internal] load .dockerignore                                                                                                                               0.2s
 => => transferring context: 583B                                                                                                                               0.0s
 => [internal] load build definition from Dockerfile                                                                                                            0.2s
 => => transferring dockerfile: 488B                                                                                                                            0.0s
 => [internal] load metadata for docker.io/library/nginx:latest                                                                                                 4.5s
 => [internal] load metadata for docker.io/library/node:18                                                                                                      3.6s
 => [auth] library/nginx:pull token for registry-1.docker.io                                                                                                    0.0s
 => [auth] library/node:pull token for registry-1.docker.io                                                                                                     0.0s
 => [build 1/6] FROM docker.io/library/node:18@sha256:3f567a26b6b6d601fb2b168d4f987b50697617ead15bfc0e0152e600ac48d0fe                                          0.0s
 => [internal] load build context                                                                                                                               0.2s
 => => transferring context: 851.12kB                                                                                                                           0.1s
 => [stage-1 1/4] FROM docker.io/library/nginx@sha256:480868e8c8c797794257e2abd88d0f9a8809b2fe956cbfbc05dcc0bca1f7cd43                                          0.0s
 => CACHED [stage-1 2/4] RUN rm /etc/nginx/conf.d/default.conf                                                                                                  0.0s
 => [stage-1 3/4] COPY nginx.conf /etc/nginx/conf.d                                                                                                             0.2s
 => CACHED [build 2/6] WORKDIR /app                                                                                                                             0.0s
 => CACHED [build 3/6] COPY package*.json .                                                                                                                     0.0s
 => CACHED [build 4/6] RUN npm install                                                                                                                          0.0s
 => [build 5/6] COPY . .                                                                                                                                        0.2s
 => [build 6/6] RUN npm run build                                                                                                                              45.4s
 => [stage-1 4/4] COPY --from=build /app/dist/angular-tutorial /usr/share/nginx/html                                                                            0.6s
 => exporting to image                                                                                                                                          0.2s
 => => exporting layers                                                                                                                                         0.2s
 => => writing image sha256:75d3bf719ebff24c39210609ba3f18651e1de1b64536ac732f6f438c530700d6                                                                    0.0s
 => => naming to docker.io/library/angular-app


Run Docker Container with Angular App on Nginx

Run the following command to create and start a new Docker container from the angular-app image in detached (-d) mode, and publish it to port 8080 on the host machine.

docker run -d -p 8080:80 angular-app


Docker Run Output

The output should look like below, the identifier on the second line is the UUID of the running Docker container.

PS C:\Projects\angular-tutorial> docker run -d -p 8080:80 angular-app
003c75ae97b1b74c003ce51ecb3be3f1a0f9bf971c4ca3e3befa197a6418e56a


Open Angular App in Browser

Open the Angular App running on Docker with Nginx at the URL http://localhost:8080.


A Few Useful Docker Commands

Below are just a few handy docker commands available with the Docker CLI.

For a detailed list run docker --help, docker [COMMAND] --help or visit https://docs.docker.com/engine/reference/commandline/cli/.


View Container Logs

Follow the logs of the running container with the command docker logs -f <container id prefix>

The <container id prefix> can be just the first few characters of the UUID like below:

PS C:\Projects\angular-tutorial> docker logs -f 003
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/05/17 06:46:04 [notice] 1#1: using the "epoll" event method
2023/05/17 06:46:04 [notice] 1#1: nginx/1.23.4
2023/05/17 06:46:04 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/05/17 06:46:04 [notice] 1#1: OS: Linux 5.10.16.3-microsoft-standard-WSL2
2023/05/17 06:46:04 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023/05/17 06:46:04 [notice] 1#1: start worker processes
2023/05/17 06:46:04 [notice] 1#1: start worker process 20
2023/05/17 06:46:04 [notice] 1#1: start worker process 21
2023/05/17 06:46:04 [notice] 1#1: start worker process 22
2023/05/17 06:46:04 [notice] 1#1: start worker process 23
172.17.0.1 - - [17/May/2023:06:46:37 +0000] "GET / HTTP/1.1" 200 600 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" "-"
172.17.0.1 - - [17/May/2023:06:46:37 +0000] "GET /styles.ef46db3751d8e999.css HTTP/1.1" 200 0 "http://localhost:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" "-"
172.17.0.1 - - [17/May/2023:06:46:37 +0000] "GET /runtime.2da1bf5ccc0d9801.js HTTP/1.1" 200 2712 "http://localhost:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" "-"
172.17.0.1 - - [17/May/2023:06:46:37 +0000] "GET /polyfills.c837c4c22d57d22b.js HTTP/1.1" 200 33796 "http://localhost:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" "-"
172.17.0.1 - - [17/May/2023:06:46:37 +0000] "GET /main.312302f474ab0c01.js HTTP/1.1" 200 287067 "http://localhost:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" "-"
172.17.0.1 - - [17/May/2023:06:46:37 +0000] "GET /favicon.ico HTTP/1.1" 200 1642 "http://localhost:8080/account/login?returnUrl=%2F" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" "-"


Stop Container

Stop a running container with the command docker stop <container id prefix>

PS C:\Projects\angular-tutorial> docker stop 003
003


Start Container

Start a stopped container with the command docker start <container id prefix>

PS C:\Projects\angular-tutorial> docker start 003
003


List Running Containers

List all running containers with the command docker ps

PS C:\Projects\angular-tutorial> docker ps
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS              PORTS                  NAMES
003c75ae97b1   angular-app   "/docker-entrypoint.…"   2 minutes ago   Up About a minute   0.0.0.0:8080->80/tcp   funny_shamir

 


Need Some Angular 16 Help?

Search fiverr for freelance Angular 16 developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by