Overview

We will be using Docker, which provides vm-like containers to easily deploy, scale, and replicate. Read more here.

The project has three parts: client, server, and the database. Using docker, we’ll spin up images for each part.

You can download Docker Desktop to have a visual representation of your images and containers.

Client

In /client, we are adding 3 files: .dockerignore, Dockerfile, and default.conf.template to the root directory.

The .dockerignore will ignore specific files when creating the image.

build
.dockerignore
**/.git
**/.DS_Store
**/node_modules
Dockerfile
**/npm-debug.log

The Dockerfile will create the client image used in the container. To create the production build, the script creates a production build of the app, then does it again without Typescript, and finally creates the image with the NGINX reverse-proxy.

FROM node:16-alpine3.12 as builder
WORKDIR /app
COPY package.json ./
COPY package-lock.json ./
COPY tsconfig*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:16-alpine3.12 as ts-remover
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/build ./
RUN npm install --only=production

FROM nginx:stable-alpine as production
ENV NODE_ENV production
# Copy built assets from ts-remover
COPY --from=ts-remover /app /usr/share/nginx/html
# Add your nginx.conf
COPY default.conf.template /etc/nginx/templates/
# Expose port
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

The default.conf.template is used for NGINX to reverse-proxy any requests with /api prefix to the backend.

<aside> ‼️ If the backend doesn’t use the /api prefix for its API, then you’ll have to specify the specific server port in all your API requests (i.e. http://domain_name:8080/server-route). Moreover, you won’t need the location /api block.

</aside>

server {
  listen 80;
  location /api {
    client_max_body_size 128M;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_redirect off;

    proxy_pass <http://server:8080>;
    proxy_cache_bypass $http_upgrade;
  }

  location / {
    root /usr/share/nginx/html/;
    include /etc/nginx/mime.types;
    try_files $uri /index.html;
  }
}

Server

In /server, add two files: .dockerignore and Dockerfile.

build
.dockerignore
**/.git
**/.DS_Store
**/node_modules
Dockerfile
**/npm-debug.log

The Dockerfile creates an image without Typescript.

FROM node:16-alpine3.12 as builder
WORKDIR /app
COPY package.json ./
COPY package-lock.json ./
COPY tsconfig*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:16-alpine3.12 as ts-remover
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/build ./
RUN npm install --only=production

# ---------- Release ----------
FROM builder as release
WORKDIR /app
COPY --from=ts-remover /app ./
CMD ["npm", "run", "start:prod"]

Creating the container