I wrote about this exact topic earlier:
Dockerize Nestjs app with Postgres + Redis + Prisma ORM
Manuchehr ・ Dec 28 '23
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"]
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 hereentrypoint.shit's abashfile 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.shfile, make sure to install dev dependencies by removing--omit=devflag fromyarn install. Also you need to copynode_modulesto final stage.
ENV NODE_ENV production: Always run your Nestjs app onproductionenvironment. 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 hereUSER node: Usenodeusernode:22-alpineimage provides. Because you really don't want to run your app asrootuser. Read hereCMD ["node", "dist/main.js"]: It's good way to run your node/nestjs application with directlynodecommand instead ofnpm 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
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
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)