Fullstack NestJS

Deploying


The Goal

The Code

# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/

ARG NODE_VERSION=20.11.0

################################################################################
# Use node image for base image for all stages.
FROM node:${NODE_VERSION}-alpine as base

# Set working directory for all build stages.
WORKDIR /usr/src/app


################################################################################
# Create a stage for installing production dependecies.
FROM base as deps

# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.npm to speed up subsequent builds.
# Leverage bind mounts to package.json and package-lock.json to avoid having to copy them
# into this layer.
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev

################################################################################
# Create a stage for building the application.
FROM deps as build

ARG SERVER_DIR=users

# Copy the rest of the source files into the image.
COPY ./apps/${SERVER_DIR} ./apps/${SERVER_DIR}

## TODO: select only one db, currently errors out if a service does not have a db.
COPY ./databases ./databases
COPY tsconfig.build.json .
COPY tsconfig.json .
COPY package.json .
COPY package-lock.json .

RUN npm install -g @nestjs/cli

 # Build the application.
RUN npm ci && nest build ${SERVER_DIR}



################################################################################
# Create a new stage to run the application with minimal runtime dependencies
# where the necessary files are copied from the build stage.
FROM base as final

# Use production node environment by default.
ENV NODE_ENV production

# Run the application as a non-root user.
USER node

# Copy package.json so that package manager commands can be used.
COPY package.json .

# Copy the production dependencies from the deps stage and also
# the built application from the build stage into the image.
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY --from=build /usr/src/app/dist ./
COPY --from=build /usr/src/app/databases ./databases


# Expose the port that the application listens on.
EXPOSE 3500

# Run the application.
CMD node main.js

This Dockerfile is used to create a Docker image for a Node.js application. It uses a multi-stage build process to create a lean final image with only the necessary dependencies and compiled code.

The first stage, named base, starts with a Node.js image based on a specific version (20.11.0) of Alpine Linux, which is a lightweight Linux distribution. The working directory for all build stages is set to /usr/src/app.

The second stage, named deps, is used to install the production dependencies of the application. It starts from the base image and uses Docker's caching mechanism to speed up the build process. The npm ci --omit=dev command is used to install dependencies specified in package.json and package-lock.json, omitting development dependencies.

The third stage, named build, is used to compile the application. It starts from the deps image, copies the source files into the image, and installs the NestJS CLI globally. Then, it runs npm ci to install any remaining dependencies and nest build to compile the application.

The final stage, named final, is used to create the final Docker image. It starts from the base image, sets the Node.js environment to production, and switches to a non-root user for security reasons. It then copies the package.json file, the production dependencies from the deps stage, and the compiled application from the build stage into the image. The application's listening port (3500) is exposed, and the application is started with the node main.js command.

Multi-Stage Builds

Multi-stage builds in Docker serve several purposes:

  1. Image Size Reduction: By using multi-stage builds, you can drastically reduce the size of your final Docker image. In the initial stages, you can install all the tools and dependencies needed to build your application, and then in the final stage, you can copy only the built artifacts and necessary runtime dependencies. This leaves out all the unnecessary build tools and intermediate files, resulting in a much smaller image.

  2. Separation of Concerns: Each stage in a multi-stage build can focus on a specific task. For example, one stage can be used for building the application, another for running tests, and another for creating the final runtime image. This makes the Dockerfile easier to understand and maintain.

  3. Caching and Build Speed: Docker can cache the results of each stage separately. This means if you make a change that affects one stage, Docker only needs to rebuild that stage and the ones that depend on it. This can significantly speed up the build process.

4 Security: By using multi-stage builds, you can ensure that only the necessary files and dependencies are included in the final image, reducing the attack surface for potential security vulnerabilities.

Resources

How to deploy a NestJs App for free on Vercel

< Nest Home