How to set up NestJS with Docker Compose

Updated: January 1, 2024 By: Guest Contributor Post a comment

Overview

If you’re looking to create a robust and efficient development environment for your NestJS applications, coupling NestJS with Docker Compose is a great approach. This setup will not only streamline your development process but also ensures consistent environments across different machines.

Prerequisites

  • Basic understanding of Docker and its concepts
  • Basic knowledge of NestJS framework and TypeScript
  • Docker and Docker Compose installed on your development machine
  • An existing NestJS project or willingness to create a new one

Setting Up a Basic NestJS Dockerfile

Let’s start by creating a simple Dockerfile for your NestJS application. This file defines how your application’s Docker image should be built.

# Dockerfile
FROM node:latest

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production

# Bundle app source
COPY . .

EXPOSE 3000
CMD [ "npm", "run", "start:prod" ]

This Dockerfile does the following:

  • Starts from the base Node.js image
  • Creates a working directory for your application code
  • Copies the package.json and potentially package-lock.json files and installs the dependencies
  • Copies the remaining application code
  • Exposes port 3000, which NestJS uses by default
  • Sets the container to start your app using the production start command

Writing the Docker Compose File

Next, you’ll craft a docker-compose.yml file that specifies how your Docker container should run. This includes defining services, volumes, and networks configuration.

# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    environment:
      NODE_ENV: production
    command: npm run start:prod

The docker-compose.yml does the following:

  • Defines a service called app
  • Indicates that the service should be built from the current directory (where the Dockerfile resides)
  • Maps port 3000 of the host to port 3000 of the container
  • Sets up volume mapping to enable live reloads on code changes and to not override node_modules on your host machine
  • Specifies environment variables
  • Sets the command to start your application in production mode

Integrating a Database Service

Often you will need a database service in your application stack. Let’s introduce a PostgreSQL database service to our docker-compose.yml.

# docker-compose.yml (extended)
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    environment:
      NODE_ENV: production
    depends_on:
      - db
    command: npm run start:prod
  db:
    image: postgres
    environment:
      POSTGRES_DB: your_db
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

The addition of the database service includes:

  • A new service db using the PostgreSQL image
  • Environment variables for the database setup
  • A named volume pgdata to persist database data
  • The app service now depends_on the database, ensuring the database starts first

Optimizing for Local Development

For a local development environment, you want to take advantage of NestJS’s hot-reload capabilities. Adjust your Docker Compose configuration for a better development experience.

# docker-compose.yml (optimized for development)
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    environment:
      NODE_ENV: development
    command: npm run start:dev

Significant changes include:

  • Environment variable NODE_ENV set to development
  • The command changed to npm run start:dev to support hot reloading

Handling Build Arguments and Environment Variables

If your application requires build-time variables or needs to inject environment variables more securely, you can utilize Docker Compose’s support for .env files and build arguments.

# docker-compose.yml (utilizing .env file)
version: '3.8'
services:
  # ...
  app:
    build:
      context: .
      args:
        - NODE_VERSION=${NODE_VERSION}
    environment:
      - DATABASE_URL=${DATABASE_URL}

A corresponding .env file should have:

# .env file
NODE_VERSION=14
DATABASE_URL=postgres://user:password@db:5432/your_db

The above changes now mean:

  • Use of a variable NODE_VERSION in the build process
  • An environment variable DATABASE_URL read from the .env file when starting up

Adding Health Checks and Dependencies

Good container orchestration should also include health checks for your services. Taking it a step further, you can define the dependency condition based on service health.

# docker-compose.yml (with health checks)
version: '3.8'
services:
  # ...
  app:
    # ...
    depends_on:
      db:
        condition: service_healthy
  db:
    # ...
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5

This update to the Docker Compose file ensures the app service does not start until the db service passes its health check.

Scaling Your NestJS Services with Docker Compose

Docker Compose allows you to scale out services, which can be useful during load testing or when your application traffic varies. This can be specified within the Compose file or through command-line options.

# Command to scale app service instances using Docker Compose CLI
docker-compose up --scale app=3 -d

The command docker-compose up --scale app=3 -d starts three instances of the app service.

Using Docker Compose in Production

When moving to production, remember to:

  • Use an image registry instead of building images on the fly
  • Ensure environment variables are kept secure
  • Configure logging and monitoring for your services
  • Utilize orchestration solutions like Kubernetes or Swarm if scaling is necessary

Conclusion

In this walkthrough, you’ve learned how to configure a Docker Compose environment for your NestJS application, integrate a database, optimize for development, handle configuration securely, add health checks, and prepare for scaling and production. Embrace the power of Docker to make your NestJS applications more resilient and portable across different environments.