DEV Community

Cover image for Dockerize Secure Nestjs app with Postgres & Redis
Manuchehr
Manuchehr

Posted on

Dockerize Secure Nestjs app with Postgres & Redis

I wrote about this exact topic earlier:

So why again? First of all, my previous post was more about Nestjs + prisma and basic Dockerfile, in this post I'm going to cover secure general Dockerization for Nestjs.

Let's create a Dockerfile

# Stage 1: Build the app
FROM node:22-alpine AS builder

WORKDIR /usr/src/app

COPY package*.json ./
COPY entrypoint.sh ./
COPY .env ./

RUN yarn install --omit=dev

COPY . .

RUN yarn build

# Stage 2: Setup prod
FROM node:22-alpine

WORKDIR /usr/src/app

COPY --from=builder /usr/src/app/dist ./dist
COPY --from=builder /usr/src/app/entrypoint.sh ./
COPY --from=builder /usr/src/app/package*.json ./
COPY --from=builder /usr/src/app/.env ./

ENV NODE_ENV production

RUN chmod +x ./entrypoint.sh

USER node

ENTRYPOINT ["./entrypoint.sh"]
CMD ["node", "dist/main.js"]
Enter fullscreen mode Exit fullscreen mode

As you can see, Dockerfile consists of two stages: Build & Prod. We need this to minimize build size by only copying essential files or directories (dist) to the final stage. I'm not going deep into Dockerfile in this post, but I'll explain some of the lines:

  • RUN yarn install --omit=dev: Install only production dependencies possible. Read here

  • entrypoint.sh it's a bash file containing commands run before our Nestjs application. For example, you may have to run db migrations before Nestjs starts check my previous post.

⚠️ REMEMBER: If you use dev commands in entrypoint.sh file, make sure to install dev dependencies by removing --omit=dev flag from yarn install. Also you need to copy node_modules to final stage.

  • ENV NODE_ENV production: Always run your Nestjs app on production environment. When you build your Node.js Docker image for production, you want to ensure that all frameworks and libraries are using the optimal settings for performance and security. Read here

  • USER node: Use node user node:22-alpine image provides. Because you really don't want to run your app as root user. Read here

  • CMD ["node", "dist/main.js"]: It's good way to run your node/nestjs application with directly node command instead of npm run start:prod (don't do that like I did in my prev post :D). Read here

Bonus

Docker services (Postgres & Redis) setup

Create docker-compose.yaml file:

services:
  app:
    container_name: 'nestjs-app'
    restart: always
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      REDIS_HOST: redis
      DB_HOST: postgres
      PORT: ${PORT}
    ports:
      - ${PORT}:${PORT}
    networks:
      - nestjs-net
    depends_on:
      redis:
        condition: service_healthy
        restart: true
      postgres:
        condition: service_healthy
        restart: true

  redis:
    container_name: 'nestjs-redis'
    image: bitnami/redis:7.4
    restart: always
    ports:
      - ${REDIS_PORT}:${REDIS_PORT}
    environment:
      REDIS_PASSWORD: ${REDIS_PASSWORD}
      REDIS_PORT_NUMBER: ${REDIS_PORT}
      REDIS_DB: ${REDIS_DB}
      REDIS_IO_THREADS: 4
      REDIS_IO_THREADS_DO_READS: yes
      REDIS_DISABLE_COMMANDS: FLUSHDB,FLUSHALL
    healthcheck:
      test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - nestjs-redis-data:/bitnami/redis/data
    networks:
      - nestjs-net

  postgres:
    container_name: 'nestjs-postgres'
    image: bitnami/postgresql:17
    restart: always
    environment:
      POSTGRESQL_PORT_NUMBER: ${DB_PORT}
      POSTGRESQL_USERNAME: ${DB_USER}
      POSTGRESQL_PASSWORD: ${DB_PASSWORD}
      POSTGRESQL_DATABASE: ${DB_NAME}
      POSTGRESQL_TIMEZONE: 'Asia/Tashkent' // Set your timezone
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U postgres']
      interval: 5s
      timeout: 5s
      retries: 5
    ports:
      - ${DB_PORT}:${DB_PORT}
    volumes:
      - nestjs-postgres-data:/bitnami/postgresql
    networks:
      - nestjs-net

volumes:
  nestjs-redis-data:
    driver: local
  nestjs-postgres-data:
    driver: local

networks:
  nestjs-net:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Example .env file:

PORT=9000
# Redis
REDIS_PASSWORD=
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
# Postgres
DB_USER=
DB_PASSWORD=
DB_NAME=
DB_PORT=5432
DB_HOST=localhost
Enter fullscreen mode Exit fullscreen mode

That's it! Thanks for reading. I don't say it's 100% secure Docker image because you can always implement something better. If you find any mistake please leave a comment. For more visit: OWASP Node.js Docker Cheat Sheet

Top comments (0)