Nx, NestJs, React — Docker Deploys

Kristan 'Krispy' Uccello
The Startup
Published in
4 min readDec 10, 2020

In this ongoing series of things I do with my projects to make them work, I thought it might be good to talk about setting up deployments. After all, you spend all this time building an app — knowing how to package it up and run it can be useful too.

Some assumptions about who you are:
- you know enough docker stuff to be dangerous (docker-compose included)
- you know the difference between running a dev, test and production build
- you are familiar with Nginx
- you have an Nx workspace setup with an app composed of a react client and an api server (I like to use NestJs for my api apps)

Part 1: How this all fits together

What we want to have at the end of this is an easily deployable app stack that is made up of a set of docker containers that collectively work together to serve your app client and apis.

Your react client, will need to be able to talk to your api server(s). This will be accomplished using Nginx proxy directives (will cover that later in this article).

Where the various parts live in the source tree

Part 2: Docker Details

To make this work we will build a base docker image that the app level Dockerfiles will use as their base image. The reason for this is that Nx workspaces are monorepos and each dockerized app image needs the entire repo workspace to be able to build itself properly. We can avoid the problem of duplicating file copies and shorten the time to build our docker images by creating this base docker image.

Here is the Dockerfile source I use for the top level base image. This is the Dockerfile located at <project-root>/Dockerfile

FROM node:lts-alpine3.10 as builderARG NODE_ENV
ARG BUILD_FLAG
WORKDIR /app/builder
COPY . .
RUN npm i

Also <project-root>/.dockerignore (so we don’t copy things we don’t need when making our docker images)

node_modules
dist
tmp
.vscode

To build our base image simply execute the following from the project root in your terminal.

docker build . -t my-base-image:nx-base

After running the docker build we have a new Docker image available which has all the source details and most importantly, the node_modules. It is worth noting that if/when you make changes to your node modules (adding/removing npm libs — also if you add new project level libs) you will need to rebuild this base image by running the above build step again. Think of this base image as crystalizing your project shared libraries and node_modules (which can take some time to install). Again, we are doing this to avoid paying the time cost when building our app level images (which would involve having this npm install process execute more than once)

Next, let us now look at the app level Dockerfiles. We will start with what the api server app Dockerfile should look like (located at: <project-root>/apps/api-server/Dockerfile)

This Dockerfile skips the step of installing the npm modules since that has already been performed and cached in the base image we created earlier. There are two build steps in this docker file — the first copies the latest source content into the image and then executes the Nx build step for the specific app we are interested in. The second build step for the docker file sets up the image to actually run the api server when a container is launched. Don’t build this docker image yet, we will get to that in a moment. We need to look at the React client Dockerfile next. (located at: <project-root>/apps/react-client/Dockerfile)

This Dockerfile is similar to our api-server Dockerfile but with a few key changes. Mainly, after performing the build step that compiles the react-client using Nx build the Dockerfile defines its next build step to create a context that leverages nginx to serve the react client as built by Nx. The last line in the above Dockerfile copies an nginx.conf file from the app source into the image. Let’s take a quick look at that file before we move onto how these two images get built. One final note here, because our last build step does not define a CMD it defaults to the one provided by the nginx image being used as the base image for that last build step — this means we can continue to use all the run options and such that the nginx image offers.

<project-root>/apps/react-client/nginx.conf

Wow, lots of config so far! But now the exciting part — building and running these images! To pull this last part off we will be taking advantage of docker compose. It offers a nice path to centralize the definition of what containers need to be running together and will allow us to define a private network for these two service images to communicate with each other. So, lets open up the <project-root>/docker-compose.yml file and see what goes in there:

Part 3: Running our images as containers

At this point everything is in place to actually build and run your application in a set of containers. All thats left to do is:

docker-compose up

If you just want to build your images to use on another system (GCP, AWS etc) you can simply do a:

docker-compose build

And your two container images will be available to you for retagging and pushing to the desired end point.

--

--